[ CAPÍTULO 5 ]
Ilusões
Valor nos detalhes

O processo artístico por trás de uma pintura, ilustração ou qualquer outro tipo de produção composta por múltiplas fases, normalmente começa com o planejamento. Grandes artistas usam e abusam dos rascunhos para delinear a forma inicial de uma ideia e, posteriormente, aprimorá-la. Nos quatro capítulos anteriores você foi direcionado para o criar dos esboços de sua obra através do posicionamento de figuras, controle do fluxo do desenho, repetições de padrões e inclusão de variedade através de elementos como a incerteza. Esses são os aspectos técnicos da composição artística, aqueles que precedem a arte.

O próximo passo é a etapa de construção de estilo e refinamento da forma que consiste em adicionar riqueza visual, seja por meio de cores, profundidade de sombreamento, transparência ou complexidade dos traços. Neste capítulo você irá aprender como utilizar as funções do Processing para transformar rascunhos tediosos em produções intrigantes que atraem olhares.

É tentador procurar padronizar o máximo de tarefas na computação, mas isso dificilmente pode ser estendido para a arte. A interpretação de uma obra é algo muito pessoal e depende tanto de nossa vivência pessoal quanto do contexto histórico e social da época. Consequentemente leve as informações apresentadas neste capítulo exatamente como elas são: apenas sugestões baseadas em práticas comuns na arte tradicional e computacional. Extrapole esse pensamento, entenda que toda regra tem uma exceção e, as vezes, quebrá-las causa mais efeito do que segui-las.

5.1 Linhas etéreas

As formatações em seus programas são como um paralelo dos diversos materiais artísticos disponíveis para a customização do traçado de seu desenho. Espessuras de ponta de lápis e cores vibrantes de crayon podem ser emuladas diretamente com as funções nativas do Processing, enquanto pinceladas e depósitos irregulares de tinta costumam ser programados em aplicações mais elaboradas. Ao longo do livro você informalmente usou funções de estilo como:

  • fill() Preenche a figura com uma cor qualquer.
  • noFill() : A figura não possuirá preenchimento, ela será transparente.
  • stroke() : Preenche a borda da figura (ou uma linha/ponto) com uma cor qualquer.
  • noStroke() : A borda da figura não possuirá preenchimento, ela será transparente.
  • strokeWeight() : Define a espessura da borda da figura (ou de uma linha/ponto).

Rigorosamente, as ferramentas de formatação funcionam como máquinas de estado, isto é, a formatação que você estabelecer permanecerá como vigente até que você a mude de novo. Por exemplo, se você definiu um fill(255,0,0) no início do seu programa, todas as figuras que você desenhar serão preenchidas de vermelho até que você mude de cor chamando outra função fill(). A formatação é persistente ao ponto de não voltar para a original mesmo no início de um novo frame da função draw(). Você pode fazer uma analogia com o processo de desenhar no papel, uma vez que você decide usar um lápis vermelho, tudo que você desenhar será vermelho até que você decida usar outro lápis. O código abaixo e a figura 5.1 ilustram esse fenômeno:

Código 5.1 -

void setup() {
background(255);
// O Processing irá gerar um quadro a cada 2 segundos,
// desta forma você terá tempo para ver a formatação como uma máquina de estados.
frameRate(0.5);
}

void draw() {
// Exibe o número de quadros decorridos no console:
println("Frame:", frameCount);
rect(10,10,40,40);
fill(255,0,0);
rect(50,50,40,40);
}
Figura 5.2 - Primeiro frame da execução.
Figura 5.3 - Demais frames da execução.
Figura - 5.1 - Formatação como uma máquina de estados.

Ao executar o código você deve ter observado no primeiro frame são exibidos dois quadrados, um branco e um vermelho e, no segundo frame, dois vermelhos. No quadro inicial não havia sido estabelecida uma formatação para o preenchimento das formas, fill(), e o Processing usou o padrão, que é a cor branca. Porém, no meio da execução do primeiro quadro a cor de preenchimento é definida como vermelho e logo em seguida é desenhada uma forma com essa cor. Como o Processing usa de uma máquina de estados, quando ele parte para o segundo quadro a formatação de preenchimento já está definida e ele não usará mais o padrão original. Desta forma ambos os quadrados são preenchidos com a cor vermelha. Fique atento a essa particularidade visto que, se você desejar múltiplos estilos de formatação no seu programa, deverá colocar o respectivo código imediatamente antes do desenho das figuras.

