Сдвиг оттенка цвета RGB

Я пытаюсь написать функцию для изменения оттенка цвета 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);
}

Я знаю, что вы можете сделать это с помощью 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, а затем обратно кажется довольно излишним.

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


person Anthony Mattox    schedule 14.12.2011    source источник
comment
Я не читал ваш код, но, основываясь на этом графике, вам не нужно будет преобразовывать свой цвет RGB в HSV, чтобы понять, где вы находитесь на этом графике, чтобы вы могли понять, как двигаться?   -  person Oliver Charlesworth    schedule 14.12.2011


Ответы (15)


Правка для каждого комментария заменена на «все» на «может быть линейно аппроксимирована».
Правка 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;
}
person Jacob Eggers    schedule 14.12.2011
comment
Как первоначальный автор страницы, на которую вы ссылаетесь, я хотел бы указать, что RGB->HSV и HSV->RGB не являются линейными матричными преобразованиями. На самом деле этот код выполняет преобразование RGB->YIQ (что является линейным эквивалентом HSV) и вращение в плоскости IQ. Это также не дает результатов, которых люди иногда ожидают. Тем не менее, попытка объяснить это, а также то, почему HSV является своего рода нелепой цветовой концепцией, не уместится в этом поле для комментариев. :) - person fluffy; 09.02.2012
comment
Я не могу воспроизвести правильные результаты с помощью вашего метода, однако ответ Марка Рэнсома сработал отлично. Вот пример: ввод ([R,G,B],H) = ([86,52,30], 210) и вывод для вашего метода [-31,15,2] и [36,43 ,88] с Марком. Я не думаю, что ошибка округления может объяснить эту резкую разницу, что-то не так. - person MasterHD; 27.05.2015
comment
@MasterHD Похоже, я забыл смещения, но я все еще получаю разницу [28,75,62], поэтому я не уверен, что сейчас не так. - person Jacob Eggers; 28.05.2015
comment
@mcd написал: в последней строке вычислений поменялся знак. + (.114-.886*U-.203*W)*in.b; должно быть + (.114+.886*U-.203*W)*in.b;. (У ОП недостаточно репутации, чтобы комментировать.) - person Nisse Engström; 02.06.2015
comment
@fluffy Я знаю, что это было много лет назад, но возможно ли, что вы можете обновить свой код, добавив более значащие цифры? Я запускаю ваш код на Haxe (не на C++) и получаю странные результаты. Я точно знаю, что теряю некоторую точность, и мне интересно, помогут ли более значащие цифры. - person ashes999; 21.02.2016
comment
@ ashes999 это почти наверняка не проблема значащих цифр, а скорее неуместных ожиданий. Гамма цветового пространства YIQ не является кругом, поэтому ее вращение может привести к тому, что точки выйдут за пределы исходного прямоугольника. Кроме того, вы никогда не должны накапливать несколько вращений в точке независимо от того, что представляет собой точка (цвет, положение и т. д.). - person fluffy; 22.02.2016
comment
Я хотел бы отметить, что коэффициенты неверны, если вы выполняете обычную работу на обычном дисплее. Это означает, что веса должны быть основными цветами BT.709, которые используются в sRGB. Указанные значения относятся к BT. 601, которые получены из очень загадочного набора контекстов, не существующих в современном мире. - person troy_s; 02.10.2016

Цветовое пространство RGB описывает куб. Этот куб можно вращать вокруг диагональной оси от (0,0,0) до (255,255,255), чтобы изменить оттенок. Обратите внимание, что некоторые результаты будут лежать за пределами диапазона от 0 до 255 и должны быть обрезаны.

Наконец-то у меня появилась возможность закодировать этот алгоритм. Он написан на Python, но его легко перевести на любой язык по вашему выбору. Формула трехмерного вращения взята из 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)

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

Пример поворота оттенка

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

