Inicialmente, eu teria a mesma resposta que todo mundo tinha e atribuiria isso aos problemas rand()
. No entanto, achei melhor fazê-lo e, em vez disso, analisei a distribuição que sua matemática está realmente produzindo.
TL; DR: O padrão que você vê não tem nada a ver com o gerador de números aleatórios subjacente e, em vez disso, é simplesmente devido à maneira como seu programa está manipulando os números.
Vou manter a sua função azul, uma vez que são todos semelhantes.
uint8_t blue(uint32_t x, uint32_t y) {
return (rand() % 2) ? (x + y) % rand() :
((x * y % 1024) % rand()) % 2 ? (x - y) % rand() :
rand();
}
Cada valor de pixel é seleccionado a partir de um de três funções: (x + y) % rand()
, (x - y) % rand()
e rand()
;
Vejamos as imagens produzidas por cada uma delas sozinha.
Isto é o que você esperaria, apenas barulho. Chame isso de "Imagem C"
Aqui você adiciona as coordenadas de pixel e evita que o restante seja dividido por um número aleatório. Se a imagem for 1024x1024, a soma estará no intervalo [0-2046]. O número aleatório pelo qual você está mergulhando está no intervalo [0, RAND_MAX], onde RAND_MAX é de pelo menos 32k e em alguns sistemas é de 2 bilhões. Em outras palavras, há uma chance de 1 em 16 de que o restante não seja apenas (x + y)
. Portanto, na maior parte das vezes, essa função produz apenas um gradiente de aumento de azul na direção + x + y.
No entanto, você está usando apenas os 8 bits mais baixos, porque retorna a uint8_t
, para ter faixas de gradientes com 256 pixels de largura.
Chame isso de "Imagem A"
Aqui você faz algo semelhante, mas com subtração. Contanto que x seja maior que y, você terá algo semelhante à imagem anterior. Mas onde y é maior, o resultado é um número muito grande porque x
e y
não está assinado (resultados negativos são agrupados no topo do intervalo do tipo não assinado) e, em seguida, o sinal % rand()
entra em ação e você realmente recebe ruído.
Chame isso de "Imagem B"
Cada pixel em sua imagem final é obtido de uma dessas três imagens usando as funções rand() % 2
e ((x * y % 1024) % rand()) % 2
. A primeira delas pode ser lida como uma escolha com 50% de probabilidade (ignorando problemas com rand()
e seus bits de baixa ordem).
Aqui está um close de onde rand() % 2
é verdade (pixels brancos) para que a Imagem A seja selecionada.
A segunda função ((x * y % 1024) % rand()) % 2
novamente apresenta o problema em que rand()
geralmente é maior do que o que você está dividindo (x * y % 1024)
, que é no máximo 1023. Então (x*y%1024)%2
não produz 0 e 1 com a mesma frequência. Qualquer número ímpar multiplicado por qualquer número par é par. Qualquer número par multiplicado por qualquer número par também é par. Somente um número ímpar multiplicado por um número ímpar é ímpar, e assim por %2
diante, valores que são até três quartos das vezes produzirão 0 três quartos das vezes.
Aqui está um close de onde ((x * y % 1024) % rand()) % 2
é verdade, para que a Imagem B possa ser selecionada. É selecionar exatamente onde as duas coordenadas são ímpares.
E aqui está um close de onde a Imagem C pode ser selecionada:
Finalmente, combinando as condições, é aqui que a Imagem B é selecionada:
E onde a Imagem C está selecionada:
A combinação resultante pode ser lida como:
Com 50% de probabilidade, use o pixel da Imagem A. O restante do tempo escolhe entre a Imagem B e a Imagem C, B, onde ambas as coordenadas são ímpares, C, onde ambas são pares.
Finalmente, como você faz o mesmo com três cores diferentes, mas com orientações diferentes, os padrões são orientados de maneira diferente em cada cor e produzem as tiras de cruzamento ou o padrão de grade que você está vendo.