¿Cómo generar automáticamente N colores "distintos"?


194

Escribí los dos métodos a continuación para seleccionar automáticamente N colores distintos. Funciona definiendo una función lineal por partes en el cubo RGB. El beneficio de esto es que también puede obtener una escala progresiva si eso es lo que desea, pero cuando N crece, los colores pueden comenzar a parecerse. También puedo imaginar subdividir uniformemente el cubo RGB en una red y luego dibujar puntos. ¿Alguien sabe algún otro método? Estoy descartando definir una lista y luego simplemente recorrerla. También debería decir que generalmente no me importa si chocan o no se ven bien, solo tienen que ser visualmente distintos.

public static List<Color> pick(int num) {
    List<Color> colors = new ArrayList<Color>();
    if (num < 2)
        return colors;
    float dx = 1.0f / (float) (num - 1);
    for (int i = 0; i < num; i++) {
        colors.add(get(i * dx));
    }
    return colors;
}

public static Color get(float x) {
    float r = 0.0f;
    float g = 0.0f;
    float b = 1.0f;
    if (x >= 0.0f && x < 0.2f) {
        x = x / 0.2f;
        r = 0.0f;
        g = x;
        b = 1.0f;
    } else if (x >= 0.2f && x < 0.4f) {
        x = (x - 0.2f) / 0.2f;
        r = 0.0f;
        g = 1.0f;
        b = 1.0f - x;
    } else if (x >= 0.4f && x < 0.6f) {
        x = (x - 0.4f) / 0.2f;
        r = x;
        g = 1.0f;
        b = 0.0f;
    } else if (x >= 0.6f && x < 0.8f) {
        x = (x - 0.6f) / 0.2f;
        r = 1.0f;
        g = 1.0f - x;
        b = 0.0f;
    } else if (x >= 0.8f && x <= 1.0f) {
        x = (x - 0.8f) / 0.2f;
        r = 1.0f;
        g = 0.0f;
        b = x;
    }
    return new Color(r, g, b);
}

44
Los programadores muy relevantes preguntan con respuestas interesantes: " Generación de esquemas de color: teoría y algoritmos ".
Alexey Popkov

2
La percepción del color humano no es lineal, desafortunadamente. Es posible que también deba tener en cuenta el cambio de Bezold-Brücke si utiliza intensidades variables. También hay buena información aquí: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

Respuestas:


80

Puedes usar el modelo de color HSL para crear sus colores.

Si todo lo que desea son tonos diferentes (probablemente) y ligeras variaciones en la luminosidad o la saturación, puede distribuir los tonos de la siguiente manera:

// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)

for(i = 0; i < 360; i += 360 / num_colors) {
    HSLColor c;
    c.hue = i;
    c.saturation = 90 + randf() * 10;
    c.lightness = 50 + randf() * 10;

    addColor(c);
}

2
Esta técnica es inteligente. Apuesto a que obtendrá más resultados estéticos que los míos.
mqp

45
Esto supone que los valores de tono equidistantes son igualmente perceptualmente diferentes. Incluso descontando varias formas de daltonismo, esto no es cierto para la mayoría de las personas: la diferencia entre 120 ° (verde) y 135 ° (muy ligeramente verde menta) es imperceptible, mientras que la diferencia entre 30 ° (naranja) y 45 ° (melocotón) Es bastante obvio. Necesita un espacio no lineal a lo largo del tono para obtener mejores resultados.
Phrogz

18
@mquander - No es inteligente en absoluto. No hay nada que evite que este algoritmo elija accidentalmente dos colores casi idénticos. Mi respuesta es mejor, y la respuesta de ohadsc es mucho mejor.
Rocketmagnet

1
Esto está mal por los motivos ya mencionados, pero también porque no está seleccionando de manera uniforme .
sam hocevar

3
@strager cuál es el valor esperado de randf ()
Killrawr

242

Esta pregunta aparece en bastantes discusiones SO:

Se proponen diferentes soluciones, pero ninguna es óptima. Afortunadamente, la ciencia viene al rescate

N arbitraria

Los últimos 2 serán gratuitos a través de la mayoría de las bibliotecas universitarias / proxies.

N es finito y relativamente pequeño

En este caso, uno podría buscar una solución de lista. Un artículo muy interesante sobre el tema está disponible gratuitamente:

