Jump to content

Display Color Change Direction


Recommended Posts

This is a plugin I wrote to test a method for computing the color gradient, but which might be useful for non-testing reasons. I call it (the rather unwieldy) "Display Color Change Direction" because "color gradient" has another common meaning for plugins, and I didn't want to be confusing. The maximum direction of the color change is shown by the hue. It's in the Effects>Stylize menu.


The interface is:




The Color Scale controls control the color brightness. To allow for a wide range, I have coarse and fine controls.

Color Range expands or compresses the range of colors. Because the colors no longer form a continuous circle, some anomalies may a occur when adjacent colors with almost the same color-change direction are colored with colors at the opposite ends of the color range. This problem can be reduced by using Reflected Color Range.

Color Shift shifts the hue around the color circle.

Color Phase changes which color is associated with which color-change direction. When the full range of colors is used, it duplicates the function of the Color Shift control. When used with a restricted range, the Color Shift selects which colors are used, and the Color Phase selects how the current range of colors are associated with the color change directions.

Reverse Color Order reverses the order around the color circle.

Full Circle assigns a direction to the color change. Normally, since the color changes as rapidly in the opposite direction, the direction of maximum color change is between 0° and 180°. When this option is set, a direction is assigned to the color, based on the change in intensity of the back-and-white image. I made True the default, both because I think the images generally look better, and because it makes the encoded color change direction correspond to the PDN color wheel.

Reflected Color Range reflects the current color range so instead of the going from the beginning color to the ending color, it goes from the beginning color to the ending color at mid-range, then in the opposite order, back to the beginning color. (E.g., Red->Yellow->Green->Yellow->Red.) This makes the color range continuous around the circle of color-directions, avoiding color anomalies when using restricted color ranges.

Transparent Background colors makes the background transparent instead of black.


As I've mentioned, the purpose was to test the algorithm, but it can be used to produce some interesting effects. For example, the background for this sig was produced using only Clouds and this plugin:



The plugin can also be used for edge detection. For example, by running the plugin, then converting to black-and-white and inverting the colors.


Here is the CodeLab code:

Hidden Content:
// Author: MJW
// Name: Display Color Change Direction
// Title: Display Color Change Direction
// Submenu: Stylize
// Desc: Show the direction of the maximum color change as a color.
// Keywords: color change direction
#region UICode
double Amount1 = 10;    //[0, 25]Color Scale (Coarse)
double Amount2 = 0;    //[-1, 1]Color Scale (Fine)
double Amount3 = 1.0;    //[0, 2]Color Range
double Amount4 = 0.0;    //[0, 1]Color Shift
double Amount5 = 0.0;   //[0, 1]Color Phase
bool Amount6 = false; //Reverse Color Order
bool Amount7 = true; //Full Circle
bool Amount8 = false; //Reflected Color Range
bool Amount9 = false; //Transparent Background

Surface Src, Dst;
int maxX, maxY;
double colorScale;
bool maxScale;
double colorShift;
double colorPhase;
double colorRange;
double colorOrder, colorOrderAdj;
bool fullCircle;
bool reflectColors;
bool transparentBackground;

const int middleWeight = 2;
const double angleScale = 1.0 / Math.PI;

void Render(Surface dst, Surface src, Rectangle rect)
    colorScale = 0.002 * (Amount1 + Amount2);
    if (colorScale < 0)
        colorScale = 0;
    maxScale = (colorScale >= 25.0);    // If scale is max., display all colors at full range.
    colorScale *= 4.0 / (2.0 + (double)middleWeight);
    colorShift = Amount4;
    colorPhase = Amount5;
    colorRange = Amount3;
    if (Amount6) // Sign of scaling chosen to match color menu wheel.
        colorOrder = -angleScale;
        colorOrderAdj = 1.0;
        colorOrder = angleScale;
        colorOrderAdj = 0.0;
    fullCircle = Amount7;
    reflectColors = Amount8;
    transparentBackground = Amount9;
    Src = src;
    Dst = dst;
    Rectangle selection = this.EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
    int left = rect.Left;
    int right = rect.Right;
    int top = rect.Top;
    int bottom = rect.Bottom;

    maxX = src.Width - 1;
    maxY = src.Height - 1;

    for (int y = top; y < bottom; y++)
    for (int x = left; x < right; x++)
            dst[x, y] = FindGradientColor(x, y);

