Como gerar automaticamente N colors “distintas”?

Eu escrevi os dois methods abaixo para selecionar automaticamente N colors distintas. Ele funciona definindo uma function linear por partes no cubo RGB. O benefício disso é que você também pode obter uma escala progressiva, se é isso que você quer, mas quando N fica grande, as colors podem começar a parecer semelhantes. Eu também posso imaginar subdividir o cubo RGB em uma rede e depois desenhar pontos. Alguém conhece algum outro método? Eu estou descartando a definição de uma lista e, em seguida, apenas passando por ela. Eu também devo dizer que eu geralmente não me importo se eles se chocam ou não parecem legais, eles só precisam ser visualmente distintos.

public static List pick(int num) { List colors = new ArrayList(); if (num < 2) return colors; float dx = 1.0f / (float) (num - 1); for (int i = 0; i = 0.0f && x = 0.2f && x = 0.4f && x = 0.6f && 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); } 

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

Se tudo o que você deseja é tons diferentes (provavelmente) e pequenas variações de luminosidade ou saturação, você pode distribuir os matizes 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); } 

Essas perguntas aparecem em algumas discussões do SO:

  • Algoritmo para gerar colors exclusivas
  • Gere colors exclusivas
  • Gere colors RGB distintamente diferentes em charts
  • Como gerar n colors diferentes para qualquer número natural n?

Diferentes soluções são propostas, mas nenhuma é ótima. Felizmente, a ciência vem para o resgate

N Arbitrário

  • Monitores coloridos para imagens categóricas (download gratuito)
  • UM SERVIÇO WEB PARA PERSONALIZAR MAPA COLORAÇÃO (download gratuito, uma solução de webservice deve estar disponível no próximo mês)
  • Algoritmo para a seleção de conjuntos de colors de alto contraste (os autores oferecem uma implementação C ++ gratuita)
  • Conjuntos de colors de alto contraste (o primeiro algoritmo para o problema)

Os dois últimos serão gratuitos na maioria das bibliotecas universitárias / proxies.

N é finito e relativamente pequeno

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

  • Um Alfabeto de Cor e os Limites da Codificação de Cores

Existem várias listas de colors a serem consideradas:

  • Lista de 11 colors de Boynton que quase nunca são confundidas (disponível no primeiro artigo da seção anterior)
  • As 22 colors de contraste máximo de Kelly (disponíveis no papel acima)

Eu também corri para esta paleta por um estudante do MIT. Por fim, os links a seguir podem ser úteis na conversão entre diferentes sistemas de colors / coordenadas (algumas colors 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 deveria ser óbvio). Algum código c #:

 public static ReadOnlyCollection KellysMaxContrastSet { get { return _kellysMaxContrastSet.AsReadOnly(); } } private static readonly List _kellysMaxContrastSet = new List { 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 BoyntonOptimized { get { return _boyntonOptimized.AsReadOnly(); } } private static readonly List _boyntonOptimized = new List { 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 hexadecimais 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 Java, aqui estão as colors 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, as colors kelly não classificadas de acordo com a ordem acima.

cores kelly não classificadas

o seguinte é as colors kelly ordenadas de acordo com os matizes (note que alguns amarelos não são muito contrastantes)

cores kelly ordenadas

Aqui está uma ideia. Imagine um cilindro de HSV

Defina os limites superior e inferior que você 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 sobre eles, seja para um número fixo de iterações, ou até que os pontos se estabilizem.

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

Hugo

Como a resposta de Uri Cohen, mas é um gerador em vez disso. Começará usando colors distantes. Determinista

Amostra, colors da esquerda primeiro: amostra

 #!/usr/bin/env python3.3 import colorsys import itertools from fractions import Fraction def zenos_dichotomy(): """ 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 getfracs(): """ [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 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) bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5) # can be used for the v in hsv to map linear values 0..1 to something that looks equidistant def genhsv(h): 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 genrgb = lambda x: colorsys.hsv_to_rgb(*x) flatten = itertools.chain.from_iterable gethsvs = lambda: flatten(map(genhsv,getfracs())) getrgbs = lambda: map(genrgb, gethsvs()) def genhtml(x): uint8tuple = map(lambda y: int(y*255), x) return "rgb({},{},{})".format(*uint8tuple) gethtmlcolors = lambda: map(genhtml, getrgbs()) if __name__ == "__main__": print(list(itertools.islice(gethtmlcolors(), 100))) 

Por causa 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 

Todo mundo parece ter perdido a existência do espaço de colors YUV muito útil que foi projetado para representar diferenças de colors percebidas no sistema visual humano. As distâncias no YUV representam diferenças na percepção humana. Eu precisava dessa funcionalidade para MagicCube4D que implementa cubos de Rubik 4-dimensionais e um número ilimitado de outros quebra-cabeças 4D twisty com números arbitrários de faces.

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

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

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

Crie uma esfera de unidade e solte pontos nela com taxas de repelimento. 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 fica o mais longe possível um do outro. Converta (x, y, z) para rgb.

Eu menciono isso porque, para certas classs de problemas, esse tipo de solução pode funcionar melhor do que a força bruta.

Eu originalmente vi essa abordagem aqui para tesselating uma esfera.

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

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

O que eu acrescentaria a essa ideia seria 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 

… desta forma, se você precisar de N colors diferentes, você poderia pegar os primeiros N números, invertê-los e obter o máximo possível de pontos distantes (pois N é o poder de dois) e, ao mesmo tempo, preservar cada prefixo do seqüência difere muito.

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

Se N for grande o suficiente, você obterá algumas colors semelhantes. Há apenas muitos deles no mundo.

Por que não distribuí-los uniformemente pelo espectro, da seguinte forma:

 IEnumerable 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ê quiser misturar a sequência de modo que colors semelhantes não fiquem próximas uma da outra, você pode embaralhar a lista resultante.

Eu estou pensando nisso?

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

 cmap = hsv(number_of_colors) 

Eu escrevi um pacote para R chamado qualpalr que é projetado especificamente para este propósito. Eu recomendo que você olhe para a vinheta para descobrir como ela funciona, mas vou tentar resumir os pontos principais.

qualpalr pega uma especificação de colors no espaço de cor HSL (que foi descrito anteriormente neste encadeamento), projeta-o para o espaço de colors DIN99d (que é perceptivelmente uniforme) e encontra o n que 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

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

Ele gera matizes em ciclos, tão separados quanto possível entre si 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 este tipo de algoritmo aqui. Espero que ajude, é o meu primeiro post aqui.