Hay varias listas de colores a considerar:

  • La lista de Boynton de 11 colores que casi nunca se confunden (disponible en el primer artículo de la sección anterior)
  • Los 22 colores de máximo contraste de Kelly (disponibles en el documento anterior)

También me encontré con esta paleta por un estudiante del MIT. Por último, los siguientes enlaces pueden ser útiles para convertir entre diferentes sistemas de color / coordenadas (algunos colores en los artículos no están especificados en RGB, por ejemplo):

Para la lista de Kelly y Boynton, ya hice la conversión a RGB (con la excepción de blanco y negro, que debería ser obvio). Algún código C #:

public static ReadOnlyCollection<Color> KellysMaxContrastSet
{
    get { return _kellysMaxContrastSet.AsReadOnly(); }
}

private static readonly List<Color> _kellysMaxContrastSet = new List<Color>
{
    UIntToColor(0xFFFFB300), //Vivid Yellow
    UIntToColor(0xFF803E75), //Strong Purple
    UIntToColor(0xFFFF6800), //Vivid Orange
    UIntToColor(0xFFA6BDD7), //Very Light Blue
    UIntToColor(0xFFC10020), //Vivid Red
    UIntToColor(0xFFCEA262), //Grayish Yellow
    UIntToColor(0xFF817066), //Medium Gray

    //The following will not be good for people with defective color vision
    UIntToColor(0xFF007D34), //Vivid Green
    UIntToColor(0xFFF6768E), //Strong Purplish Pink
    UIntToColor(0xFF00538A), //Strong Blue
    UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink
    UIntToColor(0xFF53377A), //Strong Violet
    UIntToColor(0xFFFF8E00), //Vivid Orange Yellow
    UIntToColor(0xFFB32851), //Strong Purplish Red
    UIntToColor(0xFFF4C800), //Vivid Greenish Yellow
    UIntToColor(0xFF7F180D), //Strong Reddish Brown
    UIntToColor(0xFF93AA00), //Vivid Yellowish Green
    UIntToColor(0xFF593315), //Deep Yellowish Brown
    UIntToColor(0xFFF13A13), //Vivid Reddish Orange
    UIntToColor(0xFF232C16), //Dark Olive Green
};

public static ReadOnlyCollection<Color> BoyntonOptimized
{
    get { return _boyntonOptimized.AsReadOnly(); }
}

private static readonly List<Color> _boyntonOptimized = new List<Color>
{
    Color.FromArgb(0, 0, 255),      //Blue
    Color.FromArgb(255, 0, 0),      //Red
    Color.FromArgb(0, 255, 0),      //Green
    Color.FromArgb(255, 255, 0),    //Yellow
    Color.FromArgb(255, 0, 255),    //Magenta
    Color.FromArgb(255, 128, 128),  //Pink
    Color.FromArgb(128, 128, 128),  //Gray
    Color.FromArgb(128, 0, 0),      //Brown
    Color.FromArgb(255, 128, 0),    //Orange
};

static public Color UIntToColor(uint color)
{
    var a = (byte)(color >> 24);
    var r = (byte)(color >> 16);
    var g = (byte)(color >> 8);
    var b = (byte)(color >> 0);
    return Color.FromArgb(a, r, g, b);
}

Y aquí están los valores RGB en representaciones hexadecimales y de 8 bits por canal:

kelly_colors_hex = [
    0xFFB300, # Vivid Yellow
    0x803E75, # Strong Purple
    0xFF6800, # Vivid Orange
    0xA6BDD7, # Very Light Blue
    0xC10020, # Vivid Red
    0xCEA262, # Grayish Yellow
    0x817066, # Medium Gray

    # The following don't work well for people with defective color vision
    0x007D34, # Vivid Green
    0xF6768E, # Strong Purplish Pink
    0x00538A, # Strong Blue
    0xFF7A5C, # Strong Yellowish Pink
    0x53377A, # Strong Violet
    0xFF8E00, # Vivid Orange Yellow
    0xB32851, # Strong Purplish Red
    0xF4C800, # Vivid Greenish Yellow
    0x7F180D, # Strong Reddish Brown
    0x93AA00, # Vivid Yellowish Green
    0x593315, # Deep Yellowish Brown
    0xF13A13, # Vivid Reddish Orange
    0x232C16, # Dark Olive Green
    ]

kelly_colors = dict(vivid_yellow=(255, 179, 0),
                    strong_purple=(128, 62, 117),
                    vivid_orange=(255, 104, 0),
                    very_light_blue=(166, 189, 215),
                    vivid_red=(193, 0, 32),
                    grayish_yellow=(206, 162, 98),
                    medium_gray=(129, 112, 102),

                    # these aren't good for people with defective color vision:
                    vivid_green=(0, 125, 52),
                    strong_purplish_pink=(246, 118, 142),
                    strong_blue=(0, 83, 138),
                    strong_yellowish_pink=(255, 122, 92),
                    strong_violet=(83, 55, 122),
                    vivid_orange_yellow=(255, 142, 0),
                    strong_purplish_red=(179, 40, 81),
                    vivid_greenish_yellow=(244, 200, 0),
                    strong_reddish_brown=(127, 24, 13),
                    vivid_yellowish_green=(147, 170, 0),
                    deep_yellowish_brown=(89, 51, 21),
                    vivid_reddish_orange=(241, 58, 19),
                    dark_olive_green=(35, 44, 22))

Para todos los desarrolladores de Java, aquí están los colores JavaFX:

// Don't forget to import javafx.scene.paint.Color;

private static final Color[] KELLY_COLORS = {
    Color.web("0xFFB300"),    // Vivid Yellow
    Color.web("0x803E75"),    // Strong Purple
    Color.web("0xFF6800"),    // Vivid Orange
    Color.web("0xA6BDD7"),    // Very Light Blue
    Color.web("0xC10020"),    // Vivid Red
    Color.web("0xCEA262"),    // Grayish Yellow
    Color.web("0x817066"),    // Medium Gray

    Color.web("0x007D34"),    // Vivid Green
    Color.web("0xF6768E"),    // Strong Purplish Pink
    Color.web("0x00538A"),    // Strong Blue
    Color.web("0xFF7A5C"),    // Strong Yellowish Pink
    Color.web("0x53377A"),    // Strong Violet
    Color.web("0xFF8E00"),    // Vivid Orange Yellow
    Color.web("0xB32851"),    // Strong Purplish Red
    Color.web("0xF4C800"),    // Vivid Greenish Yellow
    Color.web("0x7F180D"),    // Strong Reddish Brown
    Color.web("0x93AA00"),    // Vivid Yellowish Green
    Color.web("0x593315"),    // Deep Yellowish Brown
    Color.web("0xF13A13"),    // Vivid Reddish Orange
    Color.web("0x232C16"),    // Dark Olive Green
};

Los siguientes son los colores de Kelly sin clasificar según el orden anterior.

colores de Kelly sin clasificar

Los siguientes son los colores de Kelly ordenados según los tonos (tenga en cuenta que algunos amarillos no son muy contrastantes)

 colores de Kelly ordenados


+1 ¡Muchas gracias por esta gran respuesta! Por cierto, el enlace colour-journal.org/2010/5/10 está muerto, este artículo todavía está disponible en web.archive.org .
Alexey Popkov


16
Gran respuesta, gracias! Me he tomado la libertad de convertir estos dos conjuntos de colores en un conveniente jsfiddle donde puedes ver los colores en acción.
David Mills

1
Acabo de notar que solo hay 20 y 9 colores en esas listas, respectivamente. Supongo que es porque se omiten el blanco y el negro.
David Mills

2
¿Ya está disponible el servicio web?
Janus Troelsen

38

Como la respuesta de Uri Cohen, pero en cambio es un generador. Comenzará usando colores muy separados. Determinista.

Muestra, izquierda colores primero: muestra

#!/usr/bin/env python3.5
from typing import Iterable, Tuple
import colorsys
import itertools
from fractions import Fraction
from pprint import pprint

def zenos_dichotomy() -> Iterable[Fraction]:
    """
    http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7
    """
    for k in itertools.count():
        yield Fraction(1,2**k)

def fracs() -> Iterable[Fraction]:
    """
    [Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...]
    [0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...]
    """
    yield Fraction(0)
    for k in zenos_dichotomy():
        i = k.denominator # [1,2,4,8,16,...]
        for j in range(1,i,2):
            yield Fraction(j,i)

# can be used for the v in hsv to map linear values 0..1 to something that looks equidistant
# bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5)

HSVTuple = Tuple[Fraction, Fraction, Fraction]
RGBTuple = Tuple[float, float, float]

def hue_to_tones(h: Fraction) -> Iterable[HSVTuple]:
    for s in [Fraction(6,10)]: # optionally use range
        for v in [Fraction(8,10),Fraction(5,10)]: # could use range too
            yield (h, s, v) # use bias for v here if you use range

def hsv_to_rgb(x: HSVTuple) -> RGBTuple:
    return colorsys.hsv_to_rgb(*map(float, x))

flatten = itertools.chain.from_iterable

def hsvs() -> Iterable[HSVTuple]:
    return flatten(map(hue_to_tones, fracs()))

def rgbs() -> Iterable[RGBTuple]:
    return map(hsv_to_rgb, hsvs())

def rgb_to_css(x: RGBTuple) -> str:
    uint8tuple = map(lambda y: int(y*255), x)
    return "rgb({},{},{})".format(*uint8tuple)

def css_colors() -> Iterable[str]:
    return map(rgb_to_css, rgbs())

if __name__ == "__main__":
    # sample 100 colors in css format
    sample_colors = list(itertools.islice(css_colors(), 100))
    pprint(sample_colors)

+1 para la muestra, muy agradable, y muestra que el esquema también es atractivo. Las otras respuestas aquí mejorarían haciendo lo mismo y luego podrían compararse fácilmente.
Don Hatch

3
La cantidad de lambdas es demasiado alta. La lambda es fuerte con este.
Gyfis

Se ve genial, pero se atasca cuando intento ejecutarlo en 2.7
Elad Weiss

33

Aquí hay una idea. Imagine un cilindro HSV

Defina los límites superior e inferior que desea para Brillo y Saturación. Esto define un anillo cuadrado de sección transversal dentro del espacio.

Ahora, dispersa N puntos al azar dentro de este espacio.

Luego aplique un algoritmo de repulsión iterativa sobre ellos, ya sea para un número fijo de iteraciones, o hasta que los puntos se estabilicen.

Ahora debe tener N puntos que representen N colores que sean lo más diferentes posible dentro del espacio de color que le interese.

Hugo


30

Por el bien de las generaciones futuras, agrego aquí la respuesta aceptada en Python.

import numpy as np
import colorsys

def _get_colors(num_colors):
    colors=[]
    for i in np.arange(0., 360., 360. / num_colors):
        hue = i/360.
        lightness = (50 + np.random.rand() * 10)/100.
        saturation = (90 + np.random.rand() * 10)/100.
        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    return colors

18

Todo el mundo parece haberse perdido la existencia del muy útil espacio de color YUV que fue diseñado para representar las diferencias de color percibidas en el sistema visual humano. Las distancias en YUV representan diferencias en la percepción humana. Necesitaba esta funcionalidad para MagicCube4D que implementa cubos de Rubik en 4 dimensiones y un número ilimitado de otros rompecabezas retorcidos 4D que tienen un número arbitrario de caras.

Mi solución comienza seleccionando puntos aleatorios en YUV y luego separando iterativamente los dos puntos más cercanos, y solo convirtiendo a RGB cuando devuelve el resultado. El método es O (n ^ 3) pero eso no importa para los números pequeños o los que se pueden almacenar en caché. Ciertamente puede hacerse más eficiente, pero los resultados parecen ser excelentes.

La función permite la especificación opcional de umbrales de brillo para no producir colores en los que ningún componente sea más brillante o más oscuro que las cantidades dadas. Es decir, es posible que no desee valores cercanos al blanco o negro. Esto es útil cuando los colores resultantes se utilizarán como colores base que luego se sombrearán a través de la iluminación, la estratificación, la transparencia, etc. y aún deben aparecer diferentes de sus colores base.

import java.awt.Color;
import java.util.Random;

/**
 * Contains a method to generate N visually distinct colors and helper methods.
 * 
 * @author Melinda Green
 */
public class ColorUtils {
    private ColorUtils() {} // To disallow instantiation.
    private final static float
        U_OFF = .436f,
        V_OFF = .615f;
    private static final long RAND_SEED = 0;
    private static Random rand = new Random(RAND_SEED);    

