Przesunięcie odcienia koloru RGB
Próbuję napisać funkcję zmieniającą odcień koloru RGB. W szczególności używam go w aplikacji na iOS, ale matematyka jest uniwersalna.
Poniższy wykres pokazuje, jak zmieniają się wartości R, G i B w odniesieniu do odcienia.
Patrząc na to, wydaje się, że stosunkowo proste powinno być napisanie funkcji do zmiany odcienia bez dokonywania nieprzyjemnych konwersji do innego formatu kolorów, co wprowadzi więcej błędów (co może być problemem, jeśli continue nakładanie małych zmian na kolor), a podejrzewam, że byłoby bardziej kosztowne obliczeniowo.
Oto, co mam do tej pory, który rodzaj działa. Działa idealnie, jeśli zmieniasz się z czystej żółci lub cyjan lub magenty, ale poza tym w niektórych miejscach robi się trochę squiffy.
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);
}
Wiem, że możesz to zrobić za pomocą UIColors I zmienić odcień za pomocą czegoś takiego:
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);
Ale nie szaleję za tym, ponieważ działa tylko w iOS 5, i między przydzielaniem liczby kolorów obiekty i konwersja z RGB do HSB, a następnie z powrotem wydaje się dość przesada.
Mogę skończyć używając tabeli wyszukiwania lub wstępnie obliczyć kolory w mojej aplikacji, ale jestem naprawdę ciekaw, czy istnieje sposób, aby mój kod działał. Dzięki!
11 answers
Edit per comment changed "are all" to "can be linearly approximated by".
Edycja 2 Dodawanie offsetów.
Zasadniczo, kroki, które chcesz są
RBG->HSV->Update hue->RGB
Ponieważ te mogą być przybliżone przez liniowe transformaty macierzy (tzn. są asocjacyjne), można je wykonać w jednym kroku bez nieprzyjemnej konwersji lub utraty precyzji. Po prostu wiele macierzy przekształć ze sobą i użyj tego, aby przekształcić swoje colors=
Jest tu szybki krok po kroku http://beesbuzz.biz/code/hsv_color_transforms.php
Oto kod C++ (z usuniętymi transformacjami nasycenia i wartości):
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-03 00:38:06
Przestrzeń kolorów RGB opisuje sześcian. Możliwe jest obrócenie sześcianu wokół osi przekątnej od (0,0,0) do (255,255,255)w celu uzyskania zmiany barwy. Zauważ, że niektóre wyniki będą leżeć poza zakresem od 0 do 255 i będą musiały zostać przycięte.
W końcu udało mi się zakodować ten algorytm. Jest w Pythonie, ale powinno być łatwe do przetłumaczenia na wybrany język. Wzór na rotację 3D pochodzi z http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angleEdit: jeśli widziałeś kod, który wcześniej opublikowałem, zignoruj go. Tak bardzo pragnąłem znaleźć wzór na obrót, że przekształciłem rozwiązanie oparte na macierzy w wzór, nie zdając sobie sprawy, że macierz była najlepszą formą przez cały czas. Nadal uprościłem obliczanie macierzy za pomocą stałej sqrt (1/3) dla wartości wektorów jednostkowych osi, ale jest to znacznie bliższe w duchu odniesienia i prostsze w obliczeniach per-piksel apply
, jak również.
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)
Oto kilka wyników z powyższego:
Możesz znaleźć inną realizację tego samego pomysłu na http://www.graficaobscura.com/matrix/index.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-19 04:49:57
Byłem rozczarowany większością odpowiedzi, które tu znalazłem, niektóre były wadliwe i zasadniczo całkowicie błędne. Spędziłem ponad 3 godziny próbując to rozgryźć. Odpowiedź Marka Ransoma jest poprawna, ale chcę zaoferować kompletne rozwiązanie C, które jest również zweryfikowane z MATLABEM. Przetestowałem to dokładnie, a oto kod 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;
}
Uwaga: matryca rotacji zależy tylko od odcienia (fHue
), więc po obliczeniu matrix[3][3]
, możesz ponownie użyć dla każdego piksela w obraz, który przechodzi tę samą przemianę barwy! To drastycznie poprawi wydajność.
Oto kod MATLAB, który weryfikuje wyniki:
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
Oto przykładowe wejście/wyjście, które można było odtworzyć za pomocą obu kodów:
TransformH(86,52,30,210)
ans =
36
43
88
Tak więc wejściowe RGB [86,52,30]
zostało przekonwertowane na [36,43,88]
używając odcienia 210
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-28 03:19:14
Zasadniczo są dwie opcje:
- Konwertuj RGB -> HSV, Zmień odcień, Konwertuj HSV - > RGB
- Zmień odcień bezpośrednio przekształceniem liniowym
Nie jestem pewien, jak zaimplementować 2, ale zasadniczo będziesz musiał stworzyć macierz transformacji i filtrować obraz przez tę macierz. Spowoduje to jednak ponowne zabarwienie obrazu zamiast zmieniać tylko odcień. Jeśli jest to dla Ciebie w porządku, to może to być opcja, ale jeśli nie konwersja nie może być / align = "left" /
Edit
Małe badania pokazują to , co potwierdza moje myśli. Podsumowując: preferowana jest konwersja z RGB na HSV, jeśli pożądany jest dokładny wynik. Modyfikacja oryginalnego obrazu RGB za pomocą przekształcenia liniowego również prowadzi do wyniku, ale to raczej zabarwia obraz. Różnica jest wyjaśniona w następujący sposób: konwersja z RGB do HSV jest nieliniowa, podczas gdy transformacja jest liniowa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-14 19:22:57
Post jest stary, a oryginalny plakat szukał kodu ios-jednak wysłano mnie tutaj poprzez wyszukiwanie kodu visual basic, więc dla wszystkich takich jak ja, przekonwertowałem kod Marka na moduł 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
Umieściłem wspólne terminy w (długich) zmiennych, ale poza tym jest to prosta konwersja-działa dobrze dla moich potrzeb.
Przy okazji, starałem się zostawić Markowi głos za jego doskonałym kodem, ale sam nie miałem wystarczającej ilości głosów, aby mógł być widoczny (podpowiedź, Podpowiedź).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 10:48:35
Wydaje się, że konwersja na HSV ma największy sens. Sass zapewnia niesamowitych pomocników kolorów. Jest w ruby, ale może się przydać.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-14 18:04:20
Doskonały kod, ale zastanawiam się, że może być szybszy, jeśli po prostu nie używasz self.matryca [2] [0], self.matrix [2] [1], self.macierz [2] [1]
Dlatego set_hue_rotation można zapisać po prostu jako:
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]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-31 11:08:19
Scott....niezupełnie. Wydaje się, że algo działa tak samo jak w HSL/HSV, ale szybciej. Ponadto, jeśli po prostu pomnożysz pierwsze 3 elementy tablicy przez współczynnik szarości, dodasz / zmniejszysz luma.
Przykład...Greyscale z Rec709 mają te wartości [GrayRedFactor_Rec709: R$ 0.212671 GrayGreenFactor_Rec709: R$ 0.715160 GrayBlueFactor_Rec709: R$ 0.072169]
When you multply siebie.matryca [x] [x] dzięki GreyFactor zmniejszasz lumę bez dotykania nasycenie 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]
I jest też odwrotnie.Jeśli dzielisz zamiast mnożyć, jasność wzrasta dramatycznie.
Z tego, co im testowanie tych algorytmów może być cudownym zamiennikiem HSL, tak długo, jak nie trzeba nasycenia, oczywiście.
Spróbuj to zrobić...obróć odcień tylko o 1 stopień (aby zmusić algo do poprawnego działania przy zachowaniu tej samej czułości percepcji obrazu) i pomnóż przez te czynniki.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-31 13:24:58
Również Marks algo daje bardziej dokładne wyniki.
Na przykład, jeśli obrócisz odcień do 180º za pomocą przestrzeni kolorów HSV, obraz może spowodować powstanie czerwonawego koloru.
Ale na Marks algo obraz jest prawidłowo obrócony. Na przykład odcienie skórek (Hue = 17, Sat = 170, L = 160 W PSP) zmieniają się prawidłowo na niebieskie, które mają odcień około 144 W PSP, a wszystkie inne kolory obrazu są prawidłowo obrócone.
Algo ma sens, ponieważ odcień jest niczym więcej, niczym innym niż Funkcja logarytmu arktanu czerwonego, zielonego, niebieskiego zdefiniowanego tym wzorem:
Hue = arctan((logR-logG)/(logR-logG+2*LogB))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-31 20:55:07
Implementacja 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));
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-06 10:45:27
Dla każdego, kto potrzebuje opisanego powyżej (gamma-nieskorygowanego) przesunięcia barwy jako parametryzowanego HLSL Pixel shader (przejrzałem go razem dla aplikacji WPF i pomyślałem, że mogę go nawet udostępnić):
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-23 11:16:08