Como gerar automaticamente N cores "distintas"?


194

Eu escrevi os dois métodos abaixo para selecionar automaticamente N cores distintas. Ele funciona definindo uma função linear por partes no cubo RGB. O benefício disso é que você também pode obter uma escala progressiva, se é isso o que deseja, mas quando N fica grande, as cores podem começar a parecer semelhantes. Também posso imaginar subdividir uniformemente o cubo RGB em uma treliça e depois desenhar pontos. Alguém conhece outros métodos? Estou descartando a definição de uma lista e, em seguida, apenas passando por ela. Eu também deveria dizer que geralmente não me importo se eles se chocam ou não parecem bons, eles só precisam 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);
}

4
Fortemente relevante programadores pergunta com respostas interessantes: " Os esquemas de cores geração - teoria e algoritmos ."
Alexey Popkov

2
Infelizmente, a percepção da cor humana não é linear. Você também pode precisar prestar contas do turno de Bezold – Brücke se estiver usando intensidades variadas. Há também uma boa informação aqui: vis4.net/blog/posts/avoid-equidistant-hsv-colors
Spex

Respostas:


80

Você pode usar o modelo de cores HSL para criar suas cores.

Se tudo o que você deseja são tons diferentes (provável) e pequenas variações na luminosidade ou saturação, é possível distribuir os tons da seguinte forma:

// 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
Essa técnica é inteligente. Aposto que obterá mais resultados estéticos que os meus.
Mqp 22/01/09

45
Isso pressupõe que valores de matiz igualmente espaçados são igualmente perceptivamente diferentes. Mesmo descontando várias formas de daltonismo, isso não é verdade para a maioria das pessoas: a diferença entre 120 ° (verde) e 135 ° (verde ligeiramente menta) é imperceptível, enquanto a diferença entre 30 ° (laranja) e 45 ° (pêssego) é bastante óbvio. Você precisa de um espaçamento não linear ao longo da tonalidade para obter melhores resultados.
Phrogz

18
@ mquander - Não é nada inteligente. Não há nada para impedir que esse algoritmo escolha acidentalmente duas cores quase idênticas. Minha resposta é melhor e a resposta de ohadsc é muito melhor.
Rocketmagnet

1
Isso está errado pelos motivos já mencionados, mas também porque você não está escolhendo uniformemente .
Sam Hocevar

3
@strager qual é o valor esperado de randf ()
Killrawr

242

Essa pergunta aparece em várias discussões do SO:

Soluções diferentes são propostas, mas nenhuma é a ideal. Felizmente, a ciência vem em socorro

Arbitrário N

Os dois últimos serão gratuitos através da maioria das bibliotecas / proxies da universidade.

N é finito e relativamente pequeno

Nesse caso, pode-se optar por uma solução de lista. Um artigo muito interessante sobre o assunto está disponível gratuitamente:

Existem várias listas de cores a serem consideradas:

  • A lista de 11 cores de Boynton quase nunca se confunde (disponível no primeiro artigo da seção anterior)
  • As 22 cores de Kelly de máximo contraste (disponíveis no artigo acima)

Também encontrei essa paleta por um aluno do MIT. Por fim, os links a seguir podem ser úteis na conversão entre diferentes sistemas / coordenadas de cores (algumas cores nos artigos não são especificadas em RGB, por exemplo):

Para a lista de Kelly e Boynton, eu já fiz a conversão para RGB (com exceção de branco e preto, o que deve ser óbvio). Algum 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);
}

E aqui estão os valores RGB nas representações hexadecimal e 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 os desenvolvedores de Java, aqui estão as cores do 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
};

a seguir estão as cores kelly não classificadas de acordo com a ordem acima.

cores kelly não classificadas

as cores a seguir são classificadas de acordo com os tons (observe que alguns amarelos não são muito contrastantes)

 kelly cores classificadas


+1 Muito obrigado por esta ótima resposta! BTW o link colour-journal.org/2010/5/10 está morto, este artigo ainda está disponível no web.archive.org .
Alexey Popkov


16
Ótima resposta, obrigado! Tomei a liberdade de transformar esses conjuntos de duas cores em um conveniente jsfiddle, onde você pode ver as cores em ação.
David Mills

1
Só notei que existem apenas 20 e 9 cores nessas listas, respectivamente. Eu acho que é porque branco e preto são omitidos.
David Mills

2
O serviço da web já está disponível?
Janus Troelsen

38

Como a resposta de Uri Cohen, mas é um gerador. Começará usando cores distantes. Determinístico.

Amostra, deixe as cores primeiro: amostra

#!/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 a amostra, muito bom, e mostra que o esquema também é atraente. As outras respostas aqui seriam melhoradas fazendo o mesmo e, em seguida, poderiam ser prontamente comparadas.
Don escotilha

3
A quantidade de lambdas é muito alta. O lambda é forte com este.
Gyfis 01/10/16

Parece ótimo, mas fica preso ao tentar executá-lo em 2,7
Elad Weiss

33

Aqui está uma ideia. Imagine um cilindro HSV

Defina os limites superior e inferior que deseja para o brilho e a saturação. Isso define um anel de seção transversal quadrada dentro do espaço.

Agora, espalhe N pontos aleatoriamente dentro deste espaço.

Em seguida, aplique um algoritmo de repulsão iterativo neles, para um número fixo de iterações ou até que os pontos se estabilizem.

Agora você deve ter N pontos representando N cores tão diferentes quanto possível dentro do espaço de cores em que você está interessado.

Hugo


30

Para o bem das gerações vindouras, adiciono aqui a resposta aceita em 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 mundo parece ter perdido a existência do muito útil espaço de cores YUV, que foi projetado para representar diferenças de cores percebidas no sistema visual humano. As distâncias no YUV representam diferenças na percepção humana. Eu precisava dessa funcionalidade para o MagicCube4D, que implementa os cubos de Rubik em quatro dimensões e um número ilimitado de outros quebra-cabeças sinuosos em 4D com números arbitrários de faces.

Minha solução começa selecionando pontos aleatórios em YUV e depois iterativamente dividindo os dois pontos mais próximos e convertendo apenas em RGB ao retornar o resultado. O método é O (n ^ 3), mas isso não importa para números pequenos ou para números que podem ser armazenados em cache. Certamente pode ser mais eficiente, mas os resultados parecem excelentes.

A função permite a especificação opcional de limites de brilho para não produzir cores nas quais nenhum componente é mais brilhante ou mais escuro do que as quantidades especificadas. Ou seja, você pode não querer valores próximos de preto ou branco. Isso é útil quando as cores resultantes serão usadas como cores base, que serão posteriormente sombreadas por meio de iluminação, camadas, transparência etc. e ainda deverão parecer diferentes das cores 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));
    }

}

Existe uma versão C # desse código em algum lugar? Tentei convertê-lo e executá-lo com os mesmos argumentos que você transmitiu para generateVisuallyDistinctColors () e parece estar muito lento. Isso é esperado?
Chris Smith

Você obtém os mesmos resultados? É muito rápido para minhas necessidades, mas, como eu disse, não tentei otimizá-lo; portanto, se esse é seu único problema, você provavelmente deve prestar atenção à alocação / desalocação de memória. Não sei nada sobre gerenciamento de memória c #. Na pior das hipóteses, você pode reduzir a constante do loop externo 1.000 para algo menor e a diferença de qualidade pode nem ser perceptível.
Melinda Green

1
Minha paleta deve conter certas cores, mas eu queria preencher os extras. Eu gosto do seu método b / c. Posso colocar minhas cores necessárias primeiro no seu array yuv e depois modificar "j = 0" para começar a otimizar depois das cores necessárias. Eu gostaria que a separação dos piores pares fosse um pouco mais inteligente, mas eu posso entender por que isso é difícil.
Ryan

Acho que o seu método yuv2rgb está faltando o grampo (0,255).
Ryan

yuv2rgb é todos os carros alegóricos, não bytes Ryan. Escreva para melinda@superliminal.com para discutir.
Melinda Green

6

O modelo de cores HSL pode ser adequado para "classificar" cores, mas se você estiver procurando cores visualmente distintas, precisará definitivamente do modelo de cores Lab .

O CIELAB foi projetado para ser perceptualmente uniforme em relação à visão de cores humana, o que significa que a mesma quantidade de alteração numérica nesses valores corresponde à mesma quantidade de alteração percebida visualmente.

Depois que você souber disso, encontrar o subconjunto ideal de N cores de uma ampla variedade de cores ainda é um problema difícil (NP), meio semelhante ao problema do vendedor ambulante e todas as soluções que usam algoritmos k-mean ou algo realmente não Socorro.

Dito isto, se N não for muito grande e se você começar com um conjunto limitado de cores, encontrará facilmente um subconjunto muito bom de cores distintas de acordo com a distância do laboratório com uma função aleatória simples.

Eu codifiquei essa ferramenta para meu próprio uso (você pode encontrá-la aqui: https://mokole.com/palette.html ), eis o que eu tenho para N = 7: insira a descrição da imagem aqui

É tudo javascript, portanto, fique à vontade para dar uma olhada na fonte da página e adaptá-la às suas próprias necessidades.


1
Em relação a » mesma quantidade de mudança numérica [...] mesma quantidade de mudança visualmente percebida «. Eu brinquei com um seletor de cores do CIE Lab e não pude confirmar isso. Irei representar cores de laboratório usando os intervalos Lde 0 a 128 e ae ba partir de -128 a 128. ¶ I com L= 0, a= -128, b= -128, que é de um azul brilhante. Então eu aumentei atrês vezes. Big A grande mudança (+128) a= 50 resulta em um azul apenas um pouco mais escuro. ❷ (+85) a= 85 resultados ainda em azul. ❸ No entanto, a mudança relativamente pequena (+43) a= 128 muda completamente a cor para fúcsia.
Socowi 18/09/19

Isso é muito útil para mim. Seria ideal se os resultados fossem fáceis de copiar e colar.
Mitchell van Zuylen

5

Aqui está uma solução para gerenciar seu problema "distinto", que é totalmente exagerado:

Crie uma esfera unitária e solte pontos nela com cargas repelentes. Execute um sistema de partículas até que eles não se movam mais (ou o delta seja "pequeno o suficiente"). Nesse ponto, cada um dos pontos está o mais longe possível um do outro. Converta (x, y, z) em rgb.

Menciono isso porque, para certas classes de problemas, esse tipo de solução pode funcionar melhor que a força bruta.

Originalmente, vi essa abordagem aqui para pavimentar uma esfera.

Novamente, as soluções mais óbvias de percorrer o espaço HSL ou RGB provavelmente funcionarão bem.


1
É uma boa ideia, mas provavelmente faz sentido usar um cubo, em vez de uma esfera.
Rocketmagnet

1
É o que minha solução baseada em YUV faz, exceto por uma caixa 3D (não um cubo).
Melinda Green

3

Eu tentaria corrigir a saturação e a luminosidade ao máximo e focar apenas no tom. A meu ver, H pode ir de 0 a 255 e depois se mover. Agora, se você quisesse duas cores contrastantes, escolheria os lados opostos desse anel, ou seja, 0 e 128. Se desejasse 4 cores, separaria 1/4 do comprimento do círculo 256, ou seja, 0, 64,128,192. E, é claro, como outros sugeriram quando você precisa de N cores, basta separá-las por 256 / N.

O que eu acrescentaria a essa idéia é usar uma representação invertida de um número binário para formar essa sequência. Veja isso:

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

... dessa forma, se você precisar de N cores diferentes, poderá pegar os primeiros N números, invertê-los e obter o máximo de pontos distantes possível (pois N é poder de dois), preservando ao mesmo tempo que cada prefixo do sequência difere muito.

Esse era um objetivo importante no meu caso de uso, pois eu tinha um gráfico em que as cores eram classificadas por área coberta por essa cor. Eu queria que as maiores áreas do gráfico tivessem um grande contraste, e eu estava bem com algumas pequenas áreas com cores semelhantes às do top 10, pois era óbvio para o leitor qual é qual delas, apenas observando a área.


Foi o que fiz na minha resposta, embora um pouco mais " matemático ". Veja a função getfracs. Mas sua abordagem é rápido e "simples" em linguagens de baixo nível: pouco revertendo em C .
Janus Troelsen


1

Se N for grande o suficiente, você obterá cores com aparência semelhante. Existem tantos no mundo.

Por que não distribuí-los uniformemente pelo espectro, assim:

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

Se você deseja misturar a sequência para que cores semelhantes não fiquem próximas, talvez seja possível embaralhar a lista resultante.

Estou pensando isso?


2
Sim, você está subestimando isso. Infelizmente, a percepção da cor humana não é linear. Você também pode precisar prestar contas do turno de Bezold – Brücke se estiver usando intensidades variadas. Há também uma boa informação aqui: vis4.net/blog/posts/avoid-equidistant-hsv-colors
Spex

1

Isso é trivial no MATLAB (existe um comando hsv):

cmap = hsv(number_of_colors)

1

Eu escrevi um pacote para R chamado qualpalr, projetado especificamente para esse fim. Eu recomendo que você analise a vinheta para descobrir como ela funciona, mas tentarei resumir os pontos principais.

O qualpalr pega uma especificação de cores no espaço de cores HSL (que foi descrito anteriormente nesta linha), projeta-o no espaço de cores DIN99d (que é perceptualmente uniforme) e descobre o nque maximiza a distância mínima entre qualquer um deles.

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

insira a descrição da imagem aqui


1

Eu acho que esse algoritmo recursivo simples complementa a resposta aceita, a fim de gerar valores distintos de matiz. Eu fiz isso para hsv, mas também pode ser usado para outros espaços de cores.

Ele gera matizes em ciclos, o mais separado possível um do outro em 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;
    }
}

Não consegui encontrar esse tipo de algoritmo aqui. Espero que ajude, é o meu primeiro post aqui.


0

Essa função OpenCV usa o modelo de cores HSV para gerar ncores uniformemente distribuídas em torno de 0 <= H <= 360º com S = 1,0 máximo e V = 1,0. A função gera as cores BGR em 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;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.