2008-10-25 24 views
52

Tengo un código que genera la imagen de un gráfico circular. Es una clase de propósito general, por lo que se puede dar cualquier número de cortes como entrada. Ahora tengo problemas para elegir buenos colores para las rebanadas. ¿Hay algún algoritmo que sea bueno en eso?¿Cómo escoger colores para un gráfico circular?

¿O tal vez debería elegir y enumerar manualmente los colores fijos? Pero cuantos. Tal vez 10 colores y espero que no haya más de 10 rebanadas alguna vez? Además, ¿qué 10 colores elegir?

colores tienen que seguir algunas reglas:

  • que necesitan para verte bien
  • colores adyacentes no deben ser similares (azul junto al verde es un no-go)
  • color de fondo pastel es blanco , por lo blanco está fuera de opción

algún algoritmo manipular con valores RGB sería una solución preferida.

+4

cómo es similar azul a verde? – peterchen

+10

@peterchen - muy similar si eres daltónico azul-verde;) – redcalx

Respuesta

16

Precompilaría una lista de aproximadamente 20 colores, luego comenzaría a repetir con el 2nd color. De esta manera no romperás tu segunda regla. Además, si alguien hace un gráfico circular con más de 20 sectores, tienen problemas mayores. :)

+0

Esto es exactamente lo que hace la aplicación web en la que estoy trabajando actualmente. Crea 20, luego repite esos 20 diez veces para crear una lista de 200. Sin embargo, tengo un gráfico circular con más de 20 sectores, y los colores se repiten. ¿Alguna sugerencia? –

+2

Si realmente necesita ~ 200 colores diferentes, es posible que desee considerar usar la paleta Web-safe (o un subconjunto). http://en.wikipedia.org/wiki/Web_colors –

+0

