web-dev-qa-db-fra.com

Conversion RVB en HSL

Je crée un outil Color Picker et pour le curseur HSL, je dois pouvoir convertir RVB en HSL. Lorsque j'ai cherché SO pour un moyen de faire la conversion, j'ai trouvé cette question conversion de couleurs HSL en RVB .

Bien qu'il fournisse une fonction pour effectuer la conversion de RVB à HSL, je ne vois aucune explication à ce qui se passe réellement dans le calcul. Pour mieux le comprendre, j'ai lu le HSL et HSV sur Wikipédia.

Plus tard, j'ai réécrit la fonction de la "conversion de couleurs HSL en RVB" en utilisant les calculs de la page "HSL et HSV".

Je suis bloqué sur le calcul de la teinte si le R est la valeur maximale. Voir le calcul de la page "HSL et HSV":

enter image description here

Cela vient d'une autre page wiki qui est en néerlandais:

enter image description here

et cela provient de réponses à "Conversion de couleurs HSL en RVB":

case r: h = (g - b) / d + (g < b ? 6 : 0); break; // d = max-min = c

J'ai testé les trois avec quelques valeurs RVB et elles semblent produire des résultats similaires (sinon exacts). Ce que je me demande, c'est s'ils font la même chose? Vais-je obtenir des résultats différents pour certaines valeurs RVB spécifiques? Lequel devrais-je utiliser?

hue = (g - b) / c;                   // dutch wiki
hue = ((g - b) / c) % 6;             // eng wiki
hue = (g - b) / c + (g < b ? 6 : 0); // SO answer
function rgb2hsl(r, g, b) {
    // see https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
    // convert r,g,b [0,255] range to [0,1]
    r = r / 255,
    g = g / 255,
    b = b / 255;
    // get the min and max of r,g,b
    var max = Math.max(r, g, b);
    var min = Math.min(r, g, b);
    // lightness is the average of the largest and smallest color components
    var lum = (max + min) / 2;
    var hue;
    var sat;
    if (max == min) { // no saturation
        hue = 0;
        sat = 0;
    } else {
        var c = max - min; // chroma
        // saturation is simply the chroma scaled to fill
        // the interval [0, 1] for every combination of hue and lightness
        sat = c / (1 - Math.abs(2 * lum - 1));
        switch(max) {
            case r:
                // hue = (g - b) / c;
                // hue = ((g - b) / c) % 6;
                // hue = (g - b) / c + (g < b ? 6 : 0);
                break;
            case g:
                hue = (b - r) / c + 2;
                break;
            case b:
                hue = (r - g) / c + 4;
                break;
        }
    }
    hue = Math.round(hue * 60); // °
    sat = Math.round(sat * 100); // %
    lum = Math.round(lum * 100); // %
    return [hue, sat, lum];
}
23
akinuri

J'ai lu plusieurs pages wiki et vérifié différents calculs, et créé des visualisations de projection de cube RVB sur un hexagone. Et je voudrais poster ma compréhension de cette conversion. Étant donné que je trouve intéressante cette conversion (représentations de modèles de couleurs utilisant des formes géométriques), je vais essayer d'être aussi approfondie que possible. Commençons d'abord par RVB.

RVB

Eh bien, cela n'a pas vraiment besoin de beaucoup d'explications. Dans sa forme la plus simple, vous avez 3 valeurs, R, G et B dans la plage de [0,255]. Par exemple, 51,153,204. Nous pouvons le représenter à l'aide d'un graphique à barres:

RGB Bar Graph

Cube RVB

Nous pouvons également représenter une couleur dans un espace 3D. Nous avons trois valeurs R, G, B qui correspondent à X, Y et Z. Les trois valeurs se trouvent dans la plage [0,255], Ce qui donne un cube. Mais avant de créer le cube RVB, travaillons d'abord sur l'espace 2D. Deux combinaisons de R, G, B nous donne: RG, RB, GB. Si nous devions les représenter graphiquement sur un avion, nous obtiendrions ce qui suit:

RGB 2D Graphs

Ce sont les trois premiers côtés du cube RVB. Si nous les plaçons sur un espace 3D, cela donne un demi-cube:

RGB Cube Sides

Si vous vérifiez le graphique ci-dessus, en mélangeant deux couleurs, nous obtenons une nouvelle couleur à (255, 255), ce sont le jaune, le magenta et le cyan. Encore une fois, deux combinaisons de ces éléments nous donnent: YM, YC et MC. Ce sont les côtés manquants du cube. Une fois que nous les avons ajoutés, nous obtenons un cube complet:

