转换RGB颜色的色调

Ant*_*tox 36 c rgb colors objective-c

我正在尝试编写一个函数来移动RGB颜色的色调.具体来说,我在iOS应用程序中使用它,但数学是通用的.

下图显示了R,G和B值如何相对于色调变化.

色调RGB值的图表

看一下这似乎是一个相对简单的编写一个函数来转移色调而不做任何讨厌的转换到不同的颜色格式会引入更多的错误(如果继续对颜色进行小的移位可能会出现问题) ,我怀疑计算成本会更高.

这是我到目前为止所做的工作.如果你从纯黄色或青色或洋红色转移,它会完美地工作,但在某些地方会变得有点眩晕.

Color4f ShiftHue(Color4f c, float d) {
    if (d==0) {
        return c;
    }
    while (d<0) {
        d+=1;
    }

    d *= 3;

    float original[] = {c.red, c.green, c.blue};
    float returned[] = {c.red, c.green, c.blue};

    // big shifts
    for (int i=0; i<3; i++) {
        returned[i] = original[(i+((int) d))%3];
    }
    d -= (float) ((int) d);
    original[0] = returned[0];
    original[1] = returned[1];
    original[2] = returned[2];

    float lower = MIN(MIN(c.red, c.green), c.blue);
    float upper = MAX(MAX(c.red, c.green), c.blue);

    float spread = upper - lower;
    float shift  = spread * d * 2;

    // little shift
    for (int i = 0; i < 3; ++i) {
        // if middle value
        if (original[(i+2)%3]==upper && original[(i+1)%3]==lower) {
            returned[i] -= shift;
            if (returned[i]<lower) {
                returned[(i+1)%3] += lower - returned[i];
                returned[i]=lower;
            } else
                if (returned[i]>upper) {
                    returned[(i+2)%3] -= returned[i] - upper;
                    returned[i]=upper;
                }
            break;
        }
    }

    return Color4fMake(returned[0], returned[1], returned[2], c.alpha);
}
Run Code Online (Sandbox Code Playgroud)

我知道你可以用UIColors做到这一点并用这样的东西改变色调:

CGFloat hue;
CGFloat sat;
CGFloat bri;
[[UIColor colorWithRed:parent.color.red green:parent.color.green blue:parent.color.blue alpha:1] getHue:&hue saturation:&sat brightness:&bri alpha:nil];
hue -= .03;
if (hue<0) {
    hue+=1;
}
UIColor *tempColor = [UIColor colorWithHue:hue saturation:sat brightness:bri alpha:1];
const float* components= CGColorGetComponents(tempColor.CGColor);
color = Color4fMake(components[0], components[1], components[2], 1);
Run Code Online (Sandbox Code Playgroud)

但我并不为此疯狂,因为它只能在iOS 5中运行,在分配大量颜色对象和从RGB转换为HSB然后再回来之间似乎相当过分.

我可能最终使用查找表或预先计算我的应用程序中的颜色,但我真的好奇是否有办法使我的代码工作.谢谢!

Mar*_*som 42

RGB颜色空间描述了一个立方体.可以围绕对角轴旋转此立方体(0,0,0)到(255,255,255)以实现色调的变化.请注意,某些结果将位于0到255范围之外,需要进行裁剪.

我终于有机会编写了这个算法.它在Python中,但应该很容易翻译成您选择的语言.3D旋转公式来自http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

编辑:如果您看到我之前发布的代码,请忽略它.我非常渴望找到一个旋转公式,我将基于矩阵的解决方案转换为公式,没有意识到矩阵一直是最好的形式.我仍然使用常量sqrt(1/3)对轴单位矢量值简化了矩阵的计算,但这在精神上与参考更接近,并且在每像素计算apply中也更简单.

from math import sqrt,cos,sin,radians

def clamp(v):
    if v < 0:
        return 0
    if v > 255:
        return 255
    return int(v + 0.5)

class RGBRotate(object):
    def __init__(self):
        self.matrix = [[1,0,0],[0,1,0],[0,0,1]]

    def set_hue_rotation(self, degrees):
        cosA = cos(radians(degrees))
        sinA = sin(radians(degrees))
        self.matrix[0][0] = cosA + (1.0 - cosA) / 3.0
        self.matrix[0][1] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
        self.matrix[0][2] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
        self.matrix[1][0] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
        self.matrix[1][1] = cosA + 1./3.*(1.0 - cosA)
        self.matrix[1][2] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
        self.matrix[2][0] = 1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA
        self.matrix[2][1] = 1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA
        self.matrix[2][2] = cosA + 1./3. * (1.0 - cosA)

    def apply(self, r, g, b):
        rx = r * self.matrix[0][0] + g * self.matrix[0][1] + b * self.matrix[0][2]
        gx = r * self.matrix[1][0] + g * self.matrix[1][1] + b * self.matrix[1][2]
        bx = r * self.matrix[2][0] + g * self.matrix[2][1] + b * self.matrix[2][2]
        return clamp(rx), clamp(gx), clamp(bx)
Run Code Online (Sandbox Code Playgroud)

以下是上述结果:

Hue旋转示例

您可以在http://www.graficaobscura.com/matrix/index.html找到相同构思的不同实现

  • 刚刚救了我几个小时。怎么没有更多的赞誉? (2认同)
  • @Attila 256x256x256 立方体的对角线比 256 长,所以当你旋转它时,这些角就会伸出。这是因为 RGB 描述的是立方体而不是球体。 (2认同)

Jac*_*ers 16

每个评论的编辑更改为"全部"到"可以线性近似".
编辑2添加偏移量.


基本上,你想要的步骤是

RBG->HSV->Update hue->RGB
Run Code Online (Sandbox Code Playgroud)

由于这些可以通过线性矩阵变换近似(即它们是关联的),因此您可以在一个步骤中执行它,而不会产生任何令人讨厌的转换或精度损失.您只需将变换矩阵相互叠加,然后使用它来转换颜色.

这里有一个快速的步骤http://beesbuzz.biz/code/hsv_color_transforms.php

这是C++代码(删除了饱和度和值转换):

Color TransformH(
    const Color &in,  // color to transform
    float H
)
{
  float U = cos(H*M_PI/180);
  float W = sin(H*M_PI/180);

  Color ret;
  ret.r = (.299+.701*U+.168*W)*in.r
    + (.587-.587*U+.330*W)*in.g
    + (.114-.114*U-.497*W)*in.b;
  ret.g = (.299-.299*U-.328*W)*in.r
    + (.587+.413*U+.035*W)*in.g
    + (.114-.114*U+.292*W)*in.b;
  ret.b = (.299-.3*U+1.25*W)*in.r
    + (.587-.588*U-1.05*W)*in.g
    + (.114+.886*U-.203*W)*in.b;
  return ret;
}
Run Code Online (Sandbox Code Playgroud)

  • 作为您链接的页面的原作者,我想指出RGB-> HSV和HSV-> RGB不是线性矩阵变换.该代码实际上正在做的是转换RGB-> YIQ(线性等效于HSV)并在IQ平面上旋转.它也不会产生人们期望的结果.然而,试图解释这个以及为什么HSV开始时是一种荒谬的颜色概念将不适合这个评论框.:) (14认同)

Mas*_*rHD 8

我对这里找到的大多数答案感到失望,有些是有缺陷的,基本上是错误的.我最终花了3个多小时试图解决这个问题.Mark Ransom的答案是正确的,但我想提供一个完整的C解决方案,该解决方案也通过MATLAB验证.我已经彻底测试了这个,这里是C代码:

#include <math.h>
typedef unsigned char BYTE; //define an "integer" that only stores 0-255 value

typedef struct _CRGB //Define a struct to store the 3 color values
{
    BYTE r;
    BYTE g;
    BYTE b;
}CRGB;

BYTE clamp(float v) //define a function to bound and round the input float value to 0-255
{
    if (v < 0)
        return 0;
    if (v > 255)
        return 255;
    return (BYTE)v;
}

CRGB TransformH(const CRGB &in, const float fHue)
{
    CRGB out;
    const float cosA = cos(fHue*3.14159265f/180); //convert degrees to radians
    const float sinA = sin(fHue*3.14159265f/180); //convert degrees to radians
    //calculate the rotation matrix, only depends on Hue
    float matrix[3][3] = {{cosA + (1.0f - cosA) / 3.0f, 1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA, 1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA},
        {1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA, cosA + 1.0f/3.0f*(1.0f - cosA), 1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA},
        {1.0f/3.0f * (1.0f - cosA) - sqrtf(1.0f/3.0f) * sinA, 1.0f/3.0f * (1.0f - cosA) + sqrtf(1.0f/3.0f) * sinA, cosA + 1.0f/3.0f * (1.0f - cosA)}};
    //Use the rotation matrix to convert the RGB directly
    out.r = clamp(in.r*matrix[0][0] + in.g*matrix[0][1] + in.b*matrix[0][2]);
    out.g = clamp(in.r*matrix[1][0] + in.g*matrix[1][1] + in.b*matrix[1][2]);
    out.b = clamp(in.r*matrix[2][0] + in.g*matrix[2][1] + in.b*matrix[2][2]);
    return out;
}
Run Code Online (Sandbox Code Playgroud)

注意:旋转矩阵仅取决于Hue(fHue),因此一旦计算完毕matrix[3][3],就可以将其重复用于正在进行相同色调变换的图像中的每个像素!这将大大提高效率.这是一个验证结果的MATLAB代码:

function out = TransformH(r,g,b,H)
    cosA = cos(H * pi/180);
    sinA = sin(H * pi/180);

    matrix = [cosA + (1-cosA)/3, 1/3 * (1 - cosA) - sqrt(1/3) * sinA, 1/3 * (1 - cosA) + sqrt(1/3) * sinA;
          1/3 * (1 - cosA) + sqrt(1/3) * sinA, cosA + 1/3*(1 - cosA), 1/3 * (1 - cosA) - sqrt(1/3) * sinA;
          1/3 * (1 - cosA) - sqrt(1/3) * sinA, 1/3 * (1 - cosA) + sqrt(1/3) * sinA, cosA + 1/3 * (1 - cosA)];

    in = [r, g, b]';
    out = round(matrix*in);
end
Run Code Online (Sandbox Code Playgroud)

以下是两个代码都可以重现的输入/输出示例:

TransformH(86,52,30,210)
ans =
    36
    43
    88
Run Code Online (Sandbox Code Playgroud)

所以输入的RGB [86,52,30]转换为[36,43,88]使用的色调210.


小智 5

Javascript 实现(基于上面 Vladimir 的 PHP)

const deg = Math.PI / 180;

function rotateRGBHue(r, g, b, hue) {
  const cosA = Math.cos(hue * deg);
  const sinA = Math.sin(hue * deg);
  const neo = [
    cosA + (1 - cosA) / 3,
    (1 - cosA) / 3 - Math.sqrt(1 / 3) * sinA,
    (1 - cosA) / 3 + Math.sqrt(1 / 3) * sinA,
  ];
  const result = [
    r * neo[0] + g * neo[1] + b * neo[2],
    r * neo[2] + g * neo[0] + b * neo[1],
    r * neo[1] + g * neo[2] + b * neo[0],
  ];
  return result.map(x => uint8(x));
}

function uint8(value) {
  return 0 > value ? 0 : (255 < value ? 255 : Math.round(value));
}
Run Code Online (Sandbox Code Playgroud)