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.

Wykres wartości RGB w różnych odcieniach

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!

Author: Anthony Mattox, 2011-12-14

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;
}
 15
Author: Jacob Eggers,
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_angle

Edit: 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:

Przykład rotacji odcieni

Możesz znaleźć inną realizację tego samego pomysłu na http://www.graficaobscura.com/matrix/index.html

 34
Author: Mark Ransom,
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.

 6
Author: MasterHD,
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:

  1. Konwertuj RGB -> HSV, Zmień odcień, Konwertuj HSV - > RGB
  2. 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.

 3
Author: Sebastian Dressler,
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ź).

 2
Author: Dave P.,
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ć.

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

 0
Author: Scott Messinger,
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]
 0
Author: Gustavo Trigueiros,
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.
 0
Author: Gustavo Trigueiros,
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))
 0
Author: Gustavo Trigueiros,
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));
    }
}
 0
Author: Vladimir Klubov,
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;
    }
 0
Author: Robin B,
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