Um dos fatores emulados pelas funções de estilo que contribui significativamente na aparência final de uma imagem é a suavidade dos traços usados nos desenhos. Essa formatação pode ser evidenciada através de um panorama saturado de formas, tais como círculos de posições e raios distintos. No código abaixo, no qual o ruído Perlin é o protagonista na disposição irregular das figuras, exploramos a suavidade dos traços:

Código 5.2 -

// Ruídos:
float nx = 0, ny = 100, nr = 200;

void setup() {
size(1200, 500);
background(255);
noFill();
}

void draw() {
float posX = map(noise(nx),0,1,0,width);
float posY = map(noise(ny),0,1,0,height);
float r = 100 + 200*noise(nr);

// Círculo de posição e raio variados:
ellipse(posX,posY,r,r);

// Os incrementos de ruídos foram alterados
// inúmeras vezes até achar um movimento satisfatório.
nx += 0.008;
ny += 0.005;
nr += 0.005;
}
Figura 5.4 - Desenhos de múltiplos círculos na janela.

A janela de saída se parecerá com algo similar a figura 5.4. Você deve ter notado que não há variação no traço do círculo o que torna a figura densa e sobrecarregada. Se o programa continuasse a executar por um pouco mais de tempo, obteríamos apenas um borrão preto proveniente da aglomeração de traços sólidos. Experimente deixar o programa rodando por um minuto e depois volte para ver os resultados.

Seguindo a linha de suavidade dos traços, podemos usar a função strokeWeight() com o intuito de reduzir a espessura dos mesmos. É possível aumentar ainda mais profundidade visual se adicionarmos transparência à cor preta, firmando que apenas o perseverante acúmulo de figuras através dos frames produza uma cor mais intensa. Adicione a linha abaixo logo antes do desenho do círculo.

Código 5.3 -

stroke(map(r,100,300,100,0),20);

Veja a diferença ao deixar os dois programas executarem exatamente o mesmo número de frames (8000) na figura 5.5.

Figura 5.6 - Formatação padrão do Processing.
Figura 5.5 - Formatação customizada.
Figura - 5.5 - Comparação da imagem gerada após 8000 frames.

Essa técnica leva a um aumento no tempo total de execução para que a figura seja "concluída", situação facilmente relevada dada a sutileza das linhas que fabricarão uma imagem menos agressiva ao olhar.

5.2 Faça-se a cor

As cores são outro grupo de elementos que fornecem um aspecto completamente novo as suas imagens. Na arte tradicional e na ilustração moderna elas costumam ser agrupadas em paletas de cores, que são um conjunto discreto e finito de tons como os da figura 5.7, idealizados para manter a consistência em seus projetos.

Figura 5.8 - Na arte.
Figura 5.9 - Na computação.
Figura - 5.7 - Exemplo de paletas de cores em meios distintos.

As paletas são a representação física de um esquema de cores que, por sua vez, é definido como uma escolha de cores a serem usadas no design de um determinado tipo de mídia. Eles são empregados para criar estilo e aumentar o atrativo e apelo estético de um trabalho. Existem diversas técnicas para a escolha de um esquema de cores, como complementares, triádicas, tetraédricas e análogas dentre outras, mostradas na figura 5.10.

Figura 5.11 - Análogas.
Figura 5.12 - Complementares.
Figura 5.13 - Monocromáticas.
Figura - 5.10 - Esquema de cores.

A definição da paleta pode seguir regras estritas como as técnicas citadas acima ou então através de combinações não convencionais puramente decididas por sentimento. O instinto, nesse ponto, tem um papel substancial ao implicitamente externar como as cores influenciam nas emoções humanas, uma das áreas que é hoje estudada pela psicologia[1]. No Processing é possível escrever um código que emula uma paleta usando vetores:

Código 5.4 -

// Cores definidas no sistema RGB:
color[] c = { color(88,24,64), color(144,12,58), color(199,0,52), color(255,87,46) };

que é equivalente a:

Código 5.5 -