    /*
     * Returns an array of ncolors RGB triplets such that each is as unique from the rest as possible
     * and each color has at least one component greater than minComponent and one less than maxComponent.
     * Use min == 1 and max == 0 to include the full RGB color range.
     * 
     * Warning: O N^2 algorithm blows up fast for more than 100 colors.
     */
    public static Color[] generateVisuallyDistinctColors(int ncolors, float minComponent, float maxComponent) {
        rand.setSeed(RAND_SEED); // So that we get consistent results for each combination of inputs

        float[][] yuv = new float[ncolors][3];

        // initialize array with random colors
        for(int got = 0; got < ncolors;) {
            System.arraycopy(randYUVinRGBRange(minComponent, maxComponent), 0, yuv[got++], 0, 3);
        }
        // continually break up the worst-fit color pair until we get tired of searching
        for(int c = 0; c < ncolors * 1000; c++) {
            float worst = 8888;
            int worstID = 0;
            for(int i = 1; i < yuv.length; i++) {
                for(int j = 0; j < i; j++) {
                    float dist = sqrdist(yuv[i], yuv[j]);
                    if(dist < worst) {
                        worst = dist;
                        worstID = i;
                    }
                }
            }
            float[] best = randYUVBetterThan(worst, minComponent, maxComponent, yuv);
            if(best == null)
                break;
            else
                yuv[worstID] = best;
        }

        Color[] rgbs = new Color[yuv.length];
        for(int i = 0; i < yuv.length; i++) {
            float[] rgb = new float[3];
            yuv2rgb(yuv[i][0], yuv[i][1], yuv[i][2], rgb);
            rgbs[i] = new Color(rgb[0], rgb[1], rgb[2]);
            //System.out.println(rgb[i][0] + "\t" + rgb[i][1] + "\t" + rgb[i][2]);
        }

        return rgbs;
    }

    public static void hsv2rgb(float h, float s, float v, float[] rgb) {
        // H is given on [0->6] or -1. S and V are given on [0->1]. 
        // RGB are each returned on [0->1]. 
        float m, n, f;
        int i;

        float[] hsv = new float[3];

        hsv[0] = h;
        hsv[1] = s;
        hsv[2] = v;
        System.out.println("H: " + h + " S: " + s + " V:" + v);
        if(hsv[0] == -1) {
            rgb[0] = rgb[1] = rgb[2] = hsv[2];
            return;
        }
        i = (int) (Math.floor(hsv[0]));
        f = hsv[0] - i;
        if(i % 2 == 0)
            f = 1 - f; // if i is even 
        m = hsv[2] * (1 - hsv[1]);
        n = hsv[2] * (1 - hsv[1] * f);
        switch(i) {
            case 6:
            case 0:
                rgb[0] = hsv[2];
                rgb[1] = n;
                rgb[2] = m;
                break;
            case 1:
                rgb[0] = n;
                rgb[1] = hsv[2];
                rgb[2] = m;
                break;
            case 2:
                rgb[0] = m;
                rgb[1] = hsv[2];
                rgb[2] = n;
                break;
            case 3:
                rgb[0] = m;
                rgb[1] = n;
                rgb[2] = hsv[2];
                break;
            case 4:
                rgb[0] = n;
                rgb[1] = m;
                rgb[2] = hsv[2];
                break;
            case 5:
                rgb[0] = hsv[2];
                rgb[1] = m;
                rgb[2] = n;
                break;
        }
    }


    // From http://en.wikipedia.org/wiki/YUV#Mathematical_derivations_and_formulas
    public static void yuv2rgb(float y, float u, float v, float[] rgb) {
        rgb[0] = 1 * y + 0 * u + 1.13983f * v;
        rgb[1] = 1 * y + -.39465f * u + -.58060f * v;
        rgb[2] = 1 * y + 2.03211f * u + 0 * v;
    }

    public static void rgb2yuv(float r, float g, float b, float[] yuv) {
        yuv[0] = .299f * r + .587f * g + .114f * b;
        yuv[1] = -.14713f * r + -.28886f * g + .436f * b;
        yuv[2] = .615f * r + -.51499f * g + -.10001f * b;
    }