RGB Cube

Et la position de 51,153,204 Dans ce cube:

RGB Cube Color Position

Projection du cube RVB sur un hexagone

Maintenant que nous avons le cube RVB, projetons-le sur un hexagone. Tout d'abord, nous inclinons le cube de 45 ° sur le x, puis de 35,264 ° sur le y. Après la deuxième inclinaison, le coin noir est en bas et le coin blanc en haut, et ils traversent tous les deux l'axe z.

RGB Cube Tilt

Comme vous pouvez le voir, nous obtenons l'aspect hexagonal que nous voulons avec l'ordre de teinte correct lorsque nous regardons le cube depuis le haut. Mais nous devons projeter cela sur un véritable hexagone. Ce que nous faisons, c'est dessiner un hexagone de la même taille avec la vue de dessus du cube. Tous les coins de l'hexagone correspondent aux coins du cube et des couleurs, et le coin supérieur du cube qui est blanc, est projeté au centre de l'hexagone. Le noir est omis. Et si nous mappons chaque couleur sur l'hexagone, nous obtenons le bon aperçu.

Cube to Hexagon Projection

Et la position de 51,153,204 Sur l'hexagone serait:

Hue Color Position

Calcul de la teinte

Avant de faire le calcul, définissons ce qu'est la teinte.

La teinte est à peu près l'angle du vecteur par rapport à un point de la projection, avec du rouge à 0 °.

... la teinte est à quelle distance se trouve le bord de cet hexagone.

Ceci est le calcul de la page wiki HSL et HSV . Nous allons l'utiliser dans cette explication.

Wiki calc

Examinez l'hexagone et la position de 51,153,204 Dessus.

Hexagon basics

Tout d'abord, nous mettons à l'échelle les valeurs R, G, B pour remplir l'intervalle [0,1].

R = R / 255    R =  51 / 255 = 0.2
G = G / 255    G = 153 / 255 = 0.6
B = B / 255    B = 204 / 255 = 0.8

Ensuite, recherchez les valeurs max et min de R, G, B

M = max(R, G, B)    M = max(0.2, 0.6, 0.8) = 0.8
m = min(R, G, B)    m = min(0.2, 0.6, 0.8) = 0.2

Ensuite, calculez C (chroma). Le chroma est défini comme:

... la chrominance est à peu près la distance du point par rapport à l'origine.

Le chroma est la taille relative de l'hexagone passant par un point ...

C = OP / OP'
C = M - m
C = 0.8- 0.2 = 0.6

Maintenant, nous avons les valeurs R, G, B et C. Si nous vérifions les conditions, if M = B Renvoie vrai pour 51,153,204. Nous allons donc utiliser H'= (R - G) / C + 4.

Vérifions à nouveau l'hexagone. (R - G) / C Nous donne la longueur du segment BP.

segment = (R - G) / C = (0.2 - 0.6) / 0.6 = -0.6666666666666666

Nous placerons ce segment sur l'hexagone intérieur. Le point de départ de l'hexagone est R (rouge) à 0 °. Si la longueur du segment est positive, elle doit être sur RY, si elle est négative, elle doit être sur RM. Dans ce cas, il est négatif -0.6666666666666666 Et se trouve sur le bord RM.

Segment position & shift

Ensuite, nous devons déplacer la position du segment, ou plutôt P₁ Vers le B (parce que M = B). Le bleu est à 240°. L'hexagone a 6 côtés. Chaque côté correspond à 60°. 240 / 60 = 4. Nous devons décaler (incrémenter) le P₁ De 4 (Qui est de 240 °). Après le quart de travail, P₁ Sera à P et nous obtiendrons la longueur de RYGCP.

segment = (R - G) / C = (0.2 - 0.6) / 0.6 = -0.6666666666666666
RYGCP   = segment + 4 = 3.3333333333333335

La circonférence de l'hexagone est 6 Ce qui correspond à 360°. La distance de 53,151,204 À Est de 3.3333333333333335. Si nous multiplions 3.3333333333333335 Par 60, Nous obtiendrons sa position en degrés.

H' = 3.3333333333333335
H  = H' * 60 = 200°

Dans le cas de if M = R, Puisque nous plaçons une extrémité du segment à R (0 °), nous n'avons pas besoin de déplacer le segment sur R si la longueur du segment est positive. La position de P₁ Sera positive. Mais si la longueur du segment est négative, nous devons la décaler de 6, car une valeur négative signifie que la position angular est supérieure à 180 ° et nous devons effectuer une rotation complète.

Ainsi, ni la solution wiki néerlandaise hue = (g - b) / c; ni la solution wiki Eng hue = ((g - b) / c) % 6; ne fonctionneront pour une longueur de segment négative. Seule la réponse SO _ hue = (g - b) / c + (g < b ? 6 : 0); fonctionne à la fois pour les valeurs négatives et positives.

JSFiddle: Testez les trois méthodes pour rgb (255,71,99)


JSFiddle: Trouver visuellement la position d'une couleur dans le cube RGB et hexagone de teinte

Calcul de la teinte de travail:

console.log(rgb2hue(51,153,204));
console.log(rgb2hue(255,71,99));
console.log(rgb2hue(255,0,0));
console.log(rgb2hue(255,128,0));
console.log(rgb2hue(124,252,0));

function rgb2hue(r, g, b) {
  r /= 255;
  g /= 255;
  b /= 255;
  var max = Math.max(r, g, b);
  var min = Math.min(r, g, b);
  var c   = max - min;
  var hue;
  if (c == 0) {
    hue = 0;
  } else {
    switch(max) {
      case r:
        var segment = (g - b) / c;
        var shift   = 0 / 60;       // R° / (360° / hex sides)
        if (segment < 0) {          // hue > 180, full rotation
          shift = 360 / 60;         // R° / (360° / hex sides)
        }
        hue = segment + shift;
        break;
      case g:
        var segment = (b - r) / c;
        var shift   = 120 / 60;     // G° / (360° / hex sides)
        hue = segment + shift;
        break;
      case b:
        var segment = (r - g) / c;
        var shift   = 240 / 60;     // B° / (360° / hex sides)
        hue = segment + shift;
        break;
    }
  }
  return hue * 60; // hue is in [0,6], scale it up
}
65
akinuri

Pour continuer mon commentaire, la version anglaise semble correcte, mais je ne suis pas sûr de ce qui se passe dans la version néerlandaise car je ne comprends pas la page WIKI.

Voici une version ES6 que j'ai faite à partir de la page WIKI en anglais, avec quelques exemples de données qui semblent correspondre aux exemples WIKI (donner ou prendre la précision numérique de Javascript). J'espère que cela peut être utile lors de la création de votre propre fonction.

// see: https://en.wikipedia.org/wiki/RGB_color_model
// see: https://en.wikipedia.org/wiki/HSL_and_HSV

// expects R, G, B, Cmax and chroma to be in number interval [0, 1]
// returns undefined if chroma is 0, or a number interval [0, 360] degrees
function hue(R, G, B, Cmax, chroma) {
  let H;
  if (chroma === 0) {
    return H;
  }
  if (Cmax === R) {
    H = ((G - B) / chroma) % 6;
  } else if (Cmax === G) {
    H = ((B - R) / chroma) + 2;
  } else if (Cmax === B) {
    H = ((R - G) / chroma) + 4;
  }
  H *= 60;
  return H < 0 ? H + 360 : H;
}

// returns the average of the supplied number arguments
function average(...theArgs) {
  return theArgs.length ? theArgs.reduce((p, c) => p + c, 0) / theArgs.length : 0;
}

// expects R, G, B, Cmin, Cmax and chroma to be in number interval [0, 1]
// type is by default 'bi-hexcone' equation
// set 'luma601' or 'luma709' for alternatives
// see: https://en.wikipedia.org/wiki/Luma_(video)
// returns a number interval [0, 1]
function lightness(R, G, B, Cmin, Cmax, type = 'bi-hexcone') {
  if (type === 'luma601') {
    return (0.299 * R) + (0.587 * G) + (0.114 * B);
  }
  if (type === 'luma709') {
    return (0.2126 * R) + (0.7152 * G) + (0.0772 * B);
  }
  return average(Cmin, Cmax);
}

// expects L and chroma to be in number interval [0, 1]
// returns a number interval [0, 1]
function saturation(L, chroma) {
  return chroma === 0 ? 0 : chroma / (1 - Math.abs(2 * L - 1));
}

// returns the value to a fixed number of digits
function toFixed(value, digits) {
  return Number.isFinite(value) && Number.isFinite(digits) ? value.toFixed(digits) : value;
}

// expects R, G, and B to be in number interval [0, 1]
// returns a Map of H, S and L in the appropriate interval and digits
function RGB2HSL(R, G, B, fixed = true) {
  const Cmin = Math.min(R, G, B);
  const Cmax = Math.max(R, G, B);
  const chroma = Cmax - Cmin;
  // default 'bi-hexcone' equation
  const L = lightness(R, G, B, Cmin, Cmax);
  // H in degrees interval [0, 360]
  // L and S in interval [0, 1]
  return new Map([
    ['H', toFixed(hue(R, G, B, Cmax, chroma), fixed && 1)],
    ['S', toFixed(saturation(L, chroma), fixed && 3)],
    ['L', toFixed(L, fixed && 3)]
  ]);
}

// expects value to be number in interval [0, 255]
// returns normalised value as a number interval [0, 1]
function colourRange(value) {
  return value / 255;
};

// expects R, G, and B to be in number interval [0, 255]
function RGBdec2HSL(R, G, B) {
  return RGB2HSL(colourRange(R), colourRange(G), colourRange(B));
}

// converts a hexidecimal string into a decimal number
function hex2dec(value) {
  return parseInt(value, 16);
}

// slices a string into an array of paired characters
function pairSlicer(value) {
  return value.match(/../g);
}

// prepend '0's to the start of a string and make specific length
function prePad(value, count) {
  return ('0'.repeat(count) + value).slice(-count);
}

// format hex pair string from value
function hexPair(value) {
  return hex2dec(prePad(value, 2));
}

// expects R, G, and B to be hex string in interval ['00', 'FF']
// without a leading '#' character
function RGBhex2HSL(R, G, B) {
  return RGBdec2HSL(hexPair(R), hexPair(G), hexPair(B));
}

// expects RGB to be a hex string in interval ['000000', 'FFFFFF']
// with or without a leading '#' character
function RGBstr2HSL(RGB) {
  const hex = prePad(RGB.charAt(0) === '#' ? RGB.slice(1) : RGB, 6);
  return RGBhex2HSL(...pairSlicer(hex).slice(0, 3));
}

// expects value to be a Map object
function logIt(value) {
  console.log(value);
  document.getElementById('out').textContent += JSON.stringify([...value]) + '\n';
};

logIt(RGBstr2HSL('000000'));
logIt(RGBstr2HSL('#808080'));
logIt(RGB2HSL(0, 0, 0));
logIt(RGB2HSL(1, 1, 1));
logIt(RGBdec2HSL(0, 0, 0));
logIt(RGBdec2HSL(255, 255, 254));
logIt(RGBhex2HSL('BF', 'BF', '00'));
logIt(RGBstr2HSL('008000'));
logIt(RGBstr2HSL('80FFFF'));
logIt(RGBstr2HSL('8080FF'));
logIt(RGBstr2HSL('BF40BF'));
logIt(RGBstr2HSL('A0A424'));
logIt(RGBstr2HSL('411BEA'));
logIt(RGBstr2HSL('1EAC41'));
logIt(RGBstr2HSL('F0C80E'));
logIt(RGBstr2HSL('B430E5'));
logIt(RGBstr2HSL('ED7651'));
logIt(RGBstr2HSL('FEF888'));
logIt(RGBstr2HSL('19CB97'));
logIt(RGBstr2HSL('362698'));
logIt(RGBstr2HSL('7E7EB8'));
<pre id="out"></pre>
2
Xotic750

La teinte dans HSL est comme un angle dans un cercle. Les valeurs pertinentes pour un tel angle résident dans l'intervalle 0..360. Cependant, des valeurs négatives peuvent sortir du calcul. Et c'est pourquoi ces trois formules sont différentes. Ils font la même chose à la fin, ils gèrent simplement différemment les valeurs en dehors de l'intervalle 0..360. Ou, pour être précis, l'intervalle 0..6 qui est ensuite finalement multiplié par 60 à 0..360

hue = (g - b) / c; // dutch wiki ne fait rien avec des valeurs négatives et suppose que le code suivant peut gérer des valeurs H négatives.

hue = ((g - b) / c) % 6; // eng wiki utilise l'opérateur % pour ajuster les valeurs à l'intérieur de l'intervalle 0..6

hue = (g - b) / c + (g < b ? 6 : 0); // SO answer s'occupe des valeurs négatives en ajoutant +6 pour les rendre positives

Vous voyez que ce ne sont que des différences cosmétiques. La deuxième ou la troisième formule fonctionnera bien pour vous.

2
Matey