How to interpolate a color sequence?
Asked Answered
F

6

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

Franconian answered 2/5, 2011 at 17:10 Comment(2)
So you want the colours to rotate in effect?Ratib
Possible duplicate: #1237183Bimolecular
S
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.

Solute answered 2/5, 2011 at 17:25 Comment(3)
Bah, you beat me to it. +1, This is the only answer (so far) that actually answers the question rather than the title.Publish
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 languageFranconian
If you want to make the colors lighter, just multiply the whole thing by value to your choosingNoelnoelani
H
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.

Hemorrhoid answered 2/5, 2011 at 17:22 Comment(3)
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.Whorish
@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.Hemorrhoid
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.Whorish
W
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.

Whorish answered 2/5, 2011 at 17:21 Comment(2)
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.)Sox
@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...)Whorish
C
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();
Comet answered 2/5, 2011 at 17:28 Comment(0)
W
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.

Wightman answered 2/5, 2011 at 17:14 Comment(0)
S
-1

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

Sox answered 2/5, 2011 at 17:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.