person Mark Ransom    schedule 14.12.2011
comment
Просто сэкономил мне несколько часов. Как это не имеет больше голосов?! - person Escher; 10.12.2015
comment
@Escher много причин - 1. Мне потребовались дни, чтобы полностью конкретизировать ответ. 2. Это не то, что нужно делать многим людям. 3. Очевидное решение преобразования в цветовое пространство с компонентом оттенка простое и работает достаточно хорошо для многих людей. - person Mark Ransom; 10.12.2015
comment
Я в прямом эфире фильтровал видео 320x180 (что означает 57600 пикселей на кадр), а метод RGB->HSV->RGB давал 7 кадров в секунду. Когда я начал использовать этот метод, fps фактически упал до 5. Дело в том, что, в отличие от метода RGB->HSV->RGB, вам не нужно просчитывать все для каждого пикселя. Когда я сделал расчет матрицы по кадрам, а не по пикселям, я получил 9 кадров в секунду, что сделало видео действительно смотрибельным. Я могу дополнительно улучшить свой код, чтобы вычислять матрицу только один раз вместо каждого кадра, чтобы повысить производительность. Вот что делает этот метод намного лучше, чем RGB->HSV->RGB. - person AlicanC; 20.01.2016
comment
@AlicanC Когда я писал код, я думал, что будет очевидно, что apply нужно будет вызывать для каждого пикселя, а set_hue_rotation будет использоваться только для настройки. Думаю, я был неправ. - person Mark Ransom; 20.01.2016
comment
@MarkRansom это очевидно. Просто было быстрее реализовать это на пиксель в коде, который у меня был, поэтому я сделал это первым. - person AlicanC; 21.01.2016
comment
я считаю, что это решение работает лучше всего, что я нашел на SO. очень хорошая ротация оттенков - person AndreaBogazzi; 06.08.2017
comment
Хороший. Обратите внимание, что ваше соглашение использует векторы-столбцы для цветов, тогда как ссылка для преобразования цветов использует векторы-строки (т. е. матрицы транспонируются). - person wcochran; 07.11.2017
comment
Это очень эффективное и простое в реализации решение! Я переписал его для Java, и он отлично работает. Спасибо! - person JFreeman; 19.04.2019
comment
Отличное решение, спасибо! Почему именно некоторые результаты оказываются за пределами диапазона [0,255]? Это из-за числовых ошибок? - person Attila; 24.04.2020
comment
@ Аттила, диагональ куба 256x256x256 длиннее 256, поэтому, когда вы его поворачиваете, эти углы просто выступают. Это потому, что RGB описывает куб, а не сферу. - person Mark Ransom; 24.04.2020

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

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

person MasterHD    schedule 27.05.2015
comment
Эй, спасибо, я обнаружил, что если вы делаете это в цикле, цвета в конечном итоге темнеют, поэтому вам нужно добавить немного яркости. Возможны некоторые потери при округлении. например, я сделал: плавать ярко = 1,01; int r = (float)fg.r * яркая; если (r > 255) { r = 255; } фг.г = г; int g = (float)fg.g * яркий; если (g › 255) { g = 255; } фг.г = г; int b = (float)fg.b * яркий; если (b › 255) { b = 255; } фг.б = б; - person Neil McGill; 15.11.2020

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

  1. Преобразование RGB -> HSV, изменение оттенка, преобразование HSV -> RGB
  2. Измените оттенок напрямую с помощью линейного преобразования

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

Изменить

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

person Sebastian Dressler    schedule 14.12.2011
comment
Сделал правку. Я сделаю это прямо в следующий раз, без напоминаний ;) - person Sebastian Dressler; 14.12.2011

Пост старый, а исходный постер искал код ios — однако меня отправили сюда через поиск кода Visual Basic, поэтому для всех таких, как я, я преобразовал код Марка в модуль 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

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

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

person Dave P.    schedule 08.02.2016
comment
Если я хорошо помню, вам нужно 25 очков репутации, чтобы проголосовать (не так уж много). Удачи. - person lrnzcig; 08.02.2016

Реализация 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));
}
person Matt Blackstone    schedule 13.02.2019

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;
}
person Artur Ilkaev    schedule 07.06.2019

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

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

person Scott Messinger    schedule 14.12.2011

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

Пример... Оттенки серого из Rec709 имеют следующие значения [GrayRedFactor_Rec709: 0,212671 реалов GrayGreenFactor_Rec709: 0,715160 реалов GrayBlueFactor_Rec709: 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 градус (просто чтобы заставить алгоритм работать правильно, сохраняя ту же чувствительность восприятия изображения) и умножьте на эти факторы.

person Gustavo Trigueiros    schedule 31.05.2014

Отличный код, но мне интересно, может ли он быть быстрее, если вы просто не используете 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]
person Gustavo Trigueiros    schedule 31.05.2014

Кроме того, алгоритм Марка дает более точные результаты.

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

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

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

Hue = arctan((logR-logG)/(logR-logG+2*LogB))
person Gustavo Trigueiros    schedule 31.05.2014

Реализация 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));
    }
}
person Vladimir Klubov    schedule 06.03.2018
comment
Хотя ваш фрагмент кода может решить проблему, вы должны описать, какова цель вашего кода (как он решает проблему). Кроме того, вы можете проверить stackoverflow.com/help/how-to-answer. - person Ahmad F; 06.03.2018

Для всех, кому нужно описанное выше (без гамма-коррекции) смещение оттенка в качестве параметризованного пиксельного шейдера 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;
    }
person Robin B    schedule 23.07.2018

Самая компактная версия на gsll, которую мне удалось сделать:

vec3 hs(vec3 c, float s){
    vec3 m=vec3(cos(s),s=sin(s)*.5774,-s);
    return c*mat3(m+=(1.-m.x)/3.,m.zxy,m.yzx);
}
person Artur Ilkaev    schedule 06.08.2019

немного изменив ответ MasterHD, чтобы снова добавить Value и Saturation, мы получим следующий код C/C++:

#include <math.h>
typedef unsigned char uint8_t; //if no posix defs, remove if not needed

//if you use C not C++ this needs to be typedef ..
struct Color{
    uint8_t r;
    uint8_t g;
    uint8_t b;
};


uint8_t 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 (uint8_t)v;
}

//compare http://beesbuzz.biz/code/16-hsv-color-transforms
Color change_hsv_c(
    const Color &in, 
    const float fHue,
    const float fSat,
    const float fVal
)
{
    Color out;
    const float cosA = fSat*cos(fHue*3.14159265f/180); //convert degrees to radians
    const float sinA = fSat*sin(fHue*3.14159265f/180); //convert degrees to radians

    //helpers for faster calc //first 2 could actually be precomputed
    const float aThird = 1.0f/3.0f;
    const float rootThird = sqrtf(aThird);
    const float oneMinusCosA = (1.0f - cosA);
    const float aThirdOfOneMinusCosA = aThird * oneMinusCosA;
    const float rootThirdTimesSinA =  rootThird * sinA;
    const float plus = aThirdOfOneMinusCosA +rootThirdTimesSinA;
    const float minus = aThirdOfOneMinusCosA -rootThirdTimesSinA;

    //calculate the rotation matrix
    float matrix[3][3] = {
        {   cosA + oneMinusCosA / 3.0f  , minus                         , plus                          },
        {   plus                        , cosA + aThirdOfOneMinusCosA   , minus                         },
        {   minus                       , plus                          , cosA + aThirdOfOneMinusCosA   }
    };
    //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])*fVal);
    out.g = clamp((in.r*matrix[1][0] + in.g*matrix[1][1] + in.b*matrix[1][2])*fVal);
    out.b = clamp((in.r*matrix[2][0] + in.g*matrix[2][1] + in.b*matrix[2][2])*fVal);
    return out;
}
person HannesH    schedule 01.07.2021