Décalage teinte D'une couleur RVB

J'essaie d'écrire une fonction pour décaler la teinte D'une couleur RVB. Plus précisément, je l'utilise dans une application iOS, mais le calcul est universel.

Le graphique ci-dessous montre comment les valeurs R, G et B changent par rapport à la teinte.

Graphique des valeurs RVB à travers les teintes

En regardant cela, il semble que cela devrait être relativement simple d'écrire une fonction pour déplacer la teinte sans faire de mauvaises conversions vers un format de couleur différent qui introduirait plus d'erreur (ce qui pourrait être un problème si continuer appliquer de petits décalages à une couleur), et je soupçonne que ce serait plus coûteux en calcul.

Voici ce que j'ai jusqu'à présent qui fonctionne. Cela fonctionne parfaitement si vous passez du jaune pur ou du cyan ou du magenta, mais sinon il devient un peu squiffy à certains endroits.

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

Je sais que vous pouvez le faire avec UIColors et déplacer la teinte avec quelque chose comme ceci:

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

Mais je ne suis pas fou à ce sujet car cela ne fonctionne que dans iOS 5, et entre l'allocation d'un certain nombre de couleurs objets et conversion de RVB en HSB, puis retour, il semble assez exagéré.

Je pourrais finir par utiliser une table de recherche ou pré-calculer les couleurs dans mon application, mais je suis vraiment curieux de savoir s'il existe un moyen de faire fonctionner mon code. Merci!

30
demandé sur Anthony Mattox 2011-12-14 20:21:23

11 réponses

Edit par commentaire changé "sont tous" à "peut être approximé linéairement par".
Modifier 2 ajout de décalages.


Essentiellement, les étapes que vous voulez sont

RBG->HSV->Update hue->RGB

Puisque cespeuvent être approximés par transformations de matrice linéaire (c'est-à-dire qu'ils sont associatifs), vous pouvez l'effectuer en une seule étape sans conversion ni perte de précision. Vous venez de multiple les matrices de transformation les unes avec les autres, et l'utilisez pour transformer votre couleur.

Il y a une étape rapide par étape ici http://beesbuzz.biz/code/hsv_color_transforms.php

Voici le code C++ (avec les transformations de saturation et de valeur supprimées):

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
répondu Jacob Eggers 2015-06-03 00:38:06

L'espace colorimétrique RVB décrit un cube. Il est possible de faire pivoter ce cube autour de l'axe diagonal de (0,0,0) à (255,255,255) pour effectuer un changement de teinte. Notez que certains des résultats se situeront en dehors de la plage 0 à 255 et devront être coupés.

J'ai finalement eu la chance de coder cet algorithme. C'est en Python mais il devrait être facile de traduire dans la langue de votre choix. La formule pour la rotation 3D est venue de http://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

Edit: Si vous avez vu le code que j'ai posté précédemment, veuillez l'ignorer. J'étais tellement impatient de trouver une formule pour la rotation que j'ai converti une solution basée sur une matrice en une formule, ne réalisant pas que la matrice était la meilleure forme tout au long. J'ai encore simplifié le calcul de la matrice en utilisant la constante sqrt (1/3) pour les valeurs vectorielles d'unité d'axe, mais cela est beaucoup plus proche dans l'esprit de la référence et plus simple dans le calcul par pixel apply aussi.

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)

Voici quelques résultats de ce qui précède:

Exemple de rotation de teinte

Vous pouvez trouver une implémentation différente de la même idée à http://www.graficaobscura.com/matrix/index.html

34
répondu Mark Ransom 2011-12-19 04:49:57

J'ai été déçu par la plupart des réponses que j'ai trouvées ici, certaines étaient imparfaites et fondamentalement fausses. J'ai fini par passer 3 heures à essayer de comprendre cela. La réponse de Mark Ransom est correcte, mais je veux offrir une solution c complète qui est également vérifiée avec MATLAB. J'ai testé cela à fond, et voici le code 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;
}

REMARQUE: La matrice de rotation dépend de la Teinte (fHue), une fois que vous avez calculé matrix[3][3] vous pouvez réutilisation pour chaque pixel de l'image qui subit la même transformation de teinte! Cela permettra d'améliorer l'efficacité de façon drastique. Voici un code MATLAB qui vérifie les résultats:

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

Voici un exemple d'entrée / sortie qui était reproductible par les deux codes:

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

Donc l'entrée RVB de [86,52,30], a été convertie [36,43,88] utilisation d'une teinte de 210.

6
répondu MasterHD 2015-11-28 03:19:14

Fondamentalement, il y a deux options:

  1. convertir RVB - > HSV, changer la teinte, convertir HSV - > RVB
  2. Changer la teinte directement avec une transformation linéaire

Je ne suis pas vraiment sûr de la façon d'implémenter 2, mais fondamentalement, vous devrez créer une matrice de transformation et filtrer l'image à travers cette matrice. Cependant, cela va recolorer l'image au lieu de changer seulement la teinte. Si cela vous convient, cela pourrait être une option, mais sinon une conversion ne peut pas l'être éviter.

Modifier

Une petite recherche montre Ce, ce qui confirme mes pensées. Pour résumer: la conversion de RVB en HSV devrait être préférée, si un résultat exact est désiré. La modification de L'image RVB d'origine par une transformation linéaire conduit également à un résultat, mais cela teinte plutôt l'image. La différence est expliquée comme suit: la conversion de RVB en HSV est non linéaire, alors que la transformation est linéaire.

3
répondu Sebastian Dressler 2011-12-14 19:22:57

Le post est ancien, et l'affiche originale cherchait du code ios-cependant, j'ai été envoyé ici via une recherche de code visual basic, donc pour tous ceux comme moi, j'ai converti le code de Mark en un module 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

J'ai mis des termes communs dans des variables (longues), mais sinon c'est une conversion simple - a bien fonctionné pour mes besoins.

Au fait, j'ai essayé de laisser Mark un upvote pour son excellent code, mais je n'avais pas assez de votes moi-même pour lui permettre d'être visible (indice, Allusion).

2
répondu Dave P. 2016-02-08 10:48:35

Il semble que la conversion en HSV ait le plus de sens. Sass fournit des aides de couleur étonnantes. C'est en ruby, mais cela pourrait être utile.

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

0
répondu Scott Messinger 2011-12-14 18:04:20

Code excellent, mais, je me demande que cela peut être plus rapide si vous n'utilisez tout simplement pas self.matrice [2] [0], soi.matrice [2] [1], Soi.la matrice[2][1]

Par conséquent, set_hue_rotation peut être écrit simplement comme:

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
répondu Gustavo Trigueiros 2014-05-31 11:08:19

Scott....pas exactement. L'algo semble fonctionner de la même manière que dans HSL/HSV, mais plus rapidement. De plus, si vous multipliez simplement les 1er 3 éléments du tableau avec le facteur de gris, vous ajoutez / diminuez luma.

Exemple...Les niveaux de gris de Rec709 ont ces valeurs [GrayRedFactor_Rec709: R $ 0.212671 GrayGreenFactor_Rec709: R $ 0.715160 GrayBlueFactor_Rec709: R $ 0.072169]

Quand vous multply auto.matrice [x] [x] avec le correspondant GreyFactor vous diminuez luma sans toucher saturation 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]

Et le contraire est également vrai.Si vous divisez au lieu de multiplier, la luminosité augmente considérablement.

De ce que je teste ces algorithmes peuvent être un remplacement merveilleux pour HSL, tant que vous n'avez pas besoin de saturation, bien sûr.

Essayez de le faire...faites pivoter la teinte à seulement 1 degré (juste pour forcer l'algo à fonctionner correctement tout en gardant la même sensibilité de perception de l'image), et multipliez par ces facteurs.

0
répondu Gustavo Trigueiros 2014-05-31 13:24:58

En outre, Marks algo produit des résultats plus précis.

Par exemple, si vous faites pivoter la teinte à 180 ° à l'aide de l'espace colorimétrique HSV, l'image peut donner une couleur rougeâtre.

Mais sur les marques algo, l'image est correctement tourner. Peaux Tons par exemple (teinte = 17, Sat = 170, L = 160 dans PSP) se transforme correctement en bleu qui ont teinte autour 144 dans PSP, et toutes les autres couleurs de l'image sont correctement tourner.

L'algo a du sens puisque la teinte n'est rien de plus, rien d'autre qu'un Fonction logarithmique d'un arctan de rouge, vert, bleu tel que défini par cette formule:

Hue = arctan((logR-logG)/(logR-logG+2*LogB))
0
répondu Gustavo Trigueiros 2014-05-31 20:55:07

Implémentation 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
répondu Vladimir Klubov 2018-03-06 10:45:27

Pour tous ceux qui ont besoin du décalage de teinte décrit ci-dessus (gamma-non corrigé) en tant que shader de Pixel HLSL paramétré (Je l'ai traversé ensemble pour une application WPF et j'ai pensé que je pourrais même le partager):

    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
répondu Robin B 2018-07-23 11:16:08