从一种颜色插值到另一种颜色

问题描述 投票:0回答:7

我正在尝试将一种颜色插值到另一种相同颜色的色调。 (例如:天蓝色到深蓝色,然后再返回)。

我偶然发现了一些代码,如果范围是 0-255 或 0-1,就可以使用它们。然而,就我而言,我有 Color1 和 Color2 的 RGB 代码,并且希望发生旋转。

颜色1:151,206,255
颜色2:114,127,157

有什么想法如何解决这个问题吗?

c++ openframeworks
7个回答
36
投票

我知道这有点旧,但如果有人正在寻找它,那么这是值得的。

首先,您可以在任何颜色空间中进行插值,包括 RGB,在我看来,这是最简单的之一。

假设变化由 0 到 1 之间的分数值(例如 0.3)控制,其中 0 表示全色 1,1 表示全色 2。

理论:

Result = (color2 - color1) * fraction + color1

申请:

由于 RGB 有 3 个通道(红、绿、蓝),我们必须对每个通道执行这一数学运算。

使用示例颜色:

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

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);

编辑1。

正如飞羊所指出的,RGB 格式可能会在混合操作期间引入一些不需要的颜色。为了解决这个问题,可以在该过程中添加新的步骤:转换为线性 RGB,执行插值,然后将其转换回来。下面是 C 语言的工作示例,它还生成用于可视化的 PPM 文件。

/* vim: set sw=4 ts=4 nowrap hlsearch: */

#include <stdio.h>
#include <math.h>
#include <inttypes.h>

/**
 * convert a single default RGB (a.k.a. sRGB) channel to linear RGB
 * @param channel    R, G or B channel
 * @return channel in linear RGB
 */
float rgb2linear(uint8_t channel)
{
    float s = channel / 255.0f;

    return s <= 0.04045 ? s / 12.92 : pow((s + 0.055) / 1.055, 2.4);
}

/**
 * convert a linear RGB channel to default RGB channel (a.k.a. sRGB)
 * @param linear    R, G or B channel in linear format
 * @return converted channel to default RGB value
 */
uint8_t linear2rgb(float linear)
{
    float s = linear <= 0.0031308 ? linear * 12.92 : 1.055 * pow(linear, 1.0/2.4) - 0.055;
    return (uint8_t) (s * 255);
}

/**
 * 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)
{
    uint8_t in[3];

    for(int x = 0; x < 3; x++) {
        float c1 = rgb2linear((color1 >> (16 - x * 8)) & 0xff);
        float c2 = rgb2linear((color2 >> (16 - x * 8)) & 0xff);
        in[x] = linear2rgb((c2 - c1) * fraction + c1);
    }
    return in[0] << 16 | in[1] << 8 | in[2];;
}

/**
 * example interpolating sRGB colors
 * first the colors are converted to linear-RGB,
 * interpolated and then converted back to sRGB
 */
int main(int argc, char *argv[])
{
    float factor = 0.3f;
    int c1 = 0x0097ceff; /* RGB(151,206,255) */
    int c2 = 0x00727f9d; /* RGB(114,127,157) */
    int nc = interpolate(c1, c2, factor);
    printf("interpolate 0x%08x to 0x%08x ==> 0x%08x factor: %.2f\n", c1, c2, nc, factor);
    /* create a PPM file to vizualize the interpolation
     * gradients in 100 steps - PPM files may be opened
     * in Gimp or other image editors */
    FILE *f = fopen("interpolate.ppm", "w");
    if(f) {
        /* PPM file header */
        fprintf(f, "P3\n");
        fprintf(f, "707 50\n");
        fprintf(f, "255\n");
        /* iterate over the rows */
        for(int x = 0; x < 50; x++) {
            /* iterate over the steps */
            for(int k = 0; k <= 100; k++) {
                factor = k * 0.01f;
                nc = interpolate(c1, c2, factor);
                /* iterate over the cols */
                for(int y = 0; y < 7; y++) {
                    fprintf(f, "%d %d %d%s", (nc >> 16) & 0xff, (nc >> 8) & 0xff, nc & 0xff, k+1 > 100 && y+1 >= 7 ? "\n" : " ");
                }
            }
        }
        fclose(f);
    }
    return 0;
}

19
投票

我建议你将RGB转换为HSV,然后调整其分量,然后再转换回RGB。

维基百科有一篇关于它的文章,之前已经在这里讨论过:

HSL 到 RGB 颜色转换

将 RGB 转换为 HSV 以及将 HSV 转换为 RGB(范围为 0-255)的算法

很多框架都有转换函数,例如Qt有QColor类


但问题是关于实际的插值......这是一个简单的插值函数:

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

因此,在循环中为您想要插值的所有颜色分量调用它。使用 RBG 插值,您需要对每个分量进行插值,在某些其他颜色空间中,您可能只需要对一个分量进行插值。


13
投票

将 RGB 颜色转换为 HSV,然后插入每个分量(不仅是颜色,请参见答案末尾),之后您可以转换回 RGB。

您可以进行 RGB 插值,但使用 HSV 的结果更好,因为在这个空间中,颜色与亮度和饱和度分开(有关 HSV 的维基百科文章)。 HSV 插值比 RGB 插值更“合乎逻辑”,因为使用后者,您可以在插值时获得额外的颜色。

一些插值代码:

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);

6
投票

用于视觉效果的最佳色彩空间是HCL。这是一个专门为在穿越其维度时看起来不错而创建的空间,其中“看起来不错”与光或墨水的任何物理属性无关,例如分别为 RGB 和 CMYK。

使用 HCL 的成本很高,因此最好的办法是在这个空间中创建许多中间值,然后在本机 RGB 中进行插值。这就是我在我的动画引擎中所做的。

这是 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)
}

引擎和上面的示例都使用 HandyUIKit 进行空间之间的转换,因此请将此项目添加到您正在构建的任何项目中,以使上面的代码正常工作。

我写了一篇关于它的文章


0
投票

我看到您已将此问题标记在“openframeworks”标签下。 所以你需要做的就是使用方法

ofColor::getLerped
ofColor::lerp

getLerped
返回新值,而
lerp
修改颜色。

例如:

ofColor c1(151,206,255);
ofColor c2(114,127,157);


float p = 0.2f;
ofColor c3 = c1.getLerped(c2, p);

c1.lerp(c2, 0.3f);

0
投票

我已将 Synxis 的 C 示例(上文)改编为可执行的 JavaScript 程序。

程序从红色绿色插入颜色黄色。输入和输出在 RGB-space 中,但插值在 HSV-space 中处理。我还添加了 RGB 插值示例。如下所示,如果您在 RGB 空间中插入 redgreen,则会生成 深黄

/** Main */
var red        = { r : 255, g : 0,   b : 0 };
var green      = { r : 0,   g : 255, b : 0 };
var yellow     = interpolateHsv(red, green, 0.5, linear);
var darkYellow = interpolateRgb(red, green, 0.5, linear);

document.body.innerHTML =
  'Yellow: '      + JSON.stringify(yellow,     null, '  ') + '<br />' +
  'Dark Yellow: ' + JSON.stringify(darkYellow, null, '  ');

/**
 * Returns an HSV interpolated value between two rgb values. 
 *
 * @param {Object} rgbA - rgb() tuple
 * @param {Object} rgbB - rgb() tuple
 * @param {Number} threshold - float between [0.0, 1.0]
 * @param {function} interpolatorFn - interpolator function
 * @return {Object} rbg
 */
function interpolateHsv(rgbA, rgbB, threshold, interpolatorFn) {
  var hsvA = rgbToHsv(rgbA);
  var hsvB = rgbToHsv(rgbB);
  threshold = toArray(threshold, 3);
  return hsvToRgb({
    h : interpolatorFn(hsvA.h, hsvB.h, threshold[0]),
    s : interpolatorFn(hsvA.s, hsvB.s, threshold[1]),
    v : interpolatorFn(hsvA.v, hsvB.v, threshold[2])
  });
}

/**
 * Returns an RGB interpolated value between two rgb values. 
 *
 * @param {Object} rgbA - rgb() tuple
 * @param {Object} rgbB - rgb() tuple
 * @param {Number} threshold - float between [0.0, 1.0]
 * @param {function} interpolatorFn - interpolator function
 * @return {Object} rbg
 */
function interpolateRgb(rgbA, rgbB, threshold, interpolatorFn) {        
  threshold = toArray(threshold, 3);
  return {
    r : ~~interpolatorFn(rgbA.r, rgbB.r, threshold[0]),
    g : ~~interpolatorFn(rgbA.g, rgbB.g, threshold[1]),
    b : ~~interpolatorFn(rgbA.b, rgbB.b, threshold[2])
  };
}

/**
 * Returns an interpolated value between two values. 
 *
 * @param {Number} valueA - color channel int value
 * @param {Number} valueB - color channel int value
 * @param {Number} threshold - float between [0.0, 1.0]
 * @param {function} interpolatorFn - interpolator function
 * @return {int}
 */
function linear(valueA, valueB, threshold) {
  return valueA * (1 - threshold) + valueB * threshold;
}

/**
 * Converts an RGB color value to HSV. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and v in the set [0, 1].
 *
 * @param {Object} rgb - Color in rgb mode
 * @return {Object} - Color in hsv mode
 */
function rgbToHsv(rgb) {
  var r = rgb.r / 255,
      g = rgb.g / 255,
      b = rgb.b / 255;
  var max = Math.max(r, g, b), min = Math.min(r, g, b);
  var h, s, v = max;
  var d = max - min;
  s = max === 0 ? 0 : d / max;
  if (max == min) {
    h = 0; // achromatic
  } else {
    switch(max) {
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      case b: h = (r - g) / d + 4; break;
    }
    h /= 6;
  }
  return {
    h : h,
    s : s,
    v : v
  };
}

/**
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param {Object} hsv - Color in hsv mode
 * @return {Object} - Color in rgb mode
 */
function hsvToRgb(hsv){
  var r, g, b, i, f, p, q, t,
      h = hsv.h,
      s = hsv.s,
      v = hsv.v;
  i = Math.floor(h * 6);
  f = h * 6 - i;
  p = v * (1 - s);
  q = v * (1 - f * s);
  t = v * (1 - (1 - f) * s);
  switch(i % 6){
    case 0: r = v, g = t, b = p; break;
    case 1: r = q, g = v, b = p; break;
    case 2: r = p, g = v, b = t; break;
    case 3: r = p, g = q, b = v; break;
    case 4: r = t, g = p, b = v; break;
    case 5: r = v, g = p, b = q; break;
  }
  return {
    r : r * 255,
    g : g * 255,
    b : b * 255
  };
}

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function toArray(arr, size) {
  var isNum = isNumeric(arr);
  arr = !Array.isArray(arr) ? [arr] : arr;
  for (var i = 1; i < size; i++) {
    if (arr.length < size) {
      arr.push(isNum ? arr[0] : 0);
    }
  }
  return arr;
}


0
投票

这是基于 @hyde 答案的 Swift 2 版本:

import UIKit

func interpolate(start start: CGFloat, end: CGFloat, progress: CGFloat) -> CGFloat {
  return (end - start) * progress + start
}


extension UIColor {
  func interpolateTo(color end: UIColor, progress: CGFloat) -> UIColor {
    var r1: CGFloat = 0
    var g1: CGFloat = 0
    var b1: CGFloat = 0
    var a1: CGFloat = 0
    getRed(&r1, green: &g1, blue: &b1, alpha: &a1)

    var r2: CGFloat = 0
    var g2: CGFloat = 0
    var b2: CGFloat = 0
    var a2: CGFloat = 0
    end.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

    return UIColor(
      red: interpolate(start: r1, end: r2, progress: progress),
      green: interpolate(start: g1, end: g2, progress: progress),
      blue: interpolate(start: b1, end: b2, progress: progress),
      alpha: interpolate(start: a1, end: a2, progress: progress)
    )
  }
}

你可以这样使用它:

color1.interpolateTo(color: color2, progress: t)

其中

t
是插值的百分比 (0-1)。

© www.soinside.com 2019 - 2024. All rights reserved.