Changing the colour along a path (without changing the entire path)

Hi all, I’m drawing a line using a path, and I’d like the line to change color of a section of the path without changing the entire line. Here are some of the many solutions I’ve tried…

auto c = themeColor.withAlpha (isOn ? 0.0f : 1.0f);
myPath.lineTo (currentX, currentY);
g.setColor (c);
g.strokePath (myPath, juce::PathStrokeType (12, juce::PathStrokeType::curved));
myPath.lineTo (currentX, currentY);
g.setColor (c);
g.beginTransparencyLayer (isOn ? 0.0f : 1.0f);
g.strokePath (myPath, juce::PathStrokeType (12, juce::PathStrokeType::curved));
g.endTransparencyLayer();
// normalizedX is position along the x-axis between 0 and 1
grad = juce::ColourGradient (themeColor, 0, 0, themeColor, static_cast<float> (getWidth()), 0, false);

if (isOn)
    grad.addColour (normalizedX, juce::Colours::transparentBlack);

myPath.lineTo (currentX, currentY);
g.setGradientFill (grad);
g.strokePath (myPath, juce::PathStrokeType (12, juce::PathStrokeType::curved));

I think this can be achieved with 2 paths, but the reason I’m not giving up is because I’ve solved this problem with one. I vaguely remember somehow mapping the transparency of a ColourGradient to isOn but can’t remember how I got it to change only part of the path, rather than the entire path at one moment in time.

Any help is appreciated!

You could use setTiledImageFill() using an image with the desired area with the given colour.

Might not work so great if the path you’re rendering is dynamic (or more specifically if the area that needs to be a different colour needs to change). Say you wanted to have a line be red on the left half of the screen and blue on the right half, you could have something like:

juce::Image getImageForPathStroke()
{
    juce::Image img {juce::Image::RGB, width, height, true};
    juce::Graphics g {img};

    g.fillAll (juce::Colours::red);

    g.setColour(juce::Colours::blue);
    g.fillRect (bounds.removeFromRight (bounds.getWidth() / 2));
}

void paint (juce::Graphics& g) override
{
    const auto strokeImg = getImageForPathStroke();
    g.setTiledImageFill (strokeImg, 0, 0, 1.0f);

    g.strokePath (path, strokeType);
}

Even if it’s dynamic, you could change the way you fill the given area so it covers the segment of the path that needs to be a different colour.

Or, another way of doing basically the same thing, use juce::PathStrokeType::createStrokedPath to turn the path into one that can be filled instead of stroked. Then use juce::Graphics::reduceClipRegion and pass in the stroked path. Then you can draw to the graphics context while it’s clipped to the line so you can fill the colours in the areas you need.

Thanks for your help @ImJimmi ! I actually found an easier way…

Step 1: Set the gradient when you begin drawing…

void reset()
{
    myPath.clear();
    myPath.startNewSubPath (borderSize, currentY);
    grad = juce::ColourGradient (themeColor, 0, 0, themeColor, static_cast<float> (getWidth()), 0, false);
}

Step 2: Use addColour() to change the color while drawing

void paint (juce::Graphics& g)
{
    // Scale the current x coordinate to 0.0 - 1.0
    float xPercent = juce::jmap (float(currentX), 0.0f, (float)getWidth() - 1, 0.0f, 1.0f);

    grad.addColour (xPercent, themeColor.interpolatedWith (juce::Colours::darkgrey,
                                                               isOn ? 1.0f : 0.0f));
    
    myPath.lineTo (currentX, currentY);
    g.setGradientFill (grad);
    g.strokePath (myPath, juce::PathStrokeType (12, juce::PathStrokeType::curved));
}
2 Likes