Interpolate from one color to another

I suggest you convert RGB to HSV, then adjust its components, then convert back to RGB.

Wikipedia has an article about it, and it's been discussed here before:

HSL to RGB color conversion

Algorithm to convert RGB to HSV and HSV to RGB in range 0-255 for both

Also many frameworks have conversion functions, for example Qt has QColor class.


But the question was about the actual interpolation... here's a trivial interpolation function:

// 0 <= stepNumber <= lastStepNumber
int interpolate(int startValue, int endValue, int stepNumber, int lastStepNumber)
{
    return (endValue - startValue) * stepNumber / lastStepNumber + startValue;
}

So call that for all color components you want to interpolate, in a loop. With RBG interpolation, you need to interpolate every component, in some other color space you may need to interpolate just one.


Convert your RGB colors to HSV then interpolate each component (not only the color, see end of answer), afterwards you can convert back to RGB.

You can do RGB interpolation, but the results are better with HSV, because in this space color is separated from luminance and saturation (Wikipedia article on HSV). HSV interpolation is more "logical" than the RGB one, because with the latter you can get extra colors while interpolating.

Some code for interpolation:

template<typename F>
ColorRGB interpolate(ColorRGB a, ColorRGB b, float t, F interpolator)
{
    // 0.0 <= t <= 1.0
    ColorHSV ca = convertRGB2HSV(a);
    ColorHSV cb = convertRGB2HSV(b);
    ColorHSV final;

    final.h = interpolator(ca.h, cb.h, t);
    final.s = interpolator(ca.s, cb.s, t);
    final.v = interpolator(ca.v, cb.v, t);

    return convertHSV2RGB(final);
}

int linear(int a, int b, float t)
{
    return a * (1 - t) + b * t;
}

// use: result = interpolate(color1,color2,ratio,&linear);

I know this is little bit old, but is worthy if someone is searching for it.

First of all, you can do interpolation in any color space, including RGB, which, in my opinion, is one of the easiest.

Let's assume the variation will be controlled by a fraction value between 0 and 1 (e.g. 0.3), where 0 means full color1 and 1 means full color2.

The theory:

Result = (color2 - color1) * fraction + color1

Applying:

As the RGB has 3 channels (red, green and blue) we have to perform this math for each one of the channels.

Using your example colors:

fraction: 0.3
color1: 151,206,255
color2: 114,127,157

R =  (114-151) * fraction + 151
G =  (127-206) * fraction + 206
B =  (157-255) * fraction + 255

Code example in C/C++:

/**
 * interpolate 2 RGB colors
 * @param color1    integer containing color as 0x00RRGGBB
 * @param color2    integer containing color as 0x00RRGGBB
 * @param fraction  how much interpolation (0..1)
 * - 0: full color 1
 * - 1: full color 2
 * @return the new color after interpolation
 */
int interpolate(int color1, int color2, float fraction)
{
        unsigned char   r1 = (color1 >> 16) & 0xff;
        unsigned char   r2 = (color2 >> 16) & 0xff;
        unsigned char   g1 = (color1 >> 8) & 0xff;
        unsigned char   g2 = (color2 >> 8) & 0xff;
        unsigned char   b1 = color1 & 0xff;
        unsigned char   b2 = color2 & 0xff;

        return (int) ((r2 - r1) * fraction + r1) << 16 |
                (int) ((g2 - g1) * fraction + g1) << 8 |
                (int) ((b2 - b1) * fraction + b1);
}

/* 
 * 0x0097ceff == RGB(151,206,255)
 * 0x00727f9d == RGB(114,127,157)
 */
int new_color = interpolate(0x0097ceff, 0x00727f9d, 0.3f);

The best color space to use for visual effects is HCL. This is a space created specifically to look good while traversing its dimension, where "look good" does not relate to any physical properties of light or ink, like RGB and CMYK respectively.

Using HCL is expensive so the best thing to do is to create a number of intermediary values in this space and then interpolate in RGB which is native. This is what I have done in my animation engine.

Here is a snippet from it, in Swift 4.0

extension UIColor {
    typealias Components = (CGFloat, CGFloat, CGFloat, CGFloat)
    enum Space: String {
        case RGB = "RGB"
        case HSB = "HSB"
        case HCL = "HCL"
    }
    func components(in space: UIColor.Space) -> Components {
        switch space {
        case .RGB: return self.rgba // var defined in HandyUIKit's extension
        case .HSB: return self.hsba // var defined in HandyUIKit's extension
        case .HCL: return self.hlca // var defined in HandyUIKit's extension
        }
    }
    func spectrum(to tcol: UIColor, for space: UIColor.Space) -> [UIColor] {
        var spectrum = [UIColor]()
        spectrum.append(self)
        let fcomps  = self.components(in: space)
        let tcomps  = tcol.components(in: space)
        for i in 0 ... 5 {
            let factor  = CGFloat(i) / 5.0
            let comps   = (1.0 - factor) * fcomps + factor * tcomps
            let color   = UIColor(with: comps, in: space)
            spectrum.append(color)
        }
        spectrum.append(tcol)
        return spectrum
    }
    convenience init(with components: Components, in space: Space) {
        switch space {
        case .RGB: self.init(red: components.0, green: components.1, blue: components.2, alpha: components.3)
        case .HSB: self.init(hue: components.0, saturation: components.1, brightness: components.2, alpha: components.3)
        case .HCL: self.init(hue: components.0, luminance: components.1, chroma: components.2, alpha: components.3)
        }
    }
}
func *(lhs:CGFloat, rhs:UIColor.Components) -> UIColor.Components {
    return (lhs * rhs.0, lhs * rhs.1, lhs * rhs.2, lhs * rhs.3)
}
func +(lhs:UIColor.Components, rhs:UIColor.Components) -> UIColor.Components {
    return (lhs.0 + rhs.0, lhs.1 + rhs.1, lhs.2 + rhs.2, lhs.3 + rhs.3)
}

Both the engine and the example above are using HandyUIKit for the conversions between spaces so please add this project to whatever you are building for the code above to work.

I have written an article about it.