Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.
Como você faria para gerar cores aleatoriamente em Java as quais, seguem as características descritas abaixo ?
- Devem ser aleatórias (não podem ser fixas/hard-coded)
- Devem estar no formato hexadecimal para HTML
- Não podem ser muito claras
- Não podem ser muito escuras
- Não podem ser preto nem branco
- Não podem ser parecidas
- Não podem haver cores repetidas
Tá .... vamos começar pelo começo !!
Para que você tenha um total entendimento do assunto abordado aqui, é necessário conhecimento em Java e OO. Caso você não tenha, recomendo algumas aulinhas antes.
Nossa primeira exigência é que as cores devem ser aleatórias. Contudo, como gerar cores através de Java ? Bem, sabemos que o padrão de cores utilizado por componentes eletrônicos é o RGB. O modelo de cores RGB é baseado na teoria de visão colorida tricromática, de Young-Helmholtz, e no triângulo de cores de Hardwell. O uso do modelo RGB como padrão para apresentação de cores na Internet tem suas raízes nos padrões de cores de televisões RCA de 1953 e no uso do padrão RGB nas câmeras Land/Polaroid, pós Edwin Land [Wikipedia]. Este padrão é dividido em três cores, sendo elas:
- Red = vermelho
- Green = verde
- Blue = azul
Uma das representações mais usuais para as cores é a utilização da escala de 0 à 255, bastante encontrada na computação pela conveniência de se guardar cada valor de cor em 1 byte (8 bits) [Wikipedia].
Clique aqui e se diverta fazendo suas combinações de cores. Algumas sugestões para você:
- Branco - RGB (255,255,255);
- Azul - RGB (0,0,255);
- Vermelho - RGB (255,0,0);
- Verde - RGB (0,255,0);
- Amarelo - RGB (255,255,0);
- Magenta - RGB (255,0,255);
- Ciano - RGB (0,255,255);
- Preto - RGB (0,0,0).
Mãos a massa !!
Método gerarCorAleatoriamente()
1: /**
2: * Método responsável pela geração de cores em hexadecimal
3: * @return A cor
4: */
5: private Color gerarCorAleatoriamente(){
6: Random randColor = new Random();
7: int r = randColor.nextInt(256);
8: int g = randColor.nextInt(256);
9: int b = randColor.nextInt(256);
10: return new Color(r, g, b);
11: }
Esse método é responsável pela geração de cores aleatórias. Para realizar tal, utiliza o método nextInt(int) o qual, recebe um argumento inteiro que representa o limite do número randômico. O número que será gerado estará no limite 0 - 255. Através da geração aleatória dos números, são definidos as quantidades de R, G e B da cor. A classe Color é utilizada somente para fins de representação.
Método gerarCorHexadecimal(color) e tratarHexString(string)
1: /**
2: * Método que exporta a cor para o formato hexadecimal.
3: * @param color Representa a cor.
4: * @return Retorna a cor no formato hexadecimal.
5: */
6: private String gerarCorHexadecimal(Color color){
7: return '#'+
8: this.tratarHexString(Integer.toHexString(color.getRed()))+
9: this.tratarHexString(Integer.toHexString(color.getGreen()))+
10: this.tratarHexString(Integer.toHexString(color.getBlue()));
11: }
Este método trabalha em conjunto com o método tratarHexString(string).
1: /**
2: * Método responsável por tratar a string hexadecimal. Caso a string possua tamanho 1, o método adiciona o número 0 na frente da string.
3: * @param hexString A string hexadecimal
4: * @return A string hexadecimal
5: */
6: private String tratarHexString(String hexString){
7: String hex = null;
8: if(hexString.length() == 1){
9: hex = '0'+hexString;
10: }else{
11: hex = hexString;
12: }
13: return hex;
14: }
Os métodos acima são fácies de entender. O primeiro obtém as quantidades de R, G e B da cor, gerada aleatoriamente na seção acima, e transforma-as em hexadecimal. Contudo, um tratamento precisa ser feito o qual, é realizado no segundo método.
Quando uma quantidade tem seu correspondente hexadecimal gerado somente com um único caractere, torna problemática a exibição de cores pois, o formato de cores em hexadecimal espera três pares de R, G e B. Logo, isso #001a0f é diferente disso #001af. Então, é adicionado o caractere '0' à esquerda da string (linha 9) e o problema é solucionado.
Método isBrilhoCorreto(float) e isSaturacaoCorreta(float)
1: /**
2: * Método que trata o brilho de uma cor.
3: * Para que uma cor seja escolhida para o gráfico seu nível de brilho deve estar acima ou igual de 0.5 e abaixo ou igual de 0.9.
4: * @param brightness Representa o brilho.
5: * @return Retorna true caso a cor esteja de acordo com o ponto necessário (brilho), false caso contrário.
6: */
7: private boolean isBrilhoCorreto(float brightness){
8: if (brightness >= 0.5 && brightness <= 0.9) {
9: return true;
10: }
11: return false;
12: }
13:
14: /**
15: * Método que trata a saturação de uma cor.
16: * Para que uma cor seja escolhida para o gráfico seu nível de saturação deve estar acima ou igual de 0.7.
17: * @param saturation Representa a saturação
18: * @return Retorna true caso a cor esteja de acordo com o ponto necessário (saturação), false caso contrário.
19: */
20: private boolean isSaturacaoCorreta(float saturation){
21: if (saturation >= 0.7){
22: return true;
23: }
24: return false;
25: }
Para que possamos gerar cores que não sejam muito claras nem muito escuras precisamos controlar seus níveis de brilho (brightness) e saturação (saturation). Como parâmetros para o controle, adotaremos que nossas cores ideais tenham o nível de saturação maior ou igual a 0.7 e que possuam brilho entre 0.5, inclusive, e 0.9, inclusive.
A saturação é um parâmetro que especifica a qualidade de um matiz de cor pelo grau de mesclagem do matiz com a cor branca (padrão HSB) ou, com a cor cinza média (padrão HSL). Então, quanto menos cinza ou branco na cor, mais saturada ela é [Wikipedia]. Entenda por matiz, a cor pura.
Método calcularDistanciaDeCores(color, color), isDistanciaAceitavel(color, color) e isCorParecidaComCorPermitida(list, color)
1: /**
2: * Método que calcula a distância entre cores.
3: * @param cor1 Representa uma cor.
4: * @param cor2 Representa uma cor.
5: * @return Retorna a distância.
6: */
7: private double calcularDistanciaDeCores(Color cor1, Color cor2){
8: long meanRed = (cor1.getRed() + cor2.getRed())/2;
9: long deltaRed = cor1.getRed() - cor2.getRed();
10: long deltaGreen = cor1.getGreen() - cor2.getGreen();
11: long deltaBlue = cor1.getBlue() - cor2.getBlue();
12: return Math.sqrt((2+meanRed/256)*Math.pow(deltaRed, 2)+4*Math.pow(deltaGreen, 2)+(2+(255-meanRed)/256)*Math.pow(deltaBlue, 2));
13: }
14:
15: /**
16: * Método que avalia se a distância entre duas é aceitável. Uma distância é dita aceita se é menor que 200.
17: * @param cor1 Representa uma cor.
18: * @param cor2 Representa uma cor.
19: * @return boolean Retorna true caso a distância seja menor que 200, false caso contrário.
20: */
21: private boolean isDistanciaAceitavel(Color cor1, Color cor2){
22: if (this.calcularDistanciaDeCores(cor1, cor2) < 200) {
23: return false;
24: }
25: return true;
26: }
27:
28: /**
29: * Método que valida se uma cor gerada aleatoriamente possui uma distância aceitável com as cores previamente permitidas.
30: * @param coresPermitidas Representa as cores permitidas.
31: * @param corAleatoria Representa a cor gerada aleatoriamente.
32: * @return Retorna true caso a cor seja parecida, retorna false caso contrário.
33: */
34: private boolean isCorParecidaComCorPermitida(List<Color> coresPermitidas, Color corAleatoria){
35: boolean isCorParecida = false;
36: for (Color corPermitida : coresPermitidas) {
37: if(!this.isDistanciaAceitavel(corPermitida, corAleatoria)){
38: isCorParecida = true;
39: break;
40: }
41: }
42: return isCorParecida;
43: }
Gerar cores aleatoriamente tem um sério problema, a geração de cores parecidas. A cor azul e azul' não são iguais, mas absurdamente parecidas. Então, como saber se um cor é parecida com a outra? Comparar os níveis de R, G e B da cor não é boa idéia pois, mesmo que as cores sejam muito parecidas, seus níveis são extremamente diferentes. Logo, não temos como estabelecer um parâmetro e voltamos a pergunta. Como identificar uma cor parecida ?
Através de pesquisa, encontrei uma forma para resolver esse problema. Precisamos calcular a distância euclidiana entre as cores, ou seja, a distância entre dois pontos. O cálculo é realizado através do método calcularDistanciaDeCores(color, color) o qual, recebe duas cores como parâmetros. O valor retornado é a distância.
Agora, precisamos estabelecer o que é uma distância aceitável. Através de testes, notei que cores com a distância maior que 200 mantinham uma boa diferença em sua percepção. Então, criei o método isDistanciaAceitavel(color, color). Esse método retorna true, caso as cores estejam em uma distância aceitável ( > 200), caso contrário retorna false. Logo, o problema citado anteriormente foi resolvido. Não precisamos mais nos preocupar com azul e azul'.
A terceira etapa para encerrar esse problema é comparar a cor gerada com as cores já previamente escolhidas. Logo, o método isCorParecidaComCorPermitida(list, color) foi criado. Este método realiza essa comparação utilizando o método citado no parágrafo acima. A lista recebida como parâmetro contém as cores previamente escolhidas e o segundo parâmetro representa a cor gerada recentemente. Caso a cor não seja "parecida" com nenhuma das outras cores já escolhidas, o método retorna true, caso contrário, false.
Método carregarCoresProibidasDefault(list)
1: /**
2: * Método que realiza a carga inicial de cores proibidas.
3: * @param coresProibidas Representa a lista de cores proibidas.
4: */
5: private void carregarCoresProibidasDefault(List<Color> coresProibidas){
6: coresProibidas.add(new Color(0,0,0)); //preto
7: coresProibidas.add(new Color(255,255,255)); //branco
8: }
Este método carrega cores proibidas as quais são default (padrão). No nosso escopo, são cores proibidas por padrão, a cor preta e branca. Contudo, caso no seu escopo, a cor marrom ou azul-petróleo forem proibidas por padrão, você pode alterar o método e verificação de cores proibidas levará em conta essas, previamente adicionadas.
É possível notar que o método recebe um parâmetro do tipo List. Esse parâmetro é passado para que a lista de cores seja alimentada com as configurações iniciais e posteriormente com as cores proibidas geradas aleatoriamente.
Método gerarCores(int)
1: /**
2: * Método responsável por gerar cores aleatoriamente no formato RGB seguindo os padrões de qualidade especificados.
3: * @param qtdDeCores Representa a quantidade de cores que deseja-se gerar.
4: * @return List<Color> Representa a lista de cores permitidas em formato RGB.
5: */
6: public List<Color> gerarCores(int qtdDeCores){
7: List<Color> coresPermitidas = new ArrayList<Color>();
8: List<Color> coresProibidas = new ArrayList<Color>();
9: this.carregarCoresProibidasDefault(coresProibidas);
10:
11: Color corAleatoria = null;
12: boolean isCorProibida = false;
13: for(int i=0; i<qtdDeCores; i++){
14: while(true){
15: corAleatoria = this.gerarCorAleatoriamente();
16: float[] hsb = Color.RGBtoHSB(corAleatoria.getRed(), corAleatoria.getGreen(), corAleatoria.getBlue(), null);
17: float saturation = hsb[1];
18: float brightness = hsb[2];
19: for(Color corProibida : coresProibidas){
20: isCorProibida = corProibida.equals(corAleatoria)
21: || !this.isBrilhoCorreto(brightness)
22: || !this.isSaturacaoCorreta(saturation)
23: || !this.isDistanciaAceitavel(corAleatoria, corProibida)
24: || this.isCorParecidaComCorPermitida(coresPermitidas, corAleatoria);
25: if(isCorProibida){
26: coresProibidas.add(corProibida);
27: break;
28: }
29: }
30: if(isCorProibida){
31: isCorProibida = false;
32: continue;
33: }
34: break;
35: }
36: coresProibidas.clear();
37: coresPermitidas.add(corAleatoria);
38: }
39:
40: return coresPermitidas;
41: }
Esse método é o grande controlador de toda a inteligência da geração de cores. É ele quem aplica todas as regras que especificamos acima mas, existem alguns pontos os quais, merecem uma atenção maior. Na linha 16, é possível notar uma transformação de RGB para HSB (HueSaturationBrightness). Essa transformação é necessária para que, possamos acessar essas propriedades das cores.
O loop infinito é necessário para que seja gerada uma cor aleatoriamente até que ela seja aceita segundo nossas regras de qualidade. Como não sabemos quando essa suposta cor será gerada, então é necessário um loop infinito acompanhada de uma condicional com break.
A limpeza da lista de cores proibidas é necessária pois seu tamanho cresce absurdamente. Caso esse tratamento não fosse realizado uma OutOfMemoryError aconteceria e aplicação seria encerrada.
Pronto !! Terminamos !!
Abaixo você pode encontrar as referências usadas e um link para um site muito legal sobre cores, vale a pena dar uma conferida.
Caso você faça o download do projeto, encontrará a classe que desenvolvemos aqui e um Jar. O arquivo Jar contém nossa classe compilada para que, você possa adicioná-la ao seu projeto e gerar suas cores.
Baixe o projeto
Dropbox: https://www.dropbox.com/s/dc4b021zse7akfp/GeradorCoresAleatorias.rar?dl=0
GitHub: https://github.com/PrecisoEstudarSempre/GeradorCoresAleatorias.git
Dúvidas !? Sugestões ?! Críticas ou elogios ?!
Deixe aí nos comentários, na nossa página do facebook ou mande um email.
Facebook: https://www.facebook.com/precisoestudarsempre/
E-mail: precisoestudarsempre@gmail.com
Referências:
Colour metric - http://www.compuphase.com/cmetric.htm
RGB - https://pt.wikipedia.org/wiki/RGB
HTML Color Picker - http://www.w3schools.com/tags/ref_colorpicker.asp
Saturação - https://pt.wikipedia.org/wiki/Satura%C3%A7%C3%A3o
Matiz (cor) - https://pt.wikipedia.org/wiki/Matiz_(cor)
Color Hex Color Codes - http://www.color-hex.com/
3 comentários:
Cara, maravilhoso. Precisava exatamente deste algoritmo para gerar cores automaticamente para gráficos. Parabéns pelo trabalho!
Muito obrigado Matheus Sousa. Agradeço a consideração e carinho pelo trabalho. Fique a vontade para conhecer o blog. Você sempre será bem-vindo.
Material excelente, além de gerar cores aleatórias...estou precisando gerar cores através de combinações pré-definidas...tipo cor x combinada com a cor y gera a cor z....
Postar um comentário