// Cores definidas através de seu código hexadecimal:
color[] c = { #581840, #900C3A, #C70034, #FF572E };

Esta segunda maneira é mais usual e utiliza de uma codificação hexadecimal das cores. A equivalência entre a descrição RGB e hexadecimal segue uma transformação entre bases numéricas e a ferramenta de seleção de cores, explicada na seção 2.1.3, automaticamente realiza esse cálculo para você. Veja a figura 5.14.

Figura 5.14 - Equivalência RGB e Hexadecimal das cores.

Do ponto de vista de código, é interessante criar uma paleta de cores como um vetor para acessá-las diretamente pelos seus índices. Tal abordagem segmenta o esquema de cores do código, aumentando a organização e permitindo sua troca sem que seja necessário reescrever o código. Também possibilita que seja utilizado da aleatoriedade para escolher as cores de maneira inesperada, veja o código abaixo:

Código 5.6 -

color[] c = { #581840, #900C3A, #C70034, #FF572E };
// Cor escolhida aleatoriamente dentre todas as possibilidades da paleta:
int indice = round(random(3));
background(c[indice]);

Cabe ressaltar que a escolha aleatória dentro de um espaço finito de cores, como a de uma paleta, é fundamentalmente diferente de uma paleta completamente arbitrária uma vez que, no primeiro caso, o esquema é mantido e no segundo não. Veja as distinções na figura 5.15. A melhor maneira de expor o impacto causado por uma paleta de cores é através de uma comparação do tipo "antes e depois". As figuras 5.16, 5.17 e 5.18 ilustram o mesmo programa executado, inicialmente através da formatação padrão e depois usando uma paleta de cores. A figura 5.19 é a colorização da mandala do capítulo anterior.

Figura 5.20
Figura 5.21
Figura - 5.15 - Paleta customizada e aleatória.
Figura 5.22
Figura 5.23
Figura - 5.16 - Padrão com e sem cores.

As diferenças são notáveis como você pode ter percebido. Os dois principais responsáveis pelo sucesso das paletas sobre um esquema de cores inteiramente aleatório ou a ausência delas são a limitação do espectro de cores e escolha cuidadosa das mesmas para permitir uma harmonização de tons e contrastes.

Figura 5.24
Figura 5.25
Figura - 5.17 - Padrão com e sem cores.
Figura 5.26
Figura 5.27
Figura - 5.18 - Padrão com e sem cores.
Figura 5.19 - Mandala sob uma paleta de cores.

5.3 Através do espelho

A simetria é uma propriedade excepcionalmente bela que reina em absoluto em nossa sociedade e no mundo natural. Sua presença é tão constante que a consideramos banal, muitas vezes esquecendo de admirar sua singela beleza e a sensação de equilíbrio e harmonia que ela proporciona. Na arte computacional a simetria pode ser usada para criar figuras caleidoscópicas, invariantes e espelhadas.

O conceito técnico por trás da simetria é simples: a forma é mantida mesmo sob presença de operações geométricas como reflexão, translação, rotação, ampliação e redução (figura 5.28). A simetria radial, como aquela mostrada na seção 4.3, é obtida a partir de rotações apoiadas na trigonometria, cujo posicionamento das figuras utilizará das funções seno e cosseno. O segundo tipo, a simetria de escala, relativa à contração ou expansão da figura, é representada pelos fractais e será apresentada mais a frente neste mesmo livro. Finalmente, a simetria reflexiva, estudada nesta seção, é caracterizada pela ação de cortar um objeto por um ou mais planos, dividindo-o em partes iguais, porém espelhadas. Na presença de movimento, a seção complementar se moverá na direção oposta da original. Se uma forma se move para a esquerda, sua reflexão se moverá para a direita. O mesmo vale para a orientação vertical. Esse ponto é ilustrado conceitualmente em 5.29 e computacionalmente em 5.30.

Figura 5.31 - Reflexão.
Figura 5.32 - Rotação.
Figura 5.33 - Escala.
Figura - 5.28 - Exemplos de figuras simétricas.
Figura 5.29 - Movimento em simetria reflexiva.
Figura 5.34 - Horizontal.
Figura 5.35 - Vertical.
Figura - 5.30 - Simetria reflexiva.

No capítulo destinado a funções, na seção de interrupções, 2.3.1, você desenvolveu um código que desenhava círculos ao movimentar o mouse. Você pode experimentar adicionar a simetria reflexiva ao código para transformar o tipo de figuras que serão geradas. Uma possível adaptação é mostrada no código a seguir, que contempla simultaneamente as simetrias horizontais (eixo x) e verticais (eixo y), desenhando quatro círculos no lugar de apenas um. Veja o resultado na figura 5.36.

Código 5.7 -

// Paleta de cores:
color[] c = { #581840, #900C3A, #C70034, #FF572E };

void setup() {
size(800,800);
background(255);
noStroke();
}

void draw() {
}

// Mouse pressionado e arrastado:
void mouseDragged() {
int indice = round(random(3));
fill(c[indice]);
float raio = dist(mouseX,mouseY,pmouseX,pmouseY);
ellipse(mouseX,mouseY,raio,raio);
ellipse(mouseX,height-mouseY,raio,raio);
ellipse(width-mouseX,mouseY,raio,raio);
ellipse(width-mouseX,height-mouseY,raio,raio);
}

A computação, aliada à simetria, também pode ser aplicada quando se deseja explorar padrões artísticos repetitivos. A figura 5.37 é um máximo da simetria (aumente o zoom na imagem) que demonstra todas as 32768 formas de se preencher um quadrado, simétrico em relação ao seu eixo y central, dividido em vinte e cinco partes iguais[2].

Figura 5.36 - Desenho simétrico na janela ao arrastar o mouse.
Figura 5.37 - Total de 32768 possibilidades.

5.4 Um exemplo para reinar sobre todos

A segunda parte deste livro, e este capítulo, serão encerrados com a proposta de que você use tudo que aprendeu até o momento para desenvolver um programa que crie imagens gerativas. O principal objetivo será a visualização, de uma maneira mais elaborada, da função noise().

Na seção 4.1, dominada por operações trigonométricas, você aprendeu que é possível determinar qualquer ponto na superfície de um círculo através de duas fórmulas que dependem de seu centro, raio e ângulo do setor. Elas fórmulas também podem ser usadas para traçar uma reta qualquer, cujo primeiro ponto é o centro do círculo e o segundo é dado pelo ponto em sua superfície, que pode ser facilmente determinado quando se tem o raio e o ângulo. Estes dois últimos parâmetros são arbitrários e você é livre para escolher qual será o comprimento dessa reta (raio do círculo) e qual será sua orientação (ângulo do setor). Por exemplo, se desejar traçar uma reta com início no ponto (50,50), com 40 pixels de comprimento e uma inclinação de 30°, basta utilizar as fórmulas 4.1 para obter os pontos finais dela:

Código 5.8 -

float xf = 50 + 40*cos(radians(30)); // O resultado é xf = 84.64
float yf = 50 + 40*sin(radians(30)); // O resultado é yf = 70.00

e ela seria traçada como:

Código 5.9 -

line(50,50,xf,yf).

Isto será muito útil por que em vez de usar pontos para visualizar o ruído Perlin, você pode utilizar retas anguladas. Para obter um efeito uniforme é interessante distribuir essas retas homogeneamente em toda a janela de saída. Você pode usar um raciocínio semelhante ao do posicionamento de blocos no padrão de Truchet, seção 2.5.2, e construir uma estrutura dupla de repetições. Vamos concretizar essas ideias através de um programa, veja o código abaixo. Algumas variáveis foram parametrizadas para auxiliar nas etapas posteriores. O resultado é mostrado na figura 5.38.

Código 5.10 -

void setup() {
size(700, 200);
frameRate(30);
background(255);
}

void draw() {
background(255);

// Separação entre uma linha e outra (em pixels):
int sepLinhas = 20;

// Tamanho das linhas desenhadas (em pixels):
float tamLinha = 20;

for (int y = 0; y <= height; y += sepLinhas) {
for (int x = 0; x <= width; x += sepLinhas) {
// Define a variável que irá determinar a inclinação das retas:
float angulo = 30;
float xf = x + tamLinha * cos(radians(angulo));
float yf = y + tamLinha * sin(radians(angulo));
line(x, y, xf, yf);
}
}
}
Figura 5.38 - Retas anguladas distribuídas homogeneamente na tela.

Visto que as retas estão distribuídas de maneira adequada, você pode adicionar o ruído Perlin para diversificar a angulação das mesmas. Altere as estruturas de repetição de acordo com o código abaixo para criar padrões semelhantes ao da figura 5.39.

Código 5.11 -

// Valores iniciais dos ruídos:
float nxi = random(100);
float nyi = random(100);

void setup() {
size(700, 200);
frameRate(30);
background(255);
}

void draw() {
background(255);

// Separação entre uma linha e outra (em pixels):
int sepLinhas = 20;

// Tamanho das linhas desenhadas (em pixels):
float tamLinha = 20;

// Incrementa o valor inicial dos ruídos:
nxi += 0.01;
nyi += 0.01;

// Define os ruídos locais a serem usados na angulação das retas:
float ny = nyi;
float nx = nxi;

for (int y = 0; y <= height; y += sepLinhas) {
// Incrementa e reinicia o ruído local:
ny += 0.05;
nx = nxi;

for (int x = 0; x <= width; x += sepLinhas) {
// Incrementa o ruído local:
nx += 0.05;

// Define a variável que irá determinar a inclinação das retas:
float angulo = 360 * noise(nx, ny);
float xf = x + tamLinha * cos(radians(angulo));
float yf = y + tamLinha * sin(radians(angulo));
line(x, y, xf, yf);
}
}
}
Figura 5.39 - Retas anguladas através da função noise().

Ao executar o programa e a animação, você deve ter observado que a função noise() cria uma variação suave no ângulo das retas, expressando um efeito de fluxo na sua imagem. Mais uma vez isso reforça a natureza orgânica do ruído Perlin. A figura 5.40 mostra o que aconteceria se fosse utilizada a função random() em seu lugar.

Figura 5.40 - Retas anguladas através da função random().

Neste ponto podemos considerar que a etapa de rascunho, o alicerce do programa, está terminada e que nos resta canalizar nossa atenção para a formatação. A primeira alteração que você pode fazer é experimentar com diversos valores para o tamanho e quantidade de retas. Esses valores foram parametrizados na concepção do código, atere-os para obter padrões como os da figura 5.41.

Figura 5.44 -
sepLinhas = 2
tamLinha = 2
Figura - 5.41 - Padrões formados por outras parametrizações.

O segundo ponto é enriquecer visualmente a figura usando tons e cores. As alternativas aqui são muitas, sendo uma delas baseada na disposição espacial das retas. Nesse tipo de abordagem utiliza-se da informação da posição dos objetos, no caso as retas, para definir a formatação. Um exemplo seria colocar tons mais claros em retas à direita da tela e gradativamente escurece-las até atingir o canto esquerdo da janela, criando uma espécie de gradiente como mostrado na figura 5.46. A única alteração que deve ser feita é adicionar uma formatação, logo antes do desenho, interna na segunda estrutura de repetição:

Código 5.12 - Formatação baseada em posição -

for (int x = 0; x <= width; x += sepLinhas) {
// Incrementa o ruído local:
nx += 0.05;

// Define a variável que irá determinar a inclinação das retas:
float angulo = 360 * noise(nx, ny);

// Utiliza as coordenadas espaciais (x,y) para criar um gradiente:
float cor = map(x + y, 0, width + height, 0, 255);
stroke(cor);

float xf = x + tamLinha * cos(radians(angulo));
float yf = y + tamLinha * sin(radians(angulo));
line(x, y, xf, yf);
}
Figura 5.46 - Formatação em estilo gradiente.

A formatação no estilo gradiente é interessante em alguns casos, mas ela é simplória e previsível. Nela, sempre existirão um ou mais pontos de convergência de cores e algum tipo de interpolação (no caso acima, linear). Uma boa estratégia para cativar olhares é abusar de contrastes inesperados em um mesmo padrão. Então, qual variável pode ser responsável pela formatação sem ser a posição das retas? Um ótimo candidato é um parâmetro que varia inesperadamente com o desenho de cada uma delas como, por exemplo, seu ângulo cujos valores estão entre 0° \ e 360°. O código abaixo é uma sugestão de como fazer isso, e uma das possíveis imagens geradas está mostrada na figura 5.47.

Código 5.13 - Formatação baseada em ângulo -

for (int x = 0; x <= width; x += sepLinhas) {
// Incrementa o ruído local:
nx += 0.05;

// Define a variável que irá determinar a inclinação das retas:
float angulo = 360*noise(nx,ny);

// Utiliza a angulação das retas para definição da cor:
float cor = map(angulo,0,360,0,255);

stroke(cor);
float xf = x + tamLinha*cos(radians(angulo));
float yf = y + tamLinha*sin(radians(angulo));
line(x,y,xf,yf);
}
Figura 5.47 - Retas coloridas de acordo com suas angulações.

Você pode perceber que agora as retas formam um padrão menos homogêneo. Essa formatação ainda tem uma consequência oculta bem mais importante do que somente a inclusão de cores. Note que não há mais problema se as retas se sobreporem uma vez que serão criados contrastes proveniente das diferentes angulações. Isso permite que a quantidade de retas seja aumentada consideravelmente. Para conseguir imagens como a 5.48 altere os valores das variáveis para:

Código 5.14 -

int sepLinhas = 2;
float tamLinha = 10;

e comente a limpeza de fundo dentro de draw():

Código 5.15 -

//background(255);
Figura 5.48 - Retas sobrepostas com formatação dependente de ângulo.

A finalização da obra pode ser feita aumentando os incrementos dos ruídos que provocará uma crescente oscilação nas inclinações das retas, entalhando a imagem com uma textura mais visível, progressivamente mais agressiva. Mudando o foco da composição, podemos adicionar cores ao invés de apenas tons de cinza. Optou-se por uma paleta monocromática para manter a ilusão de uma peça de constituição única. Por último, você pode eternizar sua peça salvando-a diretamente com a função saveFrame() que recebe o caminho[3], o nome e o tipo do arquivo de imagem a ser gravada. A extensão .png é interessante por possuir uma qualidade satisfatória mantendo um tamanho reduzido. Uma boa prática ao salvar imagens (ou qualquer outro arquivo) é definir uma condição específica para essa ação, caso contrário o Processing criará arquivos na velocidade de execução do programa. O código completo, incluindo essas últimas alterações, se encontra abaixo e, a imagem, na figura 5.49:

Código 5.16 -

float nxi = random(100);
float nyi = random(100);

void setup() {
size(700, 200);
frameRate(30);
background(255);
}

void draw() {
//background(255);
strokeWeight(2);

// Separação entre uma linha e outra (em pixels):
int sepLinhas = 2;

// Tamanho das linhas desenhadas (em pixels):
float tamLinha = 10;

// Incrementa o valor inicial dos ruídos:
nxi += 0.01;
nyi += 0.01;

// Define os ruídos locais a serem usados na angulação das retas:
float ny = nyi;
float nx = nxi;

for (int y = 0; y <= height; y += sepLinhas) {
// Incrementa e reinicia o ruído local:
ny += 0.1;
nx = nxi;

for (int x = 0; x <= width; x += sepLinhas) {
// Incrementa o ruído local:
nx += 0.1;

// Define a variável que irá determinar a inclinação das retas:
float angulo = 360 * noise(nx, ny);

// Cores baseadas na inclinação das retas:
float verm = 60 + map(angulo, 0, 360, 0, 255);
stroke(verm, 0, 0);

// Cálculo do ponto final da reta angulada:
float xf = x + tamLinha * cos(radians(angulo));
float yf = y + tamLinha * sin(radians(angulo));
line(x, y, xf, yf);
}
if (frameCount == 5) {
saveFrame("Imagem.png");
}
}
}
Figura 5.49 - Imagem final após alterações na formatação.

A comparação, lado a lado entre as diversas etapas desse projeto, figura 5.50, nos ajuda a entender que empilhar sucessivamente camadas de formatação impulsiona uma evolução na riqueza e complexidade do padrão.

Figura 5.50 - Evolução da visualização do ruído bidimensional.

5.5 Sumário

Os diferentes estilos de formatação, que algumas vezes são vistas por programadores como irrisórias no contexto do programa como um todo, não devem ser menosprezadas quando se trabalha com a computação artística. Você aprendeu diversas maneiras de enriquecer visualmente suas imagens e criar uma estética marcante através de técnicas de formatação que parecem simples, mas juntas são responsáveis por modificar profundamente a imagem. Você também foi capaz de escrever um código que utiliza de tudo[4] que você aprendeu até o momento, como condicionais, repetições, trigonometria, aleatoriedade e, por fim, a própria formatação. Isso finaliza a segunda parte do livro, indicando que você agora possui um versátil conhecimento matemático e poderosas ferramentas de programação para criar verdadeiras obras primas nascidas da sua criatividade.

Na terceira parte deste livro você investigará melhor como aplicar conceitos matemáticos aliados aos recursos que você vem estudando. Serão mostradas algumas dicas e estratégias de programação, mas o verdadeiro valor nestes últimos capítulos está no processo da projeção da criatividade e na transformação do conceitual no físico.

 

 


[1] Valdez, P. e Mehrabian, A. (1994). "Effects of Color on Emotions". Journal of Experimental Psychology General 123(4): pags.394-409.

[2] Baseado na obra Invader Fractal de Jared Tarbell.

[3] Utilizar essa função sem incluir um caminho salva a imagem no próprio diretório do programa.

[4] A programação orientada a objetos não foi utilizada por ser considerada como um tópico avançado. Ela possuí uma aplicação própria em um capítulo posterior.