How to interpolate a color sequence?

10,183

Solution 1

Your code is mostly correct, but you are doing the interpolation backwards: i.e. you are interpolating B->A, then C->B, then D->C, etc. This causes the discontinuity when switching colors.

You should replace this:

colorT.r = colors[colorsIndex].r * p + ( colors[colorsIndex+1].r * ( 1.0 - p ) );

with:

colorT.r = colors[colorsIndex].r * (1.0 - p) + ( colors[colorsIndex+1].r * p );

and the same for the other lines.

Also, as others have said, using a different color space than RGB can provide better looking results.

Solution 2

There are two ways to handle interpolating colors. One is fast and easy (what you're doing), the other is slightly slower but can look better in some circumstances.

The first is the obvious, simple method of (x * s) + (y * (1-s)), which is pure linear interpolation and does what the name suggests. However, on certain color pairs (say green and orange), you get some nasty colors in the middle (a dirty brown). That's because you're lerping each component (R, G and B) and there are points where the combination is unpleasant. If you just need the most basic lerp, then this is the method you want, and your code is about right.

If you want a better-looking but slightly slower effect, you'll want to interpolate in HSL colorspace. Since the hue, saturation and lum are each interpolated, you get what color you would expect between them and can avoid a majority of the ugly ones. Since colors are typically drawn in some sort of wheel, this method is aware of that (where as basic RGB lerp acts like it's working with 3 discrete lines).

To use an HSL lerp, you need to convert the RGB values, lerp between the results, and convert back. This page has some formulas that may be useful for that, and this one has PHP code to handle it.

Solution 3

Interpolating the R, G, and B components will produce working code. The one shortcoming is that the steps you produce won't necessarily appear the same, even though they're mathematically equal.

If that bothers you, you could convert values from RGB to something like L*a*b* (which is designed to correspond more closely to human perception), do your interpolation on those values, and then convert each interpolated value back to RGB for display.

Solution 4

What you've got already looks very good, but I'd simplify the math a little bit:

int millisNow = ofGetElapsedTimeMillis();
int millisSinceLastCheck = millisNow % timerPeriod;
int colorsIndex = (millisNow / timerPerod) % (colors.size() - 1);


float p = (float)(millisSinceLastCheck) / (float)(timePeriod);
colorT.r = colors[colorsIndex+1].r * p + ( colors[colorsIndex].r * ( 1.0 - p ) );
colorT.g = colors[colorsIndex+1].g * p + ( colors[colorsIndex].g * ( 1.0 - p ) );
colorT.b = colors[colorsIndex+1].b * p + ( colors[colorsIndex].b * ( 1.0 - p ) );
colorT.normalize();
Share:
10,183
Ricardo Sanchez
Author by

Ricardo Sanchez

Former Adobe Flash & Actionscript Developer

Updated on June 04, 2022

Comments

  • Ricardo Sanchez
    Ricardo Sanchez almost 2 years

    I need to interpolate or change gradually a sequence of colors, so it goes from colorA to colorB to colorC to colorD and them back to colorA, this need to be based on time elapsed in milliseconds, any help will be much appreciated (algorithms, pseudo code will be great).

    Note that I am working with RGB, it could be 0-255 or 0.0-1.0 range.

    This is what I have so far, I need to change the colors on every "timePeriod", them I calculate the percentage of time elapsed and change the colors, the problem with this code is that there is a jump when it goes from A to B to B to C and so on

    int millisNow = ofGetElapsedTimeMillis();
    int millisSinceLastCheck = millisNow - lastTimeCheck;
    if ( millisSinceLastCheck > timePeriod ) {
        lastTimeCheck = millisNow;
        millisSinceLastCheck = 0;
        colorsIndex++;
        if ( colorsIndex == colors.size()-1 ) colorsIndex = 0;
        cout << "color indes: " << colorsIndex << endl;
        cout << "color indes: " << colorsIndex + 1 << endl;
    }
    timeFraction = (float)(millisSinceLastCheck) / (float)(timePeriod);
    float p = timeFraction;
    colorT.r = colors[colorsIndex].r * p + ( colors[colorsIndex+1].r * ( 1.0 - p ) );
    colorT.g = colors[colorsIndex].g * p + ( colors[colorsIndex+1].g * ( 1.0 - p ) );
    colorT.b = colors[colorsIndex].b * p + ( colors[colorsIndex+1].b * ( 1.0 - p ) );
    colorT.normalize();
    

    Thanks in advance

  • Anomie
    Anomie about 13 years
    Bah, you beat me to it. +1, This is the only answer (so far) that actually answers the question rather than the title.
  • Jerry Coffin
    Jerry Coffin about 13 years
    Note, however, that HSL is not really a particularly great choice. HSL and HSB are easy to visualize, and tend to give better results than working directly in RGB -- but that's about it. Lab* doesn't corresponds to color perception perfectly, but it's a lot closer than HSL or HSB.
  • Ricardo Sanchez
    Ricardo Sanchez about 13 years
    Cool very easy solution, the only thing I have to do is to work on the jump form C-D to D-A, but I think I can figure that out myself thanks a lot! I wasn't sure about the title neither :) english is not my 1st language
  • ssube
    ssube about 13 years
    @Jerry: I'm not really familiar with LAB myself (will be checking into it, though), and HSL may not be the best choice, but it is a step up from RGB and isn't too complex. Depends on what's needed here, I suppose.
  • James Kanze
    James Kanze about 13 years
    Could you provide a reference to Lab*? (RGB, literally, is fairly far from human perception. Converting it correctly to HSL could improve things, but the conversion must respect the anomalies of human perception, which isn't often the case.)
  • Jerry Coffin
    Jerry Coffin about 13 years
    yes, it is definitely a step up from working directly with RGB. It's also a rather simpler conversion, so it doesn't cause nearly as much extra computation, which can be important for real-time conversion.
  • Jerry Coffin
    Jerry Coffin about 13 years
    @James Kanze: The Science of Color by Steven Schevell is quite good, though it's about color in general, not specific to Lab*. One that's freely available online is brucelindbloom.com (which also covers the UP color space, which should at least theoretically be an even better choice for this task). I should add that Bruce has a lot that's probably way overboard for the kind of thing we're discussing though (though, being fair, CIELAB may also be...)
  • August
    August over 6 years
    If you want to make the colors lighter, just multiply the whole thing by value to your choosing