    private static float[] randYUVinRGBRange(float minComponent, float maxComponent) {
        while(true) {
            float y = rand.nextFloat(); // * YFRAC + 1-YFRAC);
            float u = rand.nextFloat() * 2 * U_OFF - U_OFF;
            float v = rand.nextFloat() * 2 * V_OFF - V_OFF;
            float[] rgb = new float[3];
            yuv2rgb(y, u, v, rgb);
            float r = rgb[0], g = rgb[1], b = rgb[2];
            if(0 <= r && r <= 1 &&
                0 <= g && g <= 1 &&
                0 <= b && b <= 1 &&
                (r > minComponent || g > minComponent || b > minComponent) && // don't want all dark components
                (r < maxComponent || g < maxComponent || b < maxComponent)) // don't want all light components

                return new float[]{y, u, v};
        }
    }

    private static float sqrdist(float[] a, float[] b) {
        float sum = 0;
        for(int i = 0; i < a.length; i++) {
            float diff = a[i] - b[i];
            sum += diff * diff;
        }
        return sum;
    }

    private static double worstFit(Color[] colors) {
        float worst = 8888;
        float[] a = new float[3], b = new float[3];
        for(int i = 1; i < colors.length; i++) {
            colors[i].getColorComponents(a);
            for(int j = 0; j < i; j++) {
                colors[j].getColorComponents(b);
                float dist = sqrdist(a, b);
                if(dist < worst) {
                    worst = dist;
                }
            }
        }
        return Math.sqrt(worst);
    }

    private static float[] randYUVBetterThan(float bestDistSqrd, float minComponent, float maxComponent, float[][] in) {
        for(int attempt = 1; attempt < 100 * in.length; attempt++) {
            float[] candidate = randYUVinRGBRange(minComponent, maxComponent);
            boolean good = true;
            for(int i = 0; i < in.length; i++)
                if(sqrdist(candidate, in[i]) < bestDistSqrd)
                    good = false;
            if(good)
                return candidate;
        }
        return null; // after a bunch of passes, couldn't find a candidate that beat the best.
    }


    /**
     * Simple example program.
     */
    public static void main(String[] args) {
        final int ncolors = 10;
        Color[] colors = generateVisuallyDistinctColors(ncolors, .8f, .3f);
        for(int i = 0; i < colors.length; i++) {
            System.out.println(colors[i].toString());
        }
        System.out.println("Worst fit color = " + worstFit(colors));
    }

}

¿Hay una versión C # de este código en alguna parte? Intenté convertirlo y ejecutarlo con los mismos argumentos que pasaste para generateVisuallyDistinctColors () y parece que funciona muy lento. ¿Es eso esperado?
Chris Smith

¿Obtiene los mismos resultados? Es bastante rápido para mis necesidades, pero como dije, no he intentado optimizarlo, por lo que si ese es su único problema, probablemente debería prestar atención a la asignación / desasignación de memoria. No sé nada sobre la administración de memoria C #. En el peor de los casos, podría reducir la constante del bucle externo de 1,000 a algo más pequeño y la diferencia de calidad puede incluso no ser notable.
Melinda Green

1
Mi paleta debe contener ciertos colores pero quería completar los extras. Me gusta su método b / c. Puedo poner mis colores requeridos primero en su matriz yuv y luego modificar "j = 0" para comenzar a optimizar después de mis colores requeridos. Desearía que la separación de las peores parejas fuera un poco más inteligente, pero puedo entender por qué eso es difícil.
Ryan

Creo que a su método yuv2rgb le falta la abrazadera (0,255).
Ryan

yuv2rgb es todo flota, no bytes Ryan. Por favor escriba a melinda@superliminal.com para discutir.
Melinda Green

6

El modelo de color HSL puede ser adecuado para "ordenar" colores, pero si está buscando colores visualmente distintos, definitivamente necesita el modelo de color Lab .

CIELAB fue diseñado para ser perceptualmente uniforme con respecto a la visión del color humano, lo que significa que la misma cantidad de cambio numérico en estos valores corresponde aproximadamente a la misma cantidad de cambio percibido visualmente.

Una vez que sepa eso, encontrar el subconjunto óptimo de N colores de una amplia gama de colores sigue siendo un problema difícil (NP), algo similar al problema del vendedor viajero y todas las soluciones que utilizan algoritmos k-mean o algo realmente no ayuda.

