Подтвердить что ты не робот

Изменение оттенка цвета RGB

Я пытаюсь написать функцию для изменения цвета RGB. В частности, я использую его в приложении iOS, но математика универсальна.

Ниже показан график изменения значений R, G и B относительно оттенка.

Graph of RGB values across hues

Глядя на то, что кажется, что должно быть относительно просто написать функцию для изменения оттенка без каких-либо неприятных преобразований в другой цветовой формат, который приведет к большей ошибке (что может быть проблемой, если продолжить применять небольшие сдвиги к цвет), и я подозреваю, что это будет более дорогостоящим вычислительным.

Вот что я до сих пор делал. Он отлично работает, если вы переходите от чистого желтого или голубого или пурпурного, но в остальном он немного приживается в некоторых местах.

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

Я знаю, что вы можете сделать это с помощью 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);

но я не сумасшедший об этом, поскольку он работает только в iOS 5, а между распределением нескольких цветовых объектов и преобразованием из RGB в HSB, а затем обратно кажется довольно переполненным.

Я мог бы использовать таблицу поиска или предварительно вычислить цвета в моем приложении, но мне действительно интересно, есть ли способ заставить мой код работать. Спасибо!

4b9b3361

Ответ 1

Изменить для каждого измененного комментария "все" в "может быть линейно аппроксимировано".
Изменить 2 добавление смещений.


По существу, требуемые шаги

RBG->HSV->Update hue->RGB

Так как они могут быть аппроксимированы линейными матричными преобразованиями (т.е. они являются ассоциативными), вы можете выполнить его за один шаг без какого-либо неприятного преобразования или потери точности. Вы просто несколько преобразовываете матрицы друг с другом и используете это для преобразования цветов.

Здесь быстро шаг за шагом http://beesbuzz.biz/code/hsv_color_transforms.php

Здесь код С++ (с преобразованием насыщенности и значения):

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

Ответ 2

Цветовое пространство 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)

Вот некоторые из результатов:

Hue rotation example

Вы можете найти другую реализацию той же идеи в http://www.graficaobscura.com/matrix/index.html

Ответ 3

Я был разочарован большинством ответов, которые я нашел здесь, некоторые из них были ошибочными и в основном ошибочными. В итоге я потратил 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;
}

ПРИМЕЧАНИЕ. Матрица вращения зависит только от 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

Вот пример ввода/вывода, который можно было восстановить с помощью обоих кодов:

TransformH(86,52,30,210)
ans =
    36
    43
    88

Таким образом, вход RGB [86,52,30] был преобразован в [36,43,88] с использованием оттенка 210.

Ответ 4

В основном есть два варианта:

  • Преобразование RGB → HSV, изменение оттенка, конвертирование HSV → RGB
  • Изменение оттенка непосредственно с помощью линейного преобразования

Я не уверен, как реализовать 2, но в основном вам придется создать матрицу преобразования и фильтровать изображение через эту матрицу. Однако это изменит цвет изображения вместо изменения только оттенка. Если это подходит для вас, то это может быть вариант, но если не удается избежать конверсии.

Edit

Небольшое исследование показывает это, что подтверждает мои мысли. Подводя итог: следует предпочесть преобразование из RGB в HSV, если требуется точный результат. Изменение исходного изображения RGB с помощью линейного преобразования также приводит к результату, но это скорее искажает изображение. Разница объясняется следующим образом: преобразование из RGB в HSV нелинейно, тогда как преобразование является линейным.

Ответ 5

Сообщение старое, а оригинальный плакат искал код ios - однако, меня отправили сюда через поиск визуального базового кода, поэтому для всех, подобных мне, я преобразовал код Mark в модуль vb.net:

