Изменение оттенка цвета 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]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, а затем назад, кажется, довольно перебор.

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

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


По сути, шаги, которые вы хотите,

 RBG->HSV->Update hue->RGB 

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

Здесь есть шаг за шагом: 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; } 

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

У меня наконец появился шанс закодировать этот алгоритм. Это в Python, но это должно быть легко перевести на язык по вашему выбору. Формула для 3D-вращения приведена в http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

Изменить: если вы видели код, который я опубликовал ранее, пожалуйста, проигнорируйте его. Я очень хотел найти формулу для вращения, в которой я преобразовал матричное решение в формулу, не понимая, что matrix является наилучшей формой. Я все же упростил вычисление матрицы, используя константу sqrt (1/3) для значений вектора оси, но это гораздо ближе по духу к эталонному и более простому в расчете на пиксель.

 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

Я был разочарован большинством ответов, которые я нашел здесь, некоторые из них были ошибочными и в основном ошибочными. В итоге я потратил 3 часа, чтобы понять это. Ответ Mark Ransom правильный, но я хочу предложить полное решение C, которое также проверяется с помощью MATLAB. Я проверил это полностью, и вот код C:

 #include  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 .

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

  1. Конвертировать RGB -> HSV, изменять оттенок, конвертировать HSV -> RGB
  2. Измените оттенок непосредственно линейным преобразованием

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

редактировать

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

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

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

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

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

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

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

Скотт …. не совсем. Алго, похоже, работает так же, как и в 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, вы уменьшаете яркость, не касаясь насыщения. Ex:

 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 gradleус (просто чтобы заставить алгоритм работать должным образом, сохраняя при этом одну и ту же чувствительность восприятия изображения) и умножьте эти факторы.

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

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

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

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

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

Реализация 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)); } } 

Для тех, кто нуждается в описанном выше (гамма-исправленном) изменении оттенка в качестве параметризованного шейдера HLSL Pixel (я через него вместе для приложения 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; } 
Interesting Posts
Давайте будем гением компьютера.