Dicho esto, si N no es demasiado grande y si comienza con un conjunto limitado de colores, encontrará fácilmente un muy buen subconjunto de colores distintivos de acuerdo con una distancia Lab con una función aleatoria simple.

He codificado dicha herramienta para mi propio uso (puede encontrarla aquí: https://mokole.com/palette.html ), esto es lo que obtuve para N = 7: ingrese la descripción de la imagen aquí

Todo es JavaScript, así que siéntase libre de echar un vistazo a la fuente de la página y adaptarla a sus propias necesidades.


1
En cuanto a »la misma cantidad de cambio numérico [...] la misma cantidad de cambio percibido visualmente «. Jugué con un selector de color CIE Lab y no pude confirmarlo en absoluto. Voy a denotar colores de laboratorio utilizando los rangos Lde 0 a 128 y ay bde -128 a 128. ¶ Empecé con L= 0, a= -128, b= -128, que es un azul brillante. Luego aumenté atres veces. Big El gran cambio (+128) a= 50 da como resultado un azul solo ligeramente más oscuro. ❷ (+85) a= 85 resultados todavía en azul. ❸ Sin embargo, el cambio relativamente pequeño (+43) a= 128 cambia completamente el color a fucsia.
Socowi

Esto es muy útil para mi. Sin embargo, sería ideal si los resultados fueran fáciles de copiar y pegar.
Mitchell van Zuylen

5

Aquí hay una solución para manejar su problema "distinto", que es totalmente exagerado:

Crea una esfera unitaria y suelta puntos sobre ella con cargas repelentes. Ejecute un sistema de partículas hasta que ya no se muevan (o el delta sea "lo suficientemente pequeño"). En este punto, cada uno de los puntos está lo más lejos posible el uno del otro. Convierte (x, y, z) a rgb.

Lo menciono porque para ciertas clases de problemas, este tipo de solución puede funcionar mejor que la fuerza bruta.

Originalmente vi este enfoque aquí para testear una esfera.

Una vez más, las soluciones más obvias de atravesar el espacio HSL o el espacio RGB probablemente funcionarán bien.


1
Esa es una buena idea, pero probablemente tenga sentido usar un cubo, en lugar de una esfera.
Rocketmagnet

1
Eso es lo que hace mi solución basada en YUV, pero para una caja 3D (no un cubo).
Melinda Green

3

Intentaría fijar la saturación y la iluminación al máximo y enfocarme solo en el tono. Según lo veo, H puede ir de 0 a 255 y luego se envuelve. Ahora, si quisieras dos colores contrastantes, tomarías los lados opuestos de este anillo, es decir, 0 y 128. Si quisieras 4 colores, tomarías algunos separados por 1/4 de la longitud del círculo 256, es decir, 0, 64,128,192. Y, por supuesto, como otros sugirieron cuando necesita N colores, puede separarlos por 256 / N.

Lo que agregaría a esta idea es usar una representación inversa de un número binario para formar esta secuencia. Mira este:

0 = 00000000  after reversal is 00000000 = 0
1 = 00000001  after reversal is 10000000 = 128
2 = 00000010  after reversal is 01000000 = 64
3 = 00000011  after reversal is 11000000 = 192

... de esta manera, si necesita N colores diferentes, puede tomar los primeros N números, revertirlos y obtener tantos puntos distantes como sea posible (para que N sea la potencia de dos) y al mismo tiempo preservar que cada prefijo del La secuencia difiere mucho.

Este era un objetivo importante en mi caso de uso, ya que tenía una tabla donde los colores se ordenaban por área cubierta por este color. Quería que las áreas más grandes de la tabla tuvieran un gran contraste, y estaba de acuerdo con que algunas áreas pequeñas tuvieran colores similares a los del top 10, ya que era obvio para el lector cuál es cuál observando el área.


Esto es lo que hice en mi respuesta, aunque un poco más " matemático ". Ver la función getfracs. Sin embargo, su enfoque es rápido y "simple" en lenguajes de bajo nivel: poco marcha atrás en C .
Janus Troelsen


1

Si N es lo suficientemente grande, obtendrás algunos colores de aspecto similar. Solo hay muchos de ellos en el mundo.

¿Por qué no simplemente distribuirlos uniformemente a través del espectro?

IEnumerable<Color> CreateUniqueColors(int nColors)
{
    int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d));
    for(int r = 0; r < 255; r += subdivision)
        for(int g = 0; g < 255; g += subdivision)
            for(int b = 0; b < 255; b += subdivision)
                yield return Color.FromArgb(r, g, b);
}

Si desea mezclar la secuencia para que colores similares no estén uno al lado del otro, tal vez podría barajar la lista resultante.

¿Estoy pensando en esto?


2
Sí, estás subestimando esto. La percepción del color humano no es lineal, desafortunadamente. Es posible que también deba tener en cuenta el cambio de Bezold-Brücke si utiliza intensidades variables. También hay buena información aquí: vis4.net/blog/posts/avoid-equidistant-hsv-colors
spex

1

Esto es trivial en MATLAB (hay un comando hsv):

cmap = hsv(number_of_colors)

1

He escrito un paquete para R llamado qualpalr que está diseñado específicamente para este propósito. Te recomiendo que mires la viñeta para averiguar cómo funciona, pero intentaré resumir los puntos principales.

Qualpalr toma una especificación de colores en el espacio de color HSL (que se describió anteriormente en este hilo), lo proyecta en el espacio de color DIN99d (que es perceptualmente uniforme) y encuentra el nque maximiza la distancia mínima entre ellos.

# Create a palette of 4 colors of hues from 0 to 360, saturations between
# 0.1 and 0.5, and lightness from 0.6 to 0.85
pal <- qualpal(n = 4, list(h = c(0, 360), s = c(0.1, 0.5), l = c(0.6, 0.85)))

# Look at the colors in hex format
pal$hex
#> [1] "#6F75CE" "#CC6B76" "#CAC16A" "#76D0D0"

# Create a palette using one of the predefined color subspaces
pal2 <- qualpal(n = 4, colorspace = "pretty")

# Distance matrix of the DIN99d color differences
pal2$de_DIN99d
#>        #69A3CC #6ECC6E #CA6BC4
#> 6ECC6E      22                
#> CA6BC4      21      30        
#> CD976B      24      21      21

plot(pal2)

ingrese la descripción de la imagen aquí


1

Creo que este algoritmo recursivo simple complementa la respuesta aceptada, para generar valores de tono distintos. Lo hice para hsv, pero también se puede usar para otros espacios de color.

Genera matices en ciclos, lo más separados posible entre sí en cada ciclo.

/**
 * 1st cycle: 0, 120, 240
 * 2nd cycle (+60): 60, 180, 300
 * 3th cycle (+30): 30, 150, 270, 90, 210, 330
 * 4th cycle (+15): 15, 135, 255, 75, 195, 315, 45, 165, 285, 105, 225, 345
 */
public static float recursiveHue(int n) {
    // if 3: alternates red, green, blue variations
    float firstCycle = 3;

    // First cycle
    if (n < firstCycle) {
        return n * 360f / firstCycle;
    }
    // Each cycle has as much values as all previous cycles summed (powers of 2)
    else {
        // floor of log base 2
        int numCycles = (int)Math.floor(Math.log(n / firstCycle) / Math.log(2));
        // divDown stores the larger power of 2 that is still lower than n
        int divDown = (int)(firstCycle * Math.pow(2, numCycles));
        // same hues than previous cycle, but summing an offset (half than previous cycle)
        return recursiveHue(n % divDown) + 180f / divDown;
    }
}

No pude encontrar este tipo de algoritmo aquí. Espero que ayude, es mi primera publicación aquí.


0

Esta función OpenCV utiliza el modelo de color HSV para generar ncolores distribuidos uniformemente alrededor de 0 <= H <= 360º con un máximo de S = 1.0 y V = 1.0. La función genera los colores BGR en bgr_mat:

void distributed_colors (int n, cv::Mat_<cv::Vec3f> & bgr_mat) {
  cv::Mat_<cv::Vec3f> hsv_mat(n,CV_32F,cv::Vec3f(0.0,1.0,1.0));
  double step = 360.0/n;
  double h= 0.0;
  cv::Vec3f value;
  for (int i=0;i<n;i++,h+=step) {
    value = hsv_mat.at<cv::Vec3f>(i);
    hsv_mat.at<cv::Vec3f>(i)[0] = h;
  }
  cv::cvtColor(hsv_mat, bgr_mat, CV_HSV2BGR);
  bgr_mat *= 255;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.