Public Module HueAndTry    
    Public Function ClampIt(ByVal v As Double) As Integer    
        Return CInt(Math.Max(0F, Math.Min(v + 0.5, 255.0F)))    
    End Function    
    Public Function DegreesToRadians(ByVal degrees As Double) As Double    
        Return degrees * Math.PI / 180    
    End Function    
    Public Function RadiansToDegrees(ByVal radians As Double) As Double    
        Return radians * 180 / Math.PI    
    End Function    
    Public Sub HueConvert(ByRef rgb() As Integer, ByVal degrees As Double)
        Dim selfMatrix(,) As Double = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
        Dim cosA As Double = Math.Cos(DegreesToRadians(degrees))
        Dim sinA As Double = Math.Sin(DegreesToRadians(degrees))
        Dim sqrtOneThirdTimesSin As Double = Math.Sqrt(1.0 / 3.0) * sinA
        Dim oneThirdTimesOneSubCos As Double = 1.0 / 3.0 * (1.0 - cosA)
        selfMatrix(0, 0) = cosA + (1.0 - cosA) / 3.0
        selfMatrix(0, 1) = oneThirdTimesOneSubCos - sqrtOneThirdTimesSin
        selfMatrix(0, 2) = oneThirdTimesOneSubCos + sqrtOneThirdTimesSin
        selfMatrix(1, 0) = selfMatrix(0, 2)
        selfMatrix(1, 1) = cosA + oneThirdTimesOneSubCos
        selfMatrix(1, 2) = selfMatrix(0, 1)
        selfMatrix(2, 0) = selfMatrix(0, 1)
        selfMatrix(2, 1) = selfMatrix(0, 2)
        selfMatrix(2, 2) = cosA + oneThirdTimesOneSubCos
        Dim rx As Double = rgb(0) * selfMatrix(0, 0) + rgb(1) * selfMatrix(0, 1) + rgb(2) * selfMatrix(0, 2)
        Dim gx As Double = rgb(0) * selfMatrix(1, 0) + rgb(1) * selfMatrix(1, 1) + rgb(2) * selfMatrix(1, 2)
        Dim bx As Double = rgb(0) * selfMatrix(2, 0) + rgb(1) * selfMatrix(2, 1) + rgb(2) * selfMatrix(2, 2)
        rgb(0) = ClampIt(rx)
        rgb(1) = ClampIt(gx)
        rgb(2) = ClampIt(bx)
    End Sub
End Module

Я поместил общие термины в (длинные) переменные, но в противном случае это простое преобразование - отлично работало для моих нужд.

Кстати, я попытался оставить Mark upvote за его отличный код, но у меня не было достаточного количества голосов, чтобы оно было видно (подсказка, подсказка).

Ответ 6

Реализация Javascript (на основе Владимирского 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));
}

Ответ 7

Кажется, что преобразование в HSV имеет наибольший смысл. Сасс предоставляет несколько удивительных помощников цвета. Это в рубине, но это может быть полезно.

http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html

Ответ 8

Отличный код, но мне интересно, что он может быть быстрее, если вы просто не используете self.matrix [2] [0], self.matrix [2] [1], self.matrix [2] [1 ]

Следовательно, set_hue_rotation можно записать просто как:

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] = self.matrix[0][2] <---Not sure, if this is the right code, but i think you got the idea
    self.matrix[1][1] = self.matrix[0][0]
    self.matrix[1][2] = self.matrix[0][1]

Ответ 9

Скотт.... не совсем. Алго, похоже, работает так же, как и в HSL/HSV, но быстрее. Кроме того, если вы просто умножаете 1-й 3 элемента массива с коэффициентом для серого, вы добавляете/уменьшаете яркость.

Пример... В оттенках серого из Rec709 есть эти значения [GrayRedFactor_Rec709: R $0.212671  GrayGreenFactor_Rec709: R $0.715160  GrayBlueFactor_Rec709: R $0.072169]

Когда вы много self.matrix [x] [x] с корректором GreyFactor вы уменьшаете яркость, не касаясь насыщения Пример:

def set_hue_rotation(self, degrees):
    cosA = cos(radians(degrees))
    sinA = sin(radians(degrees))
    self.matrix[0][0] = (cosA + (1.0 - cosA) / 3.0) * 0.212671
    self.matrix[0][1] = (1./3. * (1.0 - cosA) - sqrt(1./3.) * sinA) * 0.715160
    self.matrix[0][2] = (1./3. * (1.0 - cosA) + sqrt(1./3.) * sinA) * 0.072169
    self.matrix[1][0] = self.matrix[0][2] <---Not sure, if this is the right code, but i think you got the idea
    self.matrix[1][1] = self.matrix[0][0]
    self.matrix[1][2] = self.matrix[0][1]

И наоборот. И наоборот. Если вы разделите вместо этого умножить, яркость резко возрастает.

От того, что я тестирую, эти алгоритмы могут быть замечательной заменой для HSL, так как вам не нужна насыщенность, конечно.

Попробуйте сделать это... поверните оттенок только на 1 градус (просто чтобы заставить алгоритм работать правильно, сохраняя при этом одну и ту же чувствительность восприятия изображения) и умножайте эти факторы.

Ответ 10

Кроме того, Mark's algo дает более точные результаты.

Например, если вы поворачиваете оттенок до 180º с использованием цветового пространства HSV, изображение может привести к красноватому цвету тона.

Но на марке Марке изображение правильно вращается. Тоны скинов, например (Hue = 17, Sat = 170, L = 160 в PSP), правильно поворачиваются до синего, которые имеют оттенок около 144 в PSP, и все остальные цвета изображения правильно вращаются.

Алго имеет смысл, поскольку Хюэ - это не что иное, не что иное, как функция Логарифма арктана красного, зеленого, синего, как определено этой формулой:

Hue = arctan((logR-logG)/(logR-logG+2*LogB))

Ответ 11

Реализация PHP:

class Hue
{
    public function convert(int $r, int $g, int $b, int $hue)
    {
        $cosA = cos($hue * pi() / 180);
        $sinA = sin($hue * pi() / 180);

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

        $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 array_map([$this, 'crop'], $result);
    }

    private function crop(float $value)
    {
        return 0 > $value ? 0 : (255 < $value ? 255 : (int)round($value));
    }
}

Ответ 12

Для тех, кому необходим описанный выше (без гамма-коррекции) сдвиг оттенка в качестве параметризованного пиксельного шейдера HLSL (я прошел через это вместе для приложения WPF и подумал, что могу даже просто поделиться им):

    sampler2D implicitInput : register(s0);
    float factor : register(c0);

    float4 main(float2 uv : TEXCOORD) : COLOR
    {
            float4 color = tex2D(implicitInput, uv);

            float h = 360 * factor;          //Hue
            float s = 1;                     //Saturation
            float v = 1;                     //Value
            float M_PI = 3.14159265359;

            float vsu = v * s*cos(h*M_PI / 180);
            float vsw = v * s*sin(h*M_PI / 180);

            float4 result;
            result.r = (.299*v + .701*vsu + .168*vsw)*color.r
                            + (.587*v - .587*vsu + .330*vsw)*color.g
                            + (.114*v - .114*vsu - .497*vsw)*color.b;
            result.g = (.299*v - .299*vsu - .328*vsw)*color.r
                            + (.587*v + .413*vsu + .035*vsw)*color.g
                            + (.114*v - .114*vsu + .292*vsw)*color.b;
            result.b = (.299*v - .300*vsu + 1.25*vsw)*color.r
                            + (.587*v - .588*vsu - 1.05*vsw)*color.g
                            + (.114*v + .886*vsu - .203*vsw)*color.b;;
            result.a = color.a;

            return result;
    }

Ответ 13

Версия WebGL:

vec3 hueShift(vec3 col, float shift){
    vec3 m = vec3(cos(shift), -sin(shift) * .57735, 0);
    m = vec3(m.xy, -m.y) + (1. - m.x) * .33333;
    return mat3(m, m.zxy, m.yzx) * col;
}