Farbton einer RGB-Farbe verschieben

Lesezeit: 13 Minuten

Ich versuche, eine Funktion zu schreiben, um den Farbton einer RGB-Farbe zu verschieben. Insbesondere verwende ich es in einer iOS-App, aber die Mathematik ist universell.

Die folgende Grafik zeigt, wie sich die R-, G- und B-Werte in Bezug auf den Farbton ändern.

Diagramm der RGB-Werte über die Farbtöne hinweg

Wenn man sich das ansieht, sollte es relativ einfach sein, eine Funktion zu schreiben, um den Farbton zu verschieben, ohne unangenehme Konvertierungen in ein anderes Farbformat durchzuführen, die mehr Fehler verursachen würden (was ein Problem sein könnte, wenn weiterhin kleine Verschiebungen auf eine Farbe angewendet werden). , und ich vermute, wäre rechenintensiver.

Hier ist, was ich bisher habe, welche Art von Arbeiten. Es funktioniert perfekt, wenn Sie von reinem Gelb oder Cyan oder Magenta wechseln, aber ansonsten wird es an einigen Stellen etwas schwammig.

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);
}

Ich weiß, dass Sie dies mit UIColors tun und den Farbton mit so etwas verschieben können:

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);

aber ich bin nicht verrückt danach, da es nur in iOS 5 funktioniert, und zwischen dem Zuweisen einer Reihe von Farbobjekten und der Konvertierung von RGB nach HSB und dann zurück scheint es ziemlich übertrieben zu sein.

Am Ende verwende ich möglicherweise eine Nachschlagetabelle oder berechne die Farben in meiner Anwendung vorab, aber ich bin wirklich neugierig, ob es eine Möglichkeit gibt, meinen Code zum Laufen zu bringen. Vielen Dank!

  • Ich habe Ihren Code nicht gelesen, aber müssen Sie basierend auf diesem Diagramm nicht Ihre RGB-Farbe in HSV umwandeln, um herauszufinden, wo Sie sich in diesem Diagramm befinden, damit Sie herausfinden können, wie Sie sich bewegen müssen?

    – Oliver Charlesworth

    14. Dezember 2011 um 16:24 Uhr

Benutzeravatar von Mark Ransom
Mark Lösegeld

Der RGB-Farbraum beschreibt einen Würfel. Es ist möglich, diesen Würfel um die Diagonalachse von (0,0,0) bis (255,255,255) zu drehen, um eine Änderung des Farbtons zu bewirken. Beachten Sie, dass einige der Ergebnisse außerhalb des Bereichs von 0 bis 255 liegen und abgeschnitten werden müssen.

Ich hatte endlich die Chance, diesen Algorithmus zu codieren. Es ist in Python, aber es sollte einfach sein, es in die Sprache Ihrer Wahl zu übersetzen. Die Formel für die 3D-Rotation kam von http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

Bearbeiten: Wenn Sie den Code gesehen haben, den ich zuvor gepostet habe, ignorieren Sie ihn bitte. Ich war so begierig darauf, eine Formel für die Drehung zu finden, dass ich eine matrixbasierte Lösung in eine Formel umwandelte, ohne zu wissen, dass die Matrix die beste Form war. Ich habe die Berechnung der Matrix immer noch vereinfacht, indem ich die Konstante sqrt(1/3) für Achseneinheitsvektorwerte verwendet habe, aber dies ist im Geiste viel näher an der Referenz und einfacher in der Berechnung pro Pixel apply auch.

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)

Hier sind einige Ergebnisse aus dem oben Gesagten:

Beispiel für die Farbtonrotation