Solo una implementación de la sugerencia de @ BilltheLizard: 'if (pie.length% palette.length == 1) {palette.push (palette [1])}'. Parece que la regla 2 solo se rompe cuando tenemos un múltiplo de longitud de color + 1 rebanada; esto solo garantiza que repetiremos desde el segundo en el caso de ruptura (útil cuando se usa algo como d3 [escalas ordinales] (https://github.com/mbostock/d3/wiki/Ordinal-Scales) para elegir el color). Ahora podemos usar solo unos pocos colores de una buena (es decir, la regla 1 es satisfactoria) [paleta] (http://colorbrewer2.org/) y no preocuparnos por romper la regla 2. – funseiki

1

Encontré esta fórmula de pseudocódigo que podría ayudar. Puedes comenzar con un conjunto para sembrarlo.

Diferencia de Color Fórmula

La siguiente es la fórmula sugerida por el W3C para determinar la diferencia entre dos colores.

(máximo (valor rojo 1, valor rojo 2) - mínimo (valor rojo 1, valor rojo 2)) + (máximo (valor verde 1, valor verde 2) - mínimo (valor verde 1, valor verde 2)) + (máximo (valor azul 1, el valor de azul 2) - mínimo (valor azul 1, el valor de azul 2))

la diferencia entre el color de fondo y el color de primer plano debe ser mayor que 500.

Here is the source

2

Hay un generador here. Está diseñado para el diseño web, pero los colores también se verían bien en un gráfico circular.

Puede precompilar una lista de colores agradables o examinar la lógica detrás del generador y hacer algo similar usted mismo.

+1

Alguna explicación del generador o su algoritmo sería agradable , ya que la carne de este post está oculta detrás del enlace en este momento, y los enlaces tienen una desagradable costumbre de morir. –

12

Eche un vistazo a Color Brewer, una herramienta que ayuda a definir un esquema de coloreado para transportar información cualitativa o cuantitativa: mapas, gráficos, etc. De tres "tipos" de paletas que esta herramienta puede generar: secuencial, cualitativa y divergentes: probablemente necesite lo último, divergentes ...

Incluso puede descargar archivos de Excel con definiciones RGB de todas las paletas.

64

Lo resuelto de la siguiente manera:

  1. elegir un color de base .
  2. Calcular su hue (baseHue).
  3. crear un color con la misma saturación y luminosidad, con su tonalidad calcula como:
     
        hue = baseHue + ((240/pieces) * piece % 240 
    

En C#:

int n = 12; 

Color baseColor = System.Drawing.ColorTranslator.FromHtml("#8A56E2"); 
double baseHue = (new HSLColor(baseColor)).Hue; 

List<Color> colors = new List<Color>(); 
colors.Add(baseColor); 

double step = (240.0/(double)n); 

for (int i = 1; i < n; ++i) 
{ 
    HSLColor nextColor = new HSLColor(baseColor); 
    nextColor.Hue = (baseHue + step * ((double)i)) % 240.0; 
    colors.Add((Color)nextColor); 
} 

string colors = string.Join(",", colors.Select(e => e.Name.Substring(2)).ToArray()); 

he utilizado la HSLColor class.

El Google Charts example que utiliza 12 piezas y un color de base de # 8A56E2:

Chart Example

+2

En su ejemplo, los dos colores superiores izquierdos (10 horas hasta la medianoche) se ven idénticos en mi pantalla. – Brann

+0

¿Cómo no ignora esto por completo la regla n. ° 2, que los colores similares no deberían estar adyacentes? Hace un bonito color * rueda *, pero no tan bueno para un gráfico circular. – Geobits

+0

Aquí está el equivalente para Android: https://gist.github.com/siddharth96/cbb2d31509f9149e4565 Esto también combina la respuesta de Pic Mickael dada a continuación –

4

Este 1985 papel por "ROSS E. Roley, CAPT" da un algoritmo para maximizar la separación de colores para un conjunto arbitrario de colores (complete with code in FORTRAN).

(Separación de colores parece ser un tema importante la visualización de las fuerzas militares para prevenir incidentes azules sobre azul.)

Sin embargo, si usted quiere meter a un conjunto de 20 colores, una solución rápida y sencilla haría debe elegir los vértices de un dodecaedro y convertir las coordenadas (x, y, z) (adecuadamente escaladas) en (r, g, b).

9

Aprovechando this solution para resolver la regla # 2 de la pregunta, el siguiente algoritmo intercambia colores alrededor del punto medio de la torta. Los dos parámetros:

  1. pNbColors es el número de cortes en el pastel
  2. pNonAdjacentSimilarColor un booleano para indicar si usted quiere tener colores similares adyacentes o no.

estoy usando ColorHSL, ColorRGB y ColorUtils (proporcionada a continuación).

public static function ColorArrayGenerator(
    pNbColors:int, 
    pNonAdjacentSimilarColor:Boolean = false):Array 
{  
    var colors:Array = new Array(); 
    var baseRGB:ColorRGB = new ColorRGB(); 
    baseRGB.setRGBFromUint(0x8A56E2); 

    var baseHSL:ColorHSL = new ColorHSL(); 
    rgbToHsl(baseHSL, baseRGB); 

    var currentHue:Number = baseHSL.Hue; 

    colors.push(baseRGB.getUintFromRGB()); 

    var step:Number = (360.0/pNbColors); 
    var nextHSL:ColorHSL; 
    var nextRGB:ColorRGB; 
    var i:int; 

    for (i = 1; i < pNbColors; i++) 
    { 
     currentHue += step; 
     if (currentHue > 360) 
     { 
      currentHue -= 360; 
     } 

     nextHSL = new ColorHSL(currentHue, baseHSL.Saturation, aseHSL.Luminance); 
     nextRGB = new ColorRGB(); 
     hslToRgb(nextRGB, nextHSL); 

     colors.push(nextRGB.getUintFromRGB()); 
    } 

    if (pNonAdjacentSimilarColor == true && 
     pNbColors > 2) 
    { 
     var holder:uint = 0; 
     var j:int; 

     for (i = 0, j = pNbColors/2; i < pNbColors/2; i += 2, j += 2) 
     { 
      holder = colors[i]; 
      colors[i] = colors[j]; 
      colors[j] = holder; 
     } 
    } 

    return colors; 
} 

Esto produce la salida lado derecho:

Comparison Image

clase ColorHSL:

final public class ColorHSL 
{ 
    private var _hue:Number; // 0.0 .. 359.99999 

    private var _sat:Number; // 0.0 .. 100.0 

    private var _lum:Number; // 0.0 .. 100.0 

    public function ColorHSL(
     hue:Number = 0, 
     sat:Number = 0, 
     lum:Number = 0) 
    { 
     _hue = hue; 
     _sat = sat; 
     _lum = lum; 
    } 

    [Bindable]public function get Hue():Number 
    { 
     return _hue; 
    } 

    public function set Hue(value:Number):void 
    { 
     if (value > 360) 
     { 
      _hue = value % 360; 
     } // remember, hue is modulo 360 
     else if (value < 0) 
     { 
      _hue = 0; 
     } 
     else 
     { 
      _hue = value; 
     } 
    } 

    [Bindable]public function get Saturation():Number 
    { 
     return _sat; 
    } 

    public function set Saturation(value:Number):void 
    { 
     if (value > 100.0) 
     { 
      _sat = 100.0; 
     } 
     else if (value < 0) 
     { 
      _sat = 0; 
     } 
     else 
     { 
      _sat = value; 
     } 
    } 

    [Bindable]public function get Luminance():Number 
    { 
     return _lum; 
    } 

    public function set Luminance(value:Number):void 
    { 
     if (value > 100.0) 
     { 
      _lum = 100.0; 
     } 
     else if (value < 0) 
     { 
      _lum = 0; 
     } 
     else 
     { 
      _lum = value; 
     } 
    } 
} 

ColorRGB clase:

final public class ColorRGB 
{ 
    private var _red:uint; 
    private var _grn:uint; 
    private var _blu:uint; 
    private var _rgb:uint;  // composite form: 0xRRGGBB or #RRGGBB 

    public function ColorRGB(red:uint = 0, grn:uint = 0, blu:uint = 0) 
    { 
     setRGB(red, grn, blu); 
    } 

    [Bindable]public function get red():uint 
    { 
     return _red; 
    } 

    public function set red(value:uint):void 
    { 
     _red = (value & 0xFF); 
     updateRGB(); 
    } 

    [Bindable]public function get grn():uint 
    { 
     return _grn; 
    } 

    public function set grn(value:uint):void 
    { 
     _grn = (value & 0xFF); 
     updateRGB(); 
    } 

    [Bindable]public function get blu():uint 
    { 
     return _blu; 
    } 

    public function set blu(value:uint):void 
    { 
     _blu = (value & 0xFF); 
     updateRGB(); 
    } 

    [Bindable]public function get rgb():uint 
    { 
     return _rgb; 
    } 

    public function set rgb(value:uint):void 
    { 
     _rgb = value; 
     _red = (value >> 16) & 0xFF; 
     _grn = (value >> 8) & 0xFF; 
     _blu = value  & 0xFF; 
    } 

    public function setRGB(red:uint, grn:uint, blu:uint):void 
    { 
     this.red = red; 
     this.grn = grn; 
     this.blu = blu; 
    } 

    public function setRGBFromUint(pValue:uint):void 
    { 
     setRGB(((pValue >> 16) & 0xFF), ((pValue >> 8) & 0xFF), (pValue & 0xFF)); 
    } 

    public function getUintFromRGB():uint 
    { 
     return ((red << 16) | (grn << 8) | blu); 
    } 

    private function updateRGB():void 
    { 
     _rgb = (_red << 16) + (_grn << 8) + blu; 
    } 
} 

clase ColorUtils:

final public class ColorUtils 
{ 
    public static function HSV2RGB(hue:Number, sat:Number, val:Number):uint 
    { 
     var red:Number = 0; 
     var grn:Number = 0; 
     var blu:Number = 0; 
     var i:Number; 
     var f:Number; 
     var p:Number; 
     var q:Number; 
     var t:Number; 
     hue%=360; 
     sat/=100; 
     val/=100; 
     hue/=60; 
     i = Math.floor(hue); 
     f = hue-i; 
     p = val*(1-sat); 
     q = val*(1-(sat*f)); 
     t = val*(1-(sat*(1-f))); 
     if (i==0) 
     { 
      red=val; 
      grn=t; 
      blu=p; 
     } 
     else if (i==1) 
     { 
      red=q; 
      grn=val; 
      blu=p; 
     } 
     else if (i==2) 
     { 
      red=p; 
      grn=val; 
      blu=t; 
     } 
     else if (i==3) 
     { 
      red=p; 
      grn=q; 
      blu=val; 
     } 
     else if (i==4) 
     { 
      red=t; 
      grn=p; 
      blu=val; 
     } 
     else if (i==5) 
     { 
      red=val; 
      grn=p; 
      blu=q; 
     } 
     red = Math.floor(red*255); 
     grn = Math.floor(grn*255); 
     blu = Math.floor(blu*255); 

     return (red<<16) | (grn << 8) | (blu); 
    } 

    // 
    public static function RGB2HSV(pColor:uint):Object 
    { 
     var red:uint = (pColor >> 16) & 0xff; 
     var grn:uint = (pColor >> 8) & 0xff; 
     var blu:uint = pColor & 0xff; 

     var x:Number; 
     var val:Number; 
     var f:Number; 
     var i:Number; 
     var hue:Number; 
     var sat:Number; 
     red/=255; 
     grn/=255; 
     blu/=255; 
     x = Math.min(Math.min(red, grn), blu); 
     val = Math.max(Math.max(red, grn), blu); 
     if (x==val){ 
      return({h:undefined, s:0, v:val*100}); 
     } 
     f = (red == x) ? grn-blu : ((grn == x) ? blu-red : red-grn); 
     i = (red == x) ? 3 : ((grn == x) ? 5 : 1); 
     hue = Math.floor((i-f/(val-x))*60)%360; 
     sat = Math.floor(((val-x)/val)*100); 
     val = Math.floor(val*100); 
     return({h:hue, s:sat, v:val}); 
    } 

    /** 
    * Generates an array of pNbColors colors (uint) 
    * The colors are generated to fill a pie chart (meaning that they circle back to the starting color) 
    * @param pNbColors The number of colors to generate (ex: Number of slices in the pie chart) 
    * @param pNonAdjacentSimilarColor Should the colors stay Adjacent or not ? 
    */ 
    public static function ColorArrayGenerator(
     pNbColors:int, 
     pNonAdjacentSimilarColor:Boolean = false):Array 
    { 
     // Based on http://www.flexspectrum.com/?p=10 

     var colors:Array = []; 
     var baseRGB:ColorRGB = new ColorRGB(); 
     baseRGB.setRGBFromUint(0x8A56E2); 

     var baseHSL:ColorHSL = new ColorHSL(); 
     rgbToHsl(baseHSL, baseRGB); 

     var currentHue:Number = baseHSL.Hue; 

     colors.push(baseRGB.getUintFromRGB()); 

     var step:Number = (360.0/pNbColors); 
     var nextHSL:ColorHSL; 
     var nextRGB:ColorRGB; 
     var i:int; 

     for (i = 1; i < pNbColors; i++) 
     { 
      currentHue += step; 

      if (currentHue > 360) 
      { 
       currentHue -= 360; 
      } 

      nextHSL = new ColorHSL(currentHue, baseHSL.Saturation, baseHSL.Luminance); 
      nextRGB = new ColorRGB(); 
      hslToRgb(nextRGB, nextHSL); 

      colors.push(nextRGB.getUintFromRGB()); 
     } 

     if (pNonAdjacentSimilarColor == true && 
      pNbColors > 2) 
     { 
      var holder:uint = 0; 
      var j:int; 

      for (i = 0, j = pNbColors/2; i < pNbColors/2; i += 2, j += 2) 
      { 
       holder = colors[i]; 
       colors[i] = colors[j]; 
       colors[j] = holder; 
      } 
     } 

     return colors; 
    } 

    static public function rgbToHsl(hsl:ColorHSL, rgb:ColorRGB):void 
    { 
     var h:Number = 0; 
     var s:Number = 0; 
     var l:Number = 0; 

     // Normalizes incoming RGB values. 
     // 
     var dRed:Number = (Number)(rgb.red/255.0); 
     var dGrn:Number = (Number)(rgb.grn/255.0); 
     var dBlu:Number = (Number)(rgb.blu/255.0); 

     var dMax:Number = Math.max(dRed, Math.max(dGrn, dBlu)); 
     var dMin:Number = Math.min(dRed, Math.min(dGrn, dBlu)); 

     //------------------------- 
     // hue 
     // 
     if (dMax == dMin) 
     { 
      h = 0;     // undefined 
     } 
     else if (dMax == dRed && dGrn >= dBlu) 
     { 
      h = 60.0 * (dGrn - dBlu)/(dMax - dMin); 
     } 
     else if (dMax == dRed && dGrn < dBlu) 
     { 
      h = 60.0 * (dGrn - dBlu)/(dMax - dMin) + 360.0; 
     } 
     else if (dMax == dGrn) 
     { 
      h = 60.0 * (dBlu - dRed)/(dMax-dMin) + 120.0; 
     } 
     else if (dMax == dBlu) 
     { 
      h = 60.0 * (dRed - dGrn)/(dMax - dMin) + 240.0; 
     } 

     //------------------------- 
     // luminance 
     // 
     l = (dMax + dMin)/2.0; 

     //------------------------- 
     // saturation 
     // 
     if (l == 0 || dMax == dMin) 
     { 
      s = 0; 
     } 
     else if (0 < l && l <= 0.5) 
     { 
      s = (dMax - dMin)/(dMax + dMin); 
     } 
     else if (l>0.5) 
     { 
      s = (dMax - dMin)/(2 - (dMax + dMin)); //(dMax-dMin > 0)? 
     } 

     hsl.Hue = h; 
     hsl.Luminance = l; 
     hsl.Saturation = s; 

    } // rgbToHsl 

    //--------------------------------------- 
    // Convert the input RGB values to the corresponding HSL values. 
    // 
    static public function hslToRgb(rgb:ColorRGB, hsl:ColorHSL):void 
    { 
     if (hsl.Saturation == 0) 
     { 
      // Achromatic color case, luminance only. 
      // 
      var lumScaled:int = (int)(hsl.Luminance * 255.0); 
      rgb.setRGB(lumScaled, lumScaled, lumScaled); 
      return; 
     } 

     // Chromatic case... 
     // 
     var dQ:Number = (hsl.Luminance < 0.5) ? (hsl.Luminance * (1.0 + hsl.Saturation)): ((hsl.Luminance + hsl.Saturation) - (hsl.Luminance * hsl.Saturation)); 
     var dP:Number = (2.0 * hsl.Luminance) - dQ; 

     var dHueAng:Number = hsl.Hue/360.0; 

     var dFactor:Number = 1.0/3.0; 

     var adT:Array = []; 

     adT[0] = dHueAng + dFactor;    // Tr 
     adT[1] = dHueAng;      // Tg 
     adT[2] = dHueAng - dFactor;    // Tb 

     for (var i:int = 0; i < 3; i++) 
     { 
      if (adT[i] < 0) 
      { 
       adT[i] += 1.0; 
      } 

      if (adT[i] > 1) 
      { 
       adT[i] -= 1.0; 
      } 

      if ((adT[i] * 6) < 1) 
      { 
       adT[i] = dP + ((dQ - dP) * 6.0 * adT[i]); 
      } 
      else if ((adT[i] * 2.0) < 1)  // (1.0/6.0) <= adT[i] && adT[i] < 0.5 
      { 
       adT[i] = dQ; 
      } 
      else if ((adT[i] * 3.0) < 2)  // 0.5 <= adT[i] && adT[i] < (2.0/3.0) 
      { 
       adT[i] = dP + (dQ-dP) * ((2.0/3.0) - adT[i]) * 6.0; 
      } 
      else 
      { 
       adT[i] = dP; 
      } 
     } 

     rgb.setRGB(adT[0] * 255.0, adT[1] * 255.0, adT[2] * 255.0); 

    } // hslToRgb 

    //--------------------------------------- 
    // Adjust the luminance value by the specified factor. 
    // 
    static public function adjustRgbLuminance(rgb:ColorRGB, factor:Number):void 
    { 
     var hsl:ColorHSL = new ColorHSL(); 

     rgbToHsl(hsl, rgb); 

     hsl.Luminance *= factor; 

     if (hsl.Luminance < 0.0) 
     { 
      hsl.Luminance = 0.0; 
     } 

     if (hsl.Luminance > 1.0) 
     { 
      hsl.Luminance = 1.0; 
     } 

     hslToRgb(rgb, hsl); 
    } 

    //--------------------------------------- 
    // 
    static public function uintTo2DigitHex(value:uint):String 
    { 
     var str:String = value.toString(16).toUpperCase(); 

     if (1 == str.length) 
     { 
      str = "0" + str; 
     } 

     return str; 
    } 

    //--------------------------------------- 
    // 
    static public function uintTo6DigitHex(value:uint):String 
    { 
     var str:String = value.toString(16).toUpperCase(); 

     if (1 == str.length) {return "00000" + str;} 
     if (2 == str.length) {return "0000" + str;} 
     if (3 == str.length) {return "000" + str;} 
     if (4 == str.length) {return "00" + str;} 
     if (5 == str.length) {return "0" + str;} 

     return str; 
    } 
} 
5

general

la conversión de RGB a HSV y luego ajustar el matiz (como answered here) crea un brillo percibido inconsistente.El amarillo/verde son notablemente más ligero que el azul/púrpura:

Inconsistent

Un resultado similar sin tal variación es posible:

Consistent

Algoritmo

El algoritmo, sin embargo, es mucho más complejo:

  1. Convierta códigos HTML hexadecimales a valores RGB nominales (divida componentes por 255).
  2. Convierte valores RGB a XYZ colour space; use D65 reference white sRGB working space.
  3. Convertir de XYZ a Lab colour space.
  4. Convertir de L a b a LCH colour space.
  5. Calcular tono de color de cuña en el espacio de color de LCH:
    (360.0 div $wedges) * $wedge
  6. Vuelva a calcular el nuevo tono en radianes.
  7. Conversión de nuevo de LCH a Lab colour space usando nuevo matiz.
  8. Convertir de L a b a XYZ colour space.
  9. Convertir de XYZ a sRGB colour space.
  10. Multiplicar los valores RGB de 255.

Implementación

He aquí un ejemplo de implementación de XSLT 1.0:

<?xml version="1.0"?> 
<!-- 
| The MIT License 
| 
| Copyright 2014 White Magic Software, Inc. 
| 
| Permission is hereby granted, free of charge, to any person 
| obtaining a copy of this software and associated documentation 
| files (the "Software"), to deal in the Software without 
| restriction, including without limitation the rights to use, 
| copy, modify, merge, publish, distribute, sublicense, and/or 
| sell copies of the Software, and to permit persons to whom the 
| Software is furnished to do so, subject to the following 
| conditions: 
| 
| The above copyright notice and this permission notice shall be 
| included in all copies or substantial portions of the Software. 
| 
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
| OTHER DEALINGS IN THE SOFTWARE. 
+--> 
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<!-- Reference white (X, Y, and Z components) --> 
<xsl:variable name="X_r" select="0.950456"/> 
<xsl:variable name="Y_r" select="1.000000"/> 
<xsl:variable name="Z_r" select="1.088754"/> 
<xsl:variable name="LAB_EPSILON" select="216.0 div 24389.0"/> 
<xsl:variable name="LAB_K" select="24389.0 div 27.0"/> 

<!-- Pie wedge colours based on this hue. --> 
<xsl:variable name="base_colour" select="'46A5E5'"/> 

<!-- Pie wedge stroke colour. --> 
<xsl:variable name="stroke_colour" select="'white'"/> 

<!-- 
| Creates a colour for a particular pie wedge. 
| 
| http://en.wikipedia.org/wiki/HSL_and_HSV 
+--> 
<xsl:template name="fill"> 
    <!-- Current wedge number for generating a colour. --> 
    <xsl:param name="wedge"/> 
    <!-- Total number of wedges in the pie. --> 
    <xsl:param name="wedges"/> 
    <!-- RGB colour in hexadecimal. --> 
    <xsl:param name="colour"/> 

    <!-- Derive the colour decimal values from $colour's HEX code. --> 
    <xsl:variable name="r"> 
    <xsl:call-template name="hex2dec"> 
     <xsl:with-param name="hex" 
     select="substring($colour, 1, 2)"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="g"> 
    <xsl:call-template name="hex2dec"> 
     <xsl:with-param name="hex" 
     select="substring($colour, 3, 2)"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="b"> 
    <xsl:call-template name="hex2dec"> 
     <xsl:with-param name="hex" 
     select="substring($colour, 5, 2)"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <!-- 
    | Convert RGB to XYZ, using nominal range for RGB. 
    | http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html 
    +--> 
    <xsl:variable name="r_n" select="$r div 255" /> 
    <xsl:variable name="g_n" select="$g div 255" /> 
    <xsl:variable name="b_n" select="$b div 255" /> 

    <!-- 
    | Assume colours are in sRGB. 
    | http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 
    --> 
    <xsl:variable name="x" 
    select=".4124564 * $r_n + .3575761 * $g_n + .1804375 * $b_n"/> 
    <xsl:variable name="y" 
    select=".2126729 * $r_n + .7151522 * $g_n + .0721750 * $b_n"/> 
    <xsl:variable name="z" 
    select=".0193339 * $r_n + .1191920 * $g_n + .9503041 * $b_n"/> 

    <!-- 
    | Convert XYZ to L*a*b. 
    | http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html 
    +--> 
    <xsl:variable name="if_x"> 
    <xsl:call-template name="lab_f"> 
     <xsl:with-param name="xyz_n" select="$x div $X_r"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="if_y"> 
    <xsl:call-template name="lab_f"> 
     <xsl:with-param name="xyz_n" select="$y div $Y_r"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="if_z"> 
    <xsl:call-template name="lab_f"> 
     <xsl:with-param name="xyz_n" select="$z div $Z_r"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="lab_l" select="(116.0 * $if_y) - 16.0"/> 
    <xsl:variable name="lab_a" select="500.0 * ($if_x - $if_y)"/> 
    <xsl:variable name="lab_b" select="200.0 * ($if_y - $if_z)"/> 

    <!-- 
    | Convert L*a*b to LCH. 
    | http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html 
    +--> 
    <xsl:variable name="lch_l" select="$lab_l"/> 

    <xsl:variable name="lch_c"> 
    <xsl:call-template name="sqrt"> 
     <xsl:with-param name="n" select="($lab_a * $lab_a) + ($lab_b * $lab_b)"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="lch_h"> 
    <xsl:call-template name="atan2"> 
     <xsl:with-param name="x" select="$lab_b"/> 
     <xsl:with-param name="y" select="$lab_a"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <!-- 
    | Prevent similar adjacent colours. 
    | http://math.stackexchange.com/a/936767/7932 
    +--> 
    <xsl:variable name="wi" select="$wedge"/> 
    <xsl:variable name="wt" select="$wedges"/> 
    <xsl:variable name="w"> 
    <xsl:choose> 
     <xsl:when test="$wt &gt; 5"> 
     <xsl:variable name="weven" select="(($wi+4) mod ($wt + $wt mod 2))"/> 
     <xsl:value-of 
      select="$weven * (1-($wi mod 2)) + ($wi mod 2 * $wi)"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="$wedge"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 
    <!-- lch_l, lch_c, and lch_h are now set; rotate the hue. --> 
    <xsl:variable name="lch_wedge_h" select="(360.0 div $wedges) * $wedge"/> 

    <!-- 
    | Convert wedge's hue-adjusted LCH to L*a*b. 
    | http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html 
    +--> 
    <xsl:variable name="lab_sin_h"> 
    <xsl:call-template name="sine"> 
     <xsl:with-param name="degrees" select="$lch_wedge_h"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="lab_cos_h"> 
    <xsl:call-template name="cosine"> 
     <xsl:with-param name="degrees" select="$lch_wedge_h"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="final_lab_l" select="$lch_l"/> 
    <xsl:variable name="final_lab_a" select="$lch_c * $lab_cos_h"/> 
    <xsl:variable name="final_lab_b" select="$lch_c * $lab_sin_h"/> 

    <!-- 
    | Convert L*a*b to XYZ. 
    | http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html 
    +--> 
    <xsl:variable name="of_y" select="($final_lab_l + 16.0) div 116.0"/> 
    <xsl:variable name="of_x" select="($final_lab_a div 500.0) + $of_y"/> 
    <xsl:variable name="of_z" select="$of_y - ($final_lab_b div 200.0)"/> 

    <xsl:variable name="of_x_pow"> 
    <xsl:call-template name="power"> 
     <xsl:with-param name="base" select="$of_x"/> 
     <xsl:with-param name="exponent" select="3"/> 
    </xsl:call-template> 
    </xsl:variable> 
    <xsl:variable name="of_z_pow"> 
    <xsl:call-template name="power"> 
     <xsl:with-param name="base" select="$of_z"/> 
     <xsl:with-param name="exponent" select="3"/> 
    </xsl:call-template> 
    </xsl:variable> 

    <xsl:variable name="ox_r"> 
    <xsl:choose> 
     <xsl:when test="$of_x_pow &gt; $LAB_EPSILON"> 
     <xsl:value-of select="$of_x_pow"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="((116.0 * $of_x) - 16.0) div $LAB_K"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 
    <xsl:variable name="oy_r"> 
    <xsl:choose> 
     <xsl:when test="$final_lab_l &gt; ($LAB_K * $LAB_EPSILON)"> 
     <xsl:call-template name="power"> 
      <xsl:with-param name="base" 
      select="($final_lab_l + 16.0) div 116.0"/> 
      <xsl:with-param name="exponent" 
      select="3"/> 
     </xsl:call-template> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="$final_lab_l div $LAB_K"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 
    <xsl:variable name="oz_r"> 
    <xsl:choose> 
     <xsl:when test="$of_z_pow &gt; $LAB_EPSILON"> 
     <xsl:value-of select="$of_z_pow"/> 
     </xsl:when> 
     <xsl:otherwise> 
     <xsl:value-of select="((116.0 * $of_z) - 16.0) div $LAB_K"/> 
     </xsl:otherwise> 
    </xsl:choose> 
    </xsl:variable> 

    <xsl:variable name="X" select="$ox_r * $X_r"/> 
    <xsl:variable name="Y" select="$oy_r * $Y_r"/> 
    <xsl:variable name="Z" select="$oz_r * $Z_r"/> 

    <!-- 
    | Convert XYZ to sRGB. 
    | http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 
    +--> 
    <xsl:variable name="R" 
    select="3.2404542 * $X + -1.5371385 * $Y + -0.4985314 * $Z"/> 
    <xsl:variable name="G" 
    select="-0.9692660 * $X + 1.8760108 * $Y + 0.0415560 * $Z"/> 
    <xsl:variable name="B" 
    select="0.0556434 * $X + -0.2040259 * $Y + 1.0572252 * $Z"/> 

    <!-- Round the result. --> 
    <xsl:variable name="R_r" select="round($R * 255)"/> 
    <xsl:variable name="G_r" select="round($G * 255)"/> 
    <xsl:variable name="B_r" select="round($B * 255)"/> 

    <xsl:text>rgb(</xsl:text> 
    <xsl:value-of select="concat($R_r, ',', $G_r, ',', $B_r)"/> 
    <xsl:text>)</xsl:text> 
</xsl:template> 

<xsl:template name="lab_f"> 
    <xsl:param name="xyz_n"/> 

    <xsl:choose> 
    <xsl:when test="$xyz_n &gt; $LAB_EPSILON"> 
     <xsl:call-template name="nthroot"> 
     <xsl:with-param name="index" select="3"/> 
     <xsl:with-param name="radicand" select="$xyz_n"/> 
     </xsl:call-template> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:value-of select="($LAB_K * $xyz_n + 16.0) div 116.0" /> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

<!-- Converts a two-digit hexadecimal number to decimal. --> 
<xsl:template name="hex2dec"> 
    <xsl:param name="hex"/> 

    <xsl:variable name="digits" select="'ABCDEF'"/> 
    <xsl:variable name="X" select="substring($hex, 1, 1)"/> 
    <xsl:variable name="Y" select="substring($hex, 2, 1)"/> 
    <xsl:variable name="Xval" 
    select="string-length(substring-before($digits,$X))"/> 
    <xsl:variable name="Yval" 
    select="string-length(substring-before($digits,$Y))"/> 
    <xsl:value-of select="16 * $Xval + $Yval"/> 
</xsl:template> 

</xsl:stylesheet> 

Los trigonométricas, raíz, y funciones matemáticas misceláneos se dejan como ejercicio para el lector. Además, nadie en su sano juicio querría codificar todo esto en XSLT 1.0. XSLT 2.0, por otro lado, tiene un implementation here.

Recursos

Más información:

Cuestiones relacionadas