Uma curva de Bézier é definida por um conjunto de pontos de controle (Pᵢ, em azul). A curva não passa pelos pontos do meio — eles apenas "puxam" sua forma através do polígono de controle (linhas tracejadas cinzas). O ponto inicial e final da curva sempre coincidem com o primeiro e o último ponto de controle.
Mova o slider t devagar com "Construção" ativada e observe as linhas acinzentadas: esse é o Algoritmo de De Casteljau. Ele interpola linearmente entre cada par de pontos consecutivos, depois interpola entre os resultados, e assim sucessivamente, até sobrar um único ponto — o B(t) em vermelho.
Fontes tipográficas (as letras que você está lendo agora são feitas de curvas de Bézier), ferramentas vetoriais como Illustrator, Figma e Inkscape, curvas de "easing" em animações e jogos, estradas e trilhos em modelagem 3D, e o path do SVG que vimos na seção de vetor vs. raster.
Arraste um ponto de controle verticalmente para mudar sua altura. Arraste o fundo do canvas para girar a câmera. Use a roda do mouse para dar zoom.
Uma curva cúbica usa 4 pontos ao longo de 1 parâmetro (t). Uma superfície bicúbica usa uma malha de 4×4 = 16 pontos (a rede de controle, linhas tracejadas), controlada por dois parâmetros independentes — u e v — segundo S(u,v) = ΣΣ Bᵢ(u)·Bⱼ(v)·Pᵢⱼ. A malha colorida que preenche a superfície usa uma cor que varia com a altura (eixo y) de cada ponto: cinza-azulado nos vales, azul no meio, vermelho nos picos — puramente visual, para facilitar enxergar o relevo.
Ative "Construção (u, v)" e mexa nos sliders u e v: 1) fixando u, o De Casteljau roda em cada uma das 4 colunas da rede, gerando 4 pontos intermediários (cinza); 2) esses 4 pontos são então interpolados com v, usando o mesmo algoritmo, resultando no ponto final S(u,v) (vermelho) — o mesmo De Casteljau da curva 2D, só que aplicado duas vezes.
É a base de carrocerias de carros, cascos de navio e superfícies orgânicas em softwares como Blender, Maya e Rhino — qualquer forma 3D suave que precise ser editada arrastando poucos pontos de controle em vez de milhares de vértices individuais.
Arraste qualquer ponto P — a curva de Bézier muda inteira, enquanto só um pedaço da B-Spline se mexe.
Os mesmos pontos de controle (ligados pelo polígono tracejado) geram uma Bézier (grau N−1, usando todos os pontos de uma vez) e uma B-Spline cúbica (que usa sempre 4 pontos vizinhos por vez, deslizando pela malha). A Bézier sempre passa pelos extremos; a B-Spline uniforme geralmente não toca em nenhum ponto de controle — ela só é "puxada" por eles.
Arraste qualquer ponto P com "Destacar controle local" ativado: na Bézier, mover qualquer ponto muda a curva inteira — controle global, porque cada ponto tem peso em todo o intervalo t ∈ [0,1]. Já na B-Spline, o trecho destacado em vermelho é a única parte afetada — controle local, porque cada ponto só influencia uma janela vizinha de 4 pontos.
A Bézier é ótima para curvas curtas — ícones, letras, um único traço de assinatura — onde editar um ponto e ver tudo mudar é aceitável. B-Splines e NURBS são preferidas para formas longas e complexas (carrocerias, traçados de estrada, curvas de animação com muitos quadros-chave), já que editar uma parte não distorce o restante da curva.
0 pixels pintados pelo algoritmo de Bresenham
Quadro 1 de 6
drawImage com um retângulo de origem) um quadro por vez e o exibe rapidamente em sequência — a mesma técnica usada em qualquer jogo 2D com personagens animados.
Em RGB, começar do preto (tela apagada) e somar luz vermelha, verde e azul caminha em direção ao branco. Em CMYK, começar do branco (papel) e somar tinta caminha em direção ao preto — cada tinta filtra uma faixa de cor da luz refletida. O diagrama acima mostra os três círculos de tinta se sobrepondo sobre o papel branco: onde C e M se cruzam vira um azul escuro; onde os três se cruzam, o resultado já deveria ser preto.
Pigmentos de tinta reais não são perfeitamente puros, então C+M+Y no máximo produz um marrom-acinzentado, não um preto neutro — e ainda encharcaria o papel com três camadas de tinta. Impressoras adicionam um cartucho de tinta preta pura (K) só para texto e sombras profundas: mais barato, mais neutro e seca mais rápido do que empilhar as três cores.
Com R, G, B normalizados entre 0 e 1: K = 1 − max(R,G,B), C = (1−R−K)/(1−K), M = (1−G−K)/(1−K), Y = (1−B−K)/(1−K). Para voltar: R = (1−C)·(1−K), G = (1−M)·(1−K), B = (1−Y)·(1−K), cada uma multiplicada por 255.
Todo material impresso — jornais, embalagens, pôsteres — separa a imagem em quatro chapas ou cartuchos: C, M, Y e K. É também por isso que cores muito vivas e saturadas na tela (gamute RGB, feito de luz) às vezes "somem" ao imprimir: o gamute CMYK, limitado pelas tintas físicas disponíveis, é menor, e alguns tons de RGB simplesmente não têm tinta equivalente.
Arraste as duas formas para testar a colisão
Arraste o guarda (azul) e o alvo (círculo) para testar a detecção
√(Gx² + Gy²) — a detecção de bordas clássica. Edite os números do kernel à mão e observe a imagem reagir em tempo real: esse mesmo produto entre kernel e vizinhança, repetido pixel a pixel, roda por trás do blur, sharpen e detecção de bordas de qualquer editor de imagem — e é também a operação central das primeiras camadas de uma rede neural convolucional (CNN).
Assim como a Bézier, uma B-Spline é gerada a partir de pontos de controle, mas em vez de um único polinômio usando todos os pontos de uma vez, ela costura vários pedaços polinomiais de grau p, cada um controlado por apenas p+1 pontos vizinhos. Essa costura é definida por um vetor de nós (knot vector) — uma sequência de valores de parâmetro que diz onde cada pedaço começa e termina.
No modo uniforme, os nós são igualmente espaçados (0, 1, 2, 3...) e a curva geralmente não toca o primeiro nem o último ponto de controle — repare os nós acinzentados fora da faixa verde na régua acima. No modo aberto/clamped, o primeiro e o último nó são repetidos p+1 vezes (multiplicidade máxima), o que "grudam" a curva aos pontos extremos — igual à Bézier. Aliás, quando o número de pontos é exatamente p+1, a B-Spline aberta é uma Bézier: ela é um caso particular.
Ative "Destacar pontos de influência local" e mexa o slider u: apenas p+1 pontos (destacados em laranja) afetam o trecho laranja da curva naquele instante — mover qualquer outro ponto não muda nada ali. Isso é controle local, a grande vantagem sobre a Bézier. Cada vez que um valor de nó se repete (multiplicidade), a curva perde um grau de suavidade nesse ponto — com multiplicidade p+1 (como nas pontas do modo clamped) a curva pode até formar um "bico".
Ligue "Racional (NURBS)" e arraste o slider de peso de um ponto selecionado: valores maiores que 1 puxam a curva na direção daquele ponto; valores menores que 1 a empurram para longe. Essa curva "racional" (Non-Uniform Rational B-Spline) é a mesma B-Spline, mas cada ponto pesa diferente na média ponderada — P(u) = Σ Nᵢwᵢ Pᵢ / Σ Nᵢwᵢ. Isso importa porque nenhuma B-Spline puramente polinomial consegue desenhar um círculo ou uma elipse exatos — mas com os pesos certos, uma NURBS consegue, com erro zero.
NURBS é o formato padrão de curvas e superfícies em softwares de CAD industrial e modelagem (Rhino, CATIA, SolidWorks, carrocerias de carros e cascos de navio), justamente por representar formas orgânicas e arcos circulares perfeitos com a mesma matemática. B-Splines não-racionais aparecem em splines de câmera e caminhos de animação em motores de jogo, em fontes vetoriais e em qualquer ferramenta de edição de curvas onde mexer numa ponta não pode distorcer a curva inteira.
A forma mais simples: em vez de 256 níveis (8 bits) por canal, arredondamos cada canal para apenas 2^bits níveis igualmente espaçados. Com 1 bit por canal sobram só 2×2×2 = 8 cores (as combinações de preto e branco puro por canal); com 8 bits voltamos à imagem original. É rápido e não exige analisar a imagem, mas desperdiça níveis em cores que nem aparecem na cena.
Em vez de espaçar os níveis uniformemente, o algoritmo Median Cut primeiro observa quais cores a imagem realmente usa: coloca todas elas numa "caixa" no espaço RGB, encontra o canal com maior variação nessa caixa, ordena as cores por esse canal e corta a caixa ao meio (pela mediana ponderada por frequência). Repete o corte na caixa mais "cheia" até ter K caixas, e a cor final de cada caixa é a média das cores que caíram nela. O resultado é uma paleta de K cores escolhida sob medida para aquela imagem — por isso costuma ficar visivelmente melhor que a quantização uniforme para o mesmo número de cores.
Reduzir cores cria banding — faixas visíveis onde deveria haver um degradê suave. O dithering de Floyd–Steinberg ataca isso espalhando o erro de cada pixel quantizado (a diferença entre a cor original e a mais próxima da paleta) para os vizinhos ainda não processados (7/16 para a direita, 3/16 diagonal inferior-esquerda, 5/16 abaixo, 1/16 diagonal inferior-direita). Isso não aumenta o número de cores usadas, mas engana o olho: padrões de pontos alternados simulam tons intermediários que a paleta não tem — a mesma técnica usada em jornais impressos em preto e branco e em imagens de 1 bit do Mac original.
"Bits/pixel (índice)" é ⌈log₂(nº de cores)⌉ — quantos bits bastam para numerar cada cor da paleta. O tamanho comprimido soma esse índice por pixel mais a própria paleta (poucos bytes). Ative "Formas + ruído" com poucos bits para ver o pior caso: uma imagem cheia de cores quase únicas comprime muito menos do que um gradiente suave.
GIF e PNG-8 armazenam uma paleta de até 256 cores e um índice por pixel; jogos retrô como NES (54 cores no total) e Game Boy (4 tons de verde) eram limitados por hardware a paletas minúsculas; e a quantização de cores segue como etapa de pré-processamento em compressão com perdas, redução de tamanho de assets e conversão de imagens para telas e-ink ou displays de baixo custo.
Antes de renderizar, todo pixel do buffer de profundidade começa em "infinito" (nada foi desenhado ainda). Ao rasterizar cada objeto, para cada pixel que ele cobre calcula-se o seu Z naquele ponto e compara-se com o Z já armazenado: se o novo Z for menor (mais perto), o buffer de cor e o buffer de profundidade são atualizados; se for maior (mais longe), o fragmento é descartado — o objeto está atrás de algo que já foi desenhado ali. No fim, cada pixel guarda o objeto mais próximo, não importa a ordem em que os objetos chegaram à GPU.
Clique em "Sem teste de profundidade" e gire a cena: cada esfera passa a ser desenhada por cima de tudo que veio antes dela na lista, mesmo quando está mais longe da câmera — o índice em um array vira "prioridade" no lugar da distância real. Repare que o buffer de profundidade também fica com saltos e recortes onde antes havia curvas suaves: sem comparação por pixel, nada garante consistência entre cor e distância. É o mesmo defeito do algoritmo do pintor (ordenar objetos inteiros do mais distante ao mais próximo, como na Seção de Iluminação): funciona bem para formas convexas isoladas, mas quebra assim que os objetos se interpenetram ou formam ciclos de oclusão (A na frente de B, B na frente de C, C na frente de A).
O Z-buffer gasta memória extra: um valor de profundidade (tipicamente 16, 24 ou 32 bits) por pixel, além da cor. Em troca, dispensa qualquer ordenação prévia dos objetos — cada triângulo pode ser desenhado em paralelo, em qualquer sequência, o que combina perfeitamente com o processamento massivamente paralelo das GPUs. Variações comuns incluem o early-Z (descartar o fragmento antes até de calcular sua cor, economizando shading) e o Z-buffer invertido (guardar 1/Z em vez de Z), usado para distribuir melhor a precisão entre objetos próximos e distantes.
Todo pipeline gráfico de tempo real — jogos, engines como Unity e Unreal, e a própria composição de janelas do sistema operacional — resolve visibilidade com Z-buffer em hardware dedicado da GPU. É a alternativa direta ao ray tracing (que resolve visibilidade por raio, testando interseções para cada pixel individualmente, como a própria cena acima) e ao algoritmo do pintor (que depende de ordenar objetos inteiros e falha em cenas com interpenetração).
Cada partícula guarda apenas um punhado de números: posição (x, y), velocidade (vx, vy), idade e tempo de vida máximo, tamanho inicial/final e a que "espécie" pertence (fogo, faísca ou fumaça). A cada quadro, forças são somadas à velocidade (v += a·dt) e a velocidade é somada à posição (x += v·dt) — o mesmo integrador simples usado em qualquer simulação física discreta. Quando a idade ultrapassa o tempo de vida, a partícula simplesmente é removida do array.
Cor, tamanho e opacidade não são fixos: todos variam com t = idade / vidaMáxima, indo de 0 (nascimento) a 1 (morte). O fogo percorre uma rampa de cor branco → amarelo → laranja → vermelho → marrom escuro (quanto mais alta a temperatura simulada, mais para o branco/azul — por isso a base da chama é mais clara que a ponta). A fumaça faz o oposto: nasce quase invisível, ganha opacidade e tamanho no início, e dissolve lentamente enquanto se expande — simulando a difusão do gás no ar.
Fogo e faíscas usam globalCompositeOperation = "lighter": em vez de a cor de cima substituir a de baixo, as cores são somadas — por isso onde muitas chamas se sobrepõem o resultado estoura para branco, como luz de verdade se acumulando. Desative o glow no checkbox para ver a diferença: sem mistura aditiva, as partículas sobrepostas apenas escurecem umas às outras (blend normal), perdendo a sensação de incandescência. Fumaça nunca usa aditivo — fumaça bloqueia luz, não emite luz, então usa mistura normal (source-over) com opacidade baixa.
Clicar na tela dispara três populações de partículas simultaneamente a partir do mesmo ponto: faíscas (rápidas, pequenas, sofrem gravidade e caem em arco), uma bola de fogo central (partículas grandes e de vida curtíssima, empurradas para fora em todas as direções) e uma pluma de fumaça que só aparece depois de um pequeno atraso — o mesmo atraso que você vê em vídeos reais de explosão, onde a fumaça sobe só depois que o clarão inicial já esfriou. Um anel de onda de choque (strokeStyle se expandindo e desvanecendo) reforça a leitura de impacto.
Motores de jogos (Unity, Unreal, Godot) e ferramentas de efeitos visuais para cinema (Houdini, Blender) têm sistemas de partículas dedicados exatamente com esses parâmetros — taxa de emissão, forças, curvas de cor/tamanho ao longo do tempo — muitas vezes acelerados por GPU para simular milhões de partículas por quadro (chuva, fumaça, magia, destroços). O princípio é o mesmo usado aqui: nenhuma partícula individual precisa ser realista, só o comportamento coletivo do sistema.
canvas.getContext('2d') (translação, rotação, escala e cisalhamento), enquanto os filtros de cor usam a propriedade ctx.filter, que aplica os mesmos filtros CSS (brightness, contrast, saturate, grayscale, blur etc.) diretamente aos pixels desenhados no canvas — sem precisar percorrer manualmente cada pixel com getImageData.
Assim como na Seção 2, a ordem importa: aqui aplicamos escala → espelhamento → cisalhamento → rotação → translação. Trocar a ordem produz resultados visualmente diferentes, porque cada matriz é aplicada no referencial deixado pela anterior — é por isso que engines gráficas sempre documentam explicitamente a ordem de composição das suas transformações.
Brilho e contraste são funções lineares aplicadas a cada canal de cor (R, G, B) independentemente; saturação e matiz (hue) primeiro convertem a cor para o espaço HSL, ajustam o componente relevante, e convertem de volta para RGB; já o desfoque (blur) é, por trás dos panos, uma convolução gaussiana — o mesmo princípio da Seção 14, só que com um kernel muito maior e gerado automaticamente a partir do raio escolhido.
O botão de download chama canvas.toDataURL('image/png'), que serializa todo o conteúdo atual do canvas — já com transformações e filtros aplicados — em uma string Base64 no formato PNG, usada para gerar um link de download temporário. Nada disso passa por um servidor: a imagem carregada, editada e exportada nunca sai do seu navegador.