protected ColorBgra FindGradientColor(int x, int y)
    ColorBgra MM, UM, UR, MR, LR, LM, LL, ML, UL;
    ColorBgra gradColor = ColorBgra.Black;
    int lX, rX, uY, lY;
    lX = (x == 0) ? x : x - 1;
    rX = (x == maxX) ? x : x + 1;
    uY = (y == 0) ? y : y - 1;
    lY = (y == maxY) ? y : y + 1;
    MM = Src.GetPointUnchecked(x, y);
    UM = Src.GetPointUnchecked(x, uY);
    UR = Src.GetPointUnchecked(rX, uY);
    MR = Src.GetPointUnchecked(rX, y);
    LR = Src.GetPointUnchecked(rX, lY);
    LM = Src.GetPointUnchecked(x, lY);
    LL = Src.GetPointUnchecked(lX, lY);
    ML = Src.GetPointUnchecked(lX, y);
    UL = Src.GetPointUnchecked(lX, uY);
    int xR = (UL.R - UR.R) + middleWeight * (ML.R - MR.R) + (LL.R - LR.R);
    int xG = (UL.G - UR.G) + middleWeight * (ML.G - MR.G) + (LL.G - LR.G);
    int xB = (UL.B - UR. + middleWeight * (ML.B - MR. + (LL.B - LR.;
    int yR = (UL.R - LL.R) + middleWeight * (UM.R - LM.R) + (UR.R - LR.R);
    int yG = (UL.G - LL.G) + middleWeight * (UM.G - LM.G) + (UR.G - LR.G);
    int yB = (UL.B - LL. + middleWeight * (UM.B - LM. + (UR.B - LR.;

    int xDelta, yDelta, mag2;
    // The straight-forward implementation.
    // The change in each color component can either be consdered positive or negative.
    // Since changing all the signs won't change the magnitude of the change, red is
    // fixed and blue and green can have either sign.
    // Compute all four versions and choose the on that gives the largest magnitude.
    int xDeltaA, yDeltaA, xDeltaB, yDeltaB, xDeltaC, yDeltaC;
    int mag2A, mag2B, mag2C;
    xDelta = xR + xG + xB; yDelta = yR + yG + yB;
    xDeltaA = xR + xG - xB; yDeltaA = yR + yG - yB;
    xDeltaB = xR - xG + xB; yDeltaB = yR - yG + yB;
    xDeltaC = xR - xG - xB; yDeltaC = yR - yG - yB;
    mag2 = xDelta * xDelta + yDelta * yDelta;
    mag2A = xDeltaA * xDeltaA + yDeltaA * yDeltaA;
    mag2B = xDeltaB * xDeltaB + yDeltaB * yDeltaB;
    mag2C = xDeltaC * xDeltaC + yDeltaC * yDeltaC;

    // Use the one with the largest magnitude.
    if (mag2A > mag2)
        xDelta = xDeltaA; yDelta = yDeltaA; mag2 = mag2A;
    if (mag2C > mag2B)
        xDeltaB = xDeltaC; yDeltaB = yDeltaC; mag2B = mag2C;
    if (mag2B > mag2)
        xDelta = xDeltaB; yDelta = yDeltaB; mag2 = mag2B;
    if (mag2 == 0)
        return transparentBackground ? ColorBgra.Transparent : ColorBgra.Black;
        // Adjust sign of deltas so yDelta >= 0. This will produce an ATan between 0 and PI.
        if (yDelta < 0)
            xDelta = -xDelta; yDelta = -yDelta;
        double magnitude = Math.Sqrt((double)mag2);
      double xComponent = xDelta / magnitude;
        double yComponent = yDelta / magnitude;
      double value = maxScale ? 1.0 : Math.Min(1.0, colorScale * magnitude);
        // 0 <= hue <= 1.0
      double hue = colorOrder * Math.Atan2(yComponent, xComponent) + colorOrderAdj;

      // If full circle, use the intensity to determine the direction.
        if (fullCircle)
            hue *= 0.5;
            double intensity = xComponent * (xR + xG + xB) + yComponent * (yR + yG + yB);
            if (intensity > 0.0)
                hue += 0.5;
        // Adjust the phase before restricting the range.
        hue += colorPhase;
        if (hue >= 1.0)
            hue -= 1.0;
        // Reflected colors match at the endpoints for better restricted range colors.
        if (reflectColors)
            hue *= 2.0;
            if (hue > 1.0)
                hue = 2.0 - hue;
        hue = colorRange * hue + colorShift;
        gradColor = transparentBackground ?
            HSVtoRGBA(hue, 1.0, 1.0, value) :
            HSVtoRGB(hue, 1.0, value);

        return gradColor;

public ColorBgra HSVtoRGBA(double H, double S, double V, double A)
    byte r, g, b;
    HSVtoRGB(H, S, V, out r, out g, out ;
    return ColorBgra.FromBgra(b, g, r, (byte)(255 * A + 0.5));

public ColorBgra HSVtoRGB(double H, double S, double V)
    byte r, g, b;
    HSVtoRGB(H, S, V, out r, out g, out ;
    return ColorBgra.FromBgr(b, g, r);

public void HSVtoRGB(double H, double S, double V, out byte bR, out byte bG, out byte bB)
    // Parameters must satisfy the following ranges:
    // 0.0 <= H < 1.0
    // 0.0 <= S <= 1.0
    // 0.0 <= V <= 1.0

    // Handle special case of gray (so no Hue) first
    if ((S == 0.0) || (V == 0.0))
        byte x = (byte)(int)(V * 255.0);
        bR = x;
        bG = x;
        bB = x;

    H = HueConstrain(H);

    double R = V, G = V, B = V;
    double Hi = Math.Floor(6.0 * H);
    double f = 6.0 * H - Hi;
    double p = V * (1.0 - S);
    double q = V * (1.0 - f * S);
    double t = V * (1.0 - (1.0 - f) * S);
    if (Hi == 0.0)
        R = V;
        G = t;
        B = p;
    else if (Hi == 1.0)
        R = q;
        G = V;
        B = p;
    else if (Hi == 2.0)
        R = p;
        G = V;
        B = t;
    else if (Hi == 3.0)
        R = p;
        G = q;
        B = V;
    else if (Hi == 4.0)
        R = t;
        G = p;
        B = V;
    else // if (Hi == 5.0)
        R = V;
        G = p;
        B = q;

    int iR = (int)(R * 255.0 + 0.5);
    int iG = (int)(G * 255.0 + 0.5);
    int iB = (int)(B * 255.0 + 0.5);
    bR = (byte)iR;
    bG = (byte)iG;
    bB = (byte)iB;

public double HueConstrain(double MyHue)
    // Makes sure that 0.0 <= MyAngle < 1.0
    // Wraps around the value if its outside this range
    while (MyHue >= 1.0)
        MyHue -= 1.0;
    while (MyHue < 0.0)
        MyHue += 1.0;
    return MyHue;



Here's the (not especially attractive!) icon: post-53337-0-41731200-1432442549.png

Here is the plugin: DisplayColorChangeDirection.zip


EDIT: Fixed spelling of "Coarse" (H/T, Djisves). Changed version to 1.1.

EDIT: Restored icon, which I forgot in the previous version (sorry about that). Changed version to 1.2.

EDIT: Added Color Range control. Changed version to 1.3.

EDIT: Added Color Phase control. Changed version to 1.4.

EDIT: Removed mostly unnecessary Middle Weight and Original Image controls. Replaced (at Eli's suggestion) White Background with Transparent Background. Added Reflected Color Range for improved colors when using restricted ranges. Changed version to 2.0.


Link to post
Share on other sites

The name is pretty lame. I originally called it "Display Color Gradient," which is slightly better, but not much snappier. For the released version, I thought if I kept the original name, people might think it rendered a color gradient. For reasons I won't go into here, I was in a hurry to release it, and went with the first semi-sensible name that came to mind.

Link to post
Share on other sites

If a descriptive name doesn't pop into your thoughts try an offbeat one.

"Rainbow Eye Bleed" - because it makes my eyes whimper.... :D

Link to post
Share on other sites

"Rainbow Eye Bleed" - because it makes my eyes whimper....


For those who want a bit more subtlety in their images, I added a Color Range feature.


Here is an example of plugin applied with a restricted range to Clouds:




Here It's applied to Clouds processed with Dents:



There's sometimes somewhat of a problem with color-direction angle wrap-around producing stray pixels that contrast with the surrounding region, because unlike the full color wheel, the boundary colors don't connect continuously. This can result in two pixels with nearly that same color gradient being colored with colors at the opposite ends of the color range.  I thought about some options, such as reflecting the color range on either side of the range of direction angles, but decided to leave it as it was. I think it's still a useful feature. I'm not so sure that the ability to expand the range is useful or makes sense, but I left it in, in case it is and does.

Link to post
Share on other sites

Assuming I didn't somehow zip up and upload the wrong DLL (and I don't think I did), I can't imagine what those not getting results are doing wrong. If you run Clouds with the default settings, then run my Display Color Change Direction plugin with its default settings, you should get a bright pattern of colors. The plugin obviously worked for Seerose and Red ochre.


EDIT: I just downloaded and installed the plugin attached in my lead post (Version 1.4), and it worked perfectly (or at least as perfectly as it works).

Link to post
Share on other sites

So exactly how does this plugin work ? I have tried it on after running clouds using yellow and red for my colours and I don't get anything like the images posted here.


Though you can run it on colored Clouds and it will still work, you can run it on black-and-white Clouds, also. The colors it generates don't directly depend on the colors of image. The colors are chosen based on the direction which the image color most rapidly changes; for black and white images, that's the direction the intensity most rapidly changes. The color-change direction is determined by looking at the pixel's eight neighbors.

Link to post
Share on other sites

I wonder if a transparent (alpha) background option could be added (just like white background)


That sounds like a good idea. I'll probably do that.


EDIT: I wonder if it would be better to just replace the White Background option with the Transparent Background option. I'm not sure the white background is so generally useful that it needs a special option.  If the user wants a white background, it might be better to just add a white background layer and use the transparent option. If there are any opinions on this, I'd appreciate hearing them. If there are no opinions otherwise, I'll probably remove White Background when I add Transparent Background.

Link to post
Share on other sites


This topic is now archived and is closed to further replies.

  • Create New...