Изменение оттенка цвета 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, а затем обратно кажется довольно переполненным.
Я мог бы использовать таблицу поиска или предварительно вычислить цвета в моем приложении, но мне действительно интересно, есть ли способ заставить мой код работать. Спасибо!
Ответы
Ответ 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;
}