Eine andere Umsetzung derselben Idee finden Sie unter http://www.graficaobscura.com/matrix/index.html

  • Hat mir gerade mehrere Stunden gespart. Wie kann das nicht mehr Upvotes haben?!

    – Escher

    10. Dezember 2015 um 10:33 Uhr

  • @Escher viele Gründe – 1. Ich habe Tage gebraucht, um die Antwort vollständig zu konkretisieren. 2. Es ist nicht etwas, was viele Leute tun müssen. 3. Die offensichtliche Lösung, in einen Farbraum mit einer Farbtonkomponente umzuwandeln, ist einfach und funktioniert für viele Menschen gut genug.

    – Markieren Sie Lösegeld

    10. Dezember 2015 um 13:14 Uhr

  • @ AlicanC Als ich den Code schrieb, dachte ich, das wäre offensichtlich apply müsste pro Pixel aufgerufen werden, und set_hue_rotation würde nur für die Einrichtung verwendet werden. Ich schätze ich lag falsch.

    – Markieren Sie Lösegeld

    19. Januar 2016 um 23:52 Uhr

  • Ich finde diese Lösung funktioniert am besten von allen, die ich auf SO gefunden habe. sehr gute Farbrotation

    – Andrea Bogazzi

    6. August 2017 um 12:36 Uhr

  • @Attila Die Diagonale eines 256x256x256-Würfels ist länger als 256, wenn Sie ihn also drehen, ragen diese Ecken einfach heraus. Das liegt daran, dass RGB einen Würfel und keine Kugel beschreibt.

    – Markieren Sie Lösegeld

    24. April 2020 um 16:44 Uhr

Benutzeravatar von Jacob Eggers
Jakob Eggers

Bearbeiten pro Kommentar geändert “sind alle” in “können linear angenähert werden durch”.
Bearbeiten 2 Offsets hinzufügen.


Im Wesentlichen sind die gewünschten Schritte

RBG->HSV->Update hue->RGB

Da diese kann angenähert werden lineare Matrixtransformationen (dh sie sind assoziativ), können Sie sie in einem einzigen Schritt ohne unangenehme Konvertierung oder Genauigkeitsverlust durchführen. Sie multiplizieren einfach die Transformationsmatrizen miteinander und verwenden dies, um Ihre Farben zu transformieren.

Hier gibt es eine schnelle Schritt-für-Schritt-Anleitung http://beesbuzz.biz/code/hsv_color_transforms.php

Hier ist der C++-Code (mit entfernten Sättigungs- und Werttransformationen):

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;
}

  • Als ursprünglicher Autor der Seite, auf die Sie verlinkt haben, möchte ich darauf hinweisen, dass RGB-> HSV und HSV-> RGB keine linearen Matrixtransformationen sind. Was dieser Code tatsächlich tut, ist die Transformation von RGB-> YIQ (was ein lineares Äquivalent zu HSV ist) und die Rotation durch die IQ-Ebene. Es erzeugt auch nicht ganz die Ergebnisse, die die Leute manchmal erwarten. Der Versuch, das zu erklären und warum HSV von Anfang an ein lächerliches Farbkonzept ist, passt jedoch nicht in dieses Kommentarfeld. 🙂

    – flauschige

    9. Februar 2012 um 7:31 Uhr

  • Ich kann die korrekten Ergebnisse mit Ihrer Methode nicht reproduzieren, aber die Antwort von Mark Ransom hat hervorragend funktioniert. Hier ein Beispiel: Eingabe ([R,G,B]H) = ([86,52,30]210) und die Ausgabe ist [-31,15,2] für deine Methode und [36,43,88] mit Markus. Ich glaube nicht, dass Rundungsfehler für diesen drastischen Unterschied verantwortlich sein könnten, etwas stimmt nicht.

    – MasterHD

    27. Mai 2015 um 15:09 Uhr

  • @MasterHD Es sieht so aus, als hätte ich die Offsets vergessen, aber ich bekomme immer noch einen Unterschied [28,75,62]also bin ich mir nicht sicher, was jetzt aus ist.

    – Jakob Egger

    27. Mai 2015 um 22:44 Uhr

  • @mcd schrieb: “in der letzten Rechenzeile ist ein Vorzeichen vertauscht. + (.114-.886*U-.203*W)*in.b; sollte sein + (.114+.886*U-.203*W)*in.b;“. (Das OP hat nicht genug Ruf, um es zu kommentieren.)

    – Nisse Engström

    2. Juni 2015 um 17:18 Uhr


  • @fluffy Ich weiß, das ist Jahre her, aber ist es möglich, dass Sie Ihren Code mit signifikanteren Ziffern aktualisieren können? Ich führe Ihren Code in Haxe (nicht C++) aus und erhalte einige seltsame Ergebnisse. Ich weiß mit Sicherheit, dass ich an Präzision verliere, und ich frage mich, ob signifikantere Ziffern helfen würden.

    – Asche999

    21. Februar 2016 um 14:48 Uhr

Benutzeravatar von MasterHD
MasterHD

Ich war enttäuscht von den meisten Antworten, die ich hier gefunden habe, einige waren fehlerhaft und im Grunde völlig falsch. Am Ende verbrachte ich mehr als 3 Stunden damit, das herauszufinden. Die Antwort von Mark Ransom ist richtig, aber ich möchte eine vollständige C-Lösung anbieten, die auch mit MATLAB verifiziert ist. Ich habe das gründlich getestet, und hier ist der C-Code:

#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;
}

HINWEIS: Die Rotationsmatrix hängt nur vom Farbton ab (fHue), also sobald Sie berechnet haben matrix[3][3]du kannst Wiederverwendung es für jedes Pixel im Bild, das der gleichen Farbtontransformation unterzogen wird! Dadurch wird die Effizienz drastisch verbessert. Hier ist ein MATLAB-Code, der die Ergebnisse verifiziert:

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

Hier ist eine Beispieleingabe/-ausgabe, die von beiden Codes reproduzierbar war:

TransformH(86,52,30,210)
ans =
    36
    43
    88

Also der Eingang RGB von [86,52,30] umgewandelt wurde [36,43,88] mit einem Farbton von 210.

  • Hey – danke – ich habe festgestellt, dass die Farben schließlich dunkler werden, wenn Sie dies in einer Schleife tun, sodass Sie die Helligkeit etwas erhöhen müssen. Prob einige Rundungsverluste. zB tat ich: Float Bright = 1.01; int r = (float)fg.r * hell; wenn (r > 255) {r = 255; } fg.r = r; int g = (float)fg.g * hell; wenn (g > 255) {g = 255; } fg.g = g; int b = (float)fg.b * hell; wenn (b > 255) {b = 255; } fg.b = b;

    – Koboldhack

    15. November 2020 um 16:42 Uhr

Javascript-Implementierung (basierend auf Vladimirs PHP oben)

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));
}

Benutzeravatar von Sebastian Dressler
Sebastian Dressler

Grundsätzlich gibt es zwei Möglichkeiten:

  1. Konvertieren Sie RGB -> HSV, ändern Sie den Farbton, konvertieren Sie HSV -> RGB
  2. Ändern Sie den Farbton direkt mit einer linearen Transformation

Ich bin mir nicht sicher, wie ich 2 implementieren soll, aber im Grunde müssen Sie eine Transformationsmatrix erstellen und das Bild durch diese Matrix filtern. Dadurch wird das Bild jedoch neu eingefärbt, anstatt nur den Farbton zu ändern. Wenn das für Sie in Ordnung ist, dann könnte dies eine Option sein, aber wenn nicht, ist eine Konvertierung nicht zu vermeiden.

Bearbeiten

Ein wenig Forschung zeigt Dies, was meine Gedanken bestätigt. Zusammenfassend: Die Konvertierung von RGB nach HSV sollte bevorzugt werden, wenn ein exaktes Ergebnis gewünscht wird. Das Modifizieren des ursprünglichen RGB-Bildes durch eine lineare Transformation führt ebenfalls zu einem Ergebnis, aber dies färbt das Bild eher ein. Der Unterschied erklärt sich wie folgt: Die Konvertierung von RGB nach HSV ist nichtlinear, während die Transformation linear ist.

  • Habe eine Bearbeitung vorgenommen. Das mache ich beim nächsten Mal direkt, ohne Erinnerung 😉

    – Sebastian Dressler

    14. Dezember 2011 um 19:23 Uhr

Benutzeravatar von Dave P
David P.

Der Beitrag ist alt, und das ursprüngliche Poster suchte nach ios-Code – ich wurde jedoch über eine Suche nach Visual Basic-Code hierher geschickt, also habe ich für alle wie mich Marks Code in ein vb .net-Modul konvertiert:

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

Ich habe allgemeine Begriffe in (lange) Variablen geschrieben, aber ansonsten ist es eine einfache Konvertierung – hat für meine Bedürfnisse gut funktioniert.

Übrigens habe ich versucht, Mark eine positive Bewertung für seinen exzellenten Code zu hinterlassen, aber ich hatte selbst nicht genug Stimmen, um ihn sichtbar zu machen (Hinweis, Hinweis).

  • Habe eine Bearbeitung vorgenommen. Das mache ich beim nächsten Mal direkt, ohne Erinnerung 😉

    – Sebastian Dressler

    14. Dezember 2011 um 19:23 Uhr

Benutzeravatar von Artur Ilkaev
Artur Ilkajew

WebGL-Version:

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;
}

1402570cookie-checkFarbton einer RGB-Farbe verschieben

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy