3

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

Ricardo Sanchez
  • 4,935
  • 11
  • 56
  • 86

6 Answers6

11

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.

interjay
  • 107,303
  • 21
  • 270
  • 254
  • Bah, you beat me to it. +1, This is the only answer (so far) that actually answers the question rather than the title. – Anomie May 02 '11 at 17:30
  • 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 – Ricardo Sanchez May 02 '11 at 17:35
  • If you want to make the colors lighter, just multiply the whole thing by value to your choosing – August Aug 11 '17 at 11:13
9

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.

ssube
  • 47,010
  • 7
  • 103
  • 140
  • 1
    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. L*a*b* doesn't corresponds to color perception perfectly, but it's a *lot* closer than HSL or HSB. – Jerry Coffin May 02 '11 at 17:31
  • @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. – ssube May 02 '11 at 17:43
  • 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 May 02 '11 at 17:58
1

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Could you provide a reference to L*a*b*? (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.) – James Kanze May 02 '11 at 17:45
  • @James Kanze: *The Science of Color* by Steven Schevell is quite good, though it's about color in general, not specific to L*a*b*. One that's freely available online is http://www.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...) – Jerry Coffin May 02 '11 at 18:22
1

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();
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
0

We're doing this on a project I'm currently working on. We just treat the R, G, B values independently and transition from color1 to color2 based on how many "steps" there are in between. We have discrete values so we have a look-up table approach, but you could do the same thing with floating point and just calculate the RGB values dynamically.

If you still have questions, I could post some Java code.

Jim Clay
  • 963
  • 9
  • 24
-1

Separate the three components (RBG) and interpolate each separately, using the classical interpolation algorithm.

James Kanze
  • 150,581
  • 18
  • 184
  • 329