Saltar al contenido

Conversión de RGB a HSL

He estado leyendo varias páginas wiki y comprobando diferentes cálculos, y creando visualizaciones de proyección de cubos RGB en un hexágono. Y me gustaría publicar mi comprensión de esta conversión. Dado que encuentro interesante esta conversión (representaciones de modelos de color usando formas geométricas), intentaré ser lo más completo posible. Primero, comencemos con RGB.

RGB

Bueno, esto realmente no necesita mucha explicación. En su forma más simple, tiene 3 valores, R, G y B en el rango de [0,255]. Por ejemplo, 51,153,204. Podemos representarlo usando un gráfico de barras:

Gráfico de barras RGB

Cubo RGB

También podemos representar un color en un espacio 3D. Tenemos tres valores R, G, B que corresponde a X, Y, y Z. Los tres valores están en el [0,255] rango, que da como resultado un cubo. Pero antes de crear el cubo RGB, trabajemos primero en el espacio 2D. Dos combinaciones de R, G, B nos da: RG, RB, GB. Si tuviéramos que graficarlos en un plano, obtendríamos lo siguiente:

Gráficos RGB 2D

Estos son los primeros tres lados del cubo RGB. Si los colocamos en un espacio 3D, resulta en un medio cubo:

Lados del cubo RGB

Si comprueba el gráfico anterior, al mezclar dos colores, obtenemos un nuevo color en (255,255), y estos son amarillo, magenta y cian. Nuevamente, dos combinaciones de estos nos dan: YM, YC y MC. Estos son los lados que faltan del cubo. Una vez que los agregamos, obtenemos un cubo completo:

Cubo RGB

Y la posición de 51,153,204 en este cubo:

Posición de color del cubo RGB

Proyección de un cubo RGB en un hexágono

Ahora que tenemos el cubo RGB, proyectémoslo en un hexágono. Primero, inclinamos el cubo 45 ° en el xy luego 35,264 ° en el y. Después de la segunda inclinación, la esquina negra está en la parte inferior y la esquina blanca está en la parte superior, y ambas pasan a través del z eje.

Inclinación del cubo RGB

Como puede ver, obtenemos el aspecto hexagonal que queremos con el orden de tono correcto cuando miramos el cubo desde la parte superior. Pero necesitamos proyectar esto en un hexágono real. Lo que hacemos es dibujar un hexágono del mismo tamaño que la vista superior del cubo. Todas las esquinas del hexágono corresponden a las esquinas del cubo y los colores, y la esquina superior del cubo que es blanca, se proyecta en el centro del hexágono. Se omite el negro. Y si mapeamos cada color en el hexágono, lo vemos bien.

Proyección de cubo a hexágono

Y la posición de 51,153,204 en el hexágono sería:

Posición del color de tono

Calcular el tono

Antes de hacer el cálculo, definamos qué es el tono.

El tono es aproximadamente el ángulo del vector a un punto en la proyección, con el rojo a 0 °.

… el tono es qué tan lejos alrededor del borde del hexágono se encuentra el punto.

Este es el cálculo de la página wiki de HSL y HSV. Lo usaremos en esta explicación.

Wiki calc

Examine el hexágono y la posición de 51,153,204 en eso.

Conceptos básicos del hexágono

Primero, escalamos los valores R, G, B para llenar el [0,1] intervalo.

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

A continuación, busque el max y min valores 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

Entonces, calcula C (croma). Chroma se define como:

… el croma es aproximadamente la distancia entre el punto y el origen.

El croma es el tamaño relativo del hexágono que pasa por un punto …

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

Ahora tenemos el R, G, B, y C valores. Si comprobamos las condiciones, if M = B devoluciones true por 51,153,204. Entonces, usaremos H'= (R - G) / C + 4.

Revisemos el hexágono nuevamente. (R - G) / C nos da la longitud de BP segmento.

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

Colocaremos este segmento en el hexágono interior. El punto inicial del hexágono es R (rojo) a 0 °. Si la longitud del segmento es positiva, debe estar en RY, si es negativo, debería estar encendido RM. En este caso, es negativo -0.6666666666666666, y está en el RM borde.

Posición y cambio de segmento

A continuación, necesitamos cambiar la posición del segmento, o más bien P₁ Towars el B (porque M = B). El azul está en 240°. El hexágono tiene 6 lados. Cada lado corresponde a 60°. 240 / 60 = 4. Necesitamos cambiar (incrementar) el P₁ por 4 (que es 240 °). Después del turno, P₁ será a las P y obtendremos la longitud de RYGCP.

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

La circunferencia del hexágono es 6 que corresponde a 360°. 53,151,204la distancia a es 3.3333333333333335. Si multiplicamos 3.3333333333333335 por 60, obtendremos su posición en grados.

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

En el caso de if M = R, dado que colocamos un extremo del segmento en R (0 °), no es necesario cambiar el segmento a R si la longitud del segmento es positiva. La posición de P₁ será positivo. Pero si la longitud del segmento es negativa, debemos cambiarla en 6, porque un valor negativo significa que la posición angular es mayor que 180 ° y debemos hacer una rotación completa.

Entonces, ni la solución wiki holandesa hue = (g - b) / c; ni la solución Eng wiki hue = ((g - b) / c) % 6; funcionará para la longitud del segmento negativo. Solo la respuesta SO hue = (g - b) / c + (g < b ? 6 : 0); funciona tanto para valores negativos como positivos.

JSFiddle: prueba los tres métodos para rgb (255,71,99)


JSFiddle: busque la posición de un color en el cubo RGB y el hexágono de tono visualmente

Cálculo del tono de trabajo:

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

Continuando con mi comentario, la versión en inglés parece correcta, pero no estoy seguro de lo que está sucediendo en la versión en holandés porque no entiendo la página WIKI.

Aquí hay una versión ES6 que hice a partir de la página WIKI en inglés, junto con algunos datos de muestra que parecen coincidir con los ejemplos WIKI (proporcione o tome la precisión numérica de Javascript). Con suerte, puede ser útil al crear su propia función.

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

El tono en HSL es como un ángulo en un círculo. Los valores relevantes para dicho ángulo residen en el intervalo 0..360. Sin embargo, pueden surgir valores negativos del cálculo. Y es por eso que esas tres fórmulas son diferentes. Al final, hacen lo mismo, solo manejan de manera diferente los valores fuera del intervalo 0..360. O, para ser precisos, el intervalo 0..6 que finalmente se multiplica por 60 a 0..360

hue = (g - b) / c; // dutch wiki

no hace nada con valores negativos y supone que el código subsiguiente puede manejar valores H negativos.

hue = ((g - b) / c) % 6; // eng wiki usa el % operador para ajustar los valores dentro del intervalo 0..6

hue = (g - b) / c + (g < b ? 6 : 0); // SO answer se encarga de los valores negativos agregando +6 para hacerlos positivos

Ves que estas son solo diferencias cosméticas. La segunda o la tercera fórmula funcionarán bien para usted.

Si tienes algún titubeo y forma de perfeccionar nuestro ensayo te evocamos dejar una crítica y con placer lo interpretaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags :

Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *