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.
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:
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
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.
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:
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
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 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
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));
}
Sebastian Dressler
Grundsätzlich gibt es zwei Möglichkeiten:
Konvertieren Sie RGB -> HSV, ändern Sie den Farbton, konvertieren Sie HSV -> RGB
Ä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
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 😉
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