Recriando o MacPaint no navegador: o código-fonte de 1984 é a especificação
O PixelPaint é uma recriação funcional do MacPaint 1.3 que roda no seu navegador em /paint, validada comportamento por comportamento contra o código-fonte Pascal original de Bill Atkinson, que o Computer History Museum liberou em 2010. Dê um duplo-clique na borracha e ela apaga a janela inteira e depois devolve a ferramenta que você usava antes, porque é isso que ChooseTool faz na linha 3651 do MacPaint.p.
Em 2010, o Computer History Museum, com a permissão da Apple, publicou o código-fonte do MacPaint 1.3: o aplicativo que Bill Atkinson entregou com o Macintosh original em janeiro de 1984.1 A liberação consta no catálogo do museu sob o número de tombo 102658076.2 Na minha máquina, o MacPaint.p tem 5.804 linhas de Apple Pascal e o PaintAsm.a tem 2.738 linhas de assembly 68000. A linha 3 do arquivo Pascal diz, na íntegra:
{ BitMap Painting Program by Bill Atkinson }
Essa liberação muda aquilo que se pode exigir de uma recriação. Antes dela, reconstruir o MacPaint significava apertar os olhos diante de capturas de tela e emuladores e ficar chutando. Depois dela, existe uma verdade de referência. Quando decidi terminar o PixelPaint direito, a regra que estabeleci foi simples: nenhum comportamento é lançado com base em um chute quando a resposta está num arquivo que posso ler. A licença é não comercial e o port é comportamental — li o Pascal para descobrir o que o programa faz e então implementei isso em JavaScript do zero, sem nunca traduzir uma linha de código.
Este post é sobre o que essa regra custou e o que ela rendeu. A versão curta: o código-fonte é necessário e insuficiente. O comportamento do programa vive nas entrelinhas — em constantes, em máscaras de bits, em comentários, no formato de um procedimento — e extraí-lo é arqueologia, não transcrição.
O código-fonte não é o comportamento
Uma recriação fiel precisa de três instrumentos, e acabei usando todos eles:
- O código-fonte como especificação. Cada comportamento em disputa resolvido pela leitura do procedimento que o implementa, citado por linha.
- Um original em execução como oráculo. O Infinite Mac inicializa Macs reais da era do System no navegador, com o MacPaint de verdade no disco.5 Quando o código-fonte era ambíguo quanto à sensação — a cadência do spray, a interpolação do pincel em velocidade — o emulador resolvia a questão.
- Uma implementação independente como verificação cruzada. Para o formato de arquivo, escrevi um segundo decodificador em Python, sem compartilhar código algum com o aplicativo, e exigi que os dois concordassem byte a byte nas duas direções.
O instrumento que você não pode usar é a memória. A minha, ou a da internet. A maior parte do que “todo mundo sabe” sobre o MacPaint se revela mal especificada no instante em que você precisa fazer um pixel aparecer numa coordenada exata. Honrando os antigos jogos de Pokémon sem tomá-los emprestados lê os sistemas dos jogos antigos a partir das suas descompilações, e não de wikis.
O que o duplo-clique significava em 1984
Eis um comportamento que nenhuma captura de tela consegue lhe contar. No MacPaint, dar um duplo-clique numa ferramenta da paleta é um comando. O despacho vive num único procedimento, ChooseTool, em MacPaint.p:3651–3699:
- Borracha: apaga a janela inteira e depois volta para a ferramenta selecionada anteriormente.
- Pincel: abre o seletor de formato do pincel.
- Seleção retangular: seleciona a janela inteira.
- Grabber (a mão): abre o Show Page.
- Lápis: alterna o FatBits, o zoom no nível do pixel.
O caso da borracha tem um detalhe que só o código-fonte revela. Na linha 3643, antes de tudo isso, está a guarda:
IF theTool <> eraseTool THEN prevTool := theTool;
A borracha nunca vira a “ferramenta anterior”. Então, quando um duplo-clique apaga tudo, o programa devolve o pincel ou o lápis com que você estava realmente trabalhando — a borracha era uma visitante, não um destino. O comentário de Atkinson na linha do retorno diz isso sem rodeios: { we wont need the eraser anymore }. Isso é design de interação expresso num único condicional, e é invisível de fora até você perceber que o MacPaint nunca te deixa preso na borracha depois de uma limpeza. O PixelPaint implementa todos os cinco comportamentos de duplo-clique desse procedimento, e uma suíte automatizada de navegador verifica cada um deles de ponta a ponta.
Há um segundo detalhe escondido no caso da seleção retangular. Quando o duplo-clique seleciona a janela inteira, o código-fonte soma um à direita e à base do retângulo antes de definir a seleção. O que nos leva aos erros de off-by-one.
Dois off-by-one, e quem estava certo
No meio do projeto, uma rodada de revisão sinalizou duas divergências entre prévia e resultado real no meu build:
- A seleção retangular parecia capturar um pixel a menos do que a prévia do seu contorno elástico.
- O carimbo da borracha era um pixel maior do que a prévia do seu cursor.
Ambos são o tipo de bug que você poderia “consertar” em trinta segundos ajustando um +1 — em qualquer direção. Toda a razão de ter o código-fonte é que você não pode escolher. Você vai lá e verifica qual lado está errado.
A seleção retangular não era um bug. Os retângulos do QuickDraw são exclusivos na base e na direita: um retângulo de (10,10) a (20,20) abrange dez pixels, não onze. O contorno elástico e a captura no meu build já concordavam sob essa convenção — arrastar de 10,10 a 20,20 seleciona exatamente 10×10. O que a revisão de fato comparou foi a prévia das ferramentas de forma (que corretamente inclui o pixel final de um intervalo inclusivo) contra a captura exclusiva da seleção retangular. Duas convenções diferentes, ambas corretas, lado a lado. Resolução: não mudar nada, anotar o porquê.
A borracha era um bug — meu. No original, o bloco da borracha é idêntico ao seu cursor: um quadrado de 16×16, carimbado como está (EraseSome, MacPaint.p:2210, usando a própria máscara do cursor da ferramenta). Meu carimbo era calculado como 2*floor(size/2)+1, o que fazia uma borracha de 8 pixels apagar um buraco de 9 pixels de largura. Corrigido para que o carimbo abranja exatamente size pixels: uma borracha de 8 pixels agora apaga as colunas 16 a 23 e deixa a 15 e a 24 intactas, verificado pixel a pixel. Dentro do FatBits, a borracha cai para exatamente 2×2, o que também está no código-fonte (MacPaint.p:2214).
A regra que surgiu desse par virou a espinha dorsal do projeto: quando a prévia e a ação divergem, o original decide qual das duas está mentindo. Interfaces em pixel art: caixas de texto, fontes e japonês resolve disputas do mesmo jeito, corrigindo um texto anterior da sua série com base em jogos descompilados.
A página, não a tela
A ideia mais estrutural do MacPaint é fácil de passar despercebida porque é espacial. O documento não é a janela. O documento é uma página fixa de 576×720 pixels — declarada como constantes de compilação em MacPaint.p:108–109 — e a área de desenho na tela é uma janela sobre ela. O grabber desloca a janela pela página (ScrollDoc, :2778); o Show Page (ShowPage, :4074) dá um zoom out para a folha inteira e permite arrastar o retângulo da janela para um novo ponto. A 72 DPI, 576×720 dá exatamente 8×10 polegadas: o documento foi dimensionado para o papel, não para a tela.
O PixelPaint originalmente tinha um buffer do tamanho da viewport, o que significava que ele tinha uma tela onde o MacPaint tinha um documento. Reconstruí-lo em torno do modelo real foi a maior mudança isolada do projeto, e ela trouxe à tona uma restrição totalmente de 2026: o iOS limita o backing store de um canvas em torno de 16,7 megapixels. Alocar ingenuamente a página inteira multiplicada pelo zoom do FatBits exigiria 26,5 megapixels — um canvas que renderiza em branco silenciosamente nos iPads em que eu queria que isso funcionasse. O port mantém os canvas de exibição do tamanho da viewport e aplica, em vez disso, uma transformação de visualização no espaço do documento; o backing store mediu 0,42 megapixel com zoom de 8× e um quadro completo de desenho-mais-renderização da página inteira levou 6,4 milissegundos, abaixo de um quadro a 60 Hz. Atkinson resolveu um orçamento de memória de 128K com buffers ocultos fora da tela;4 o port para navegador resolve um teto de alocação oculto com uma transformação. Mesma disciplina, parede diferente. Mundos em pixel art no iPhone acompanha essa disciplina na arte de SNES e de Game Boy, que trabalhava de dentro para fora a partir dos limites de tiles, paletas e memória.
Arquivos que um Mac de 1984 consegue ler
Uma recriação que não consegue trocar documentos com o original é um diorama. O formato de arquivo do MacPaint é documentado pelo próprio código-fonte: um cabeçalho de 512 bytes, depois a página como 720 scanlines de 72 bytes cada, comprimida com PackBits — um esquema de run-length que o lado Pascal nunca implementa, apenas declara (PackBits/UnpackBits, marcados como EXTERNAL em MacPaint.p:420–421; a cola em assembly no MyTools.a os despacha como system traps). O cabeçalho carrega a paleta de padrões do programa, então um documento lembra os padrões com que foi pintado.
O PixelPaint lê e escreve esse formato. A exportação passa a página pelo próprio dither de difusão de erro de Atkinson para chegar a 1 bit — limiar em 128, o erro de cada pixel dividido em oitavos e empurrado para seis vizinhos, com dois oitavos deliberadamente descartados, que é o que dá ao dithering de Atkinson seu contraste marcante. Desenhos em preto e branco puros passam intactos, porque seu erro é identicamente zero. Há uma circularidade agradável em usar o algoritmo de dithering de Bill Atkinson para escrever o formato de arquivo de Bill Atkinson.
A verificação é onde o instrumento da implementação independente provou seu valor. O codec PackBits interno faz round-trip das fixtures de forma byte-idêntica. Um arquivo exportado, decodificado pela implementação separada em Python, produziu a versão de cabeçalho correta, padrões intactos e exatamente 720 scanlines de 72 bytes com cada byte consumido. Um arquivo codificado pelo lado Python — embrulhado em MacBinary, que o importador detecta pelo tipo de arquivo no offset 65 — abriu no PixelPaint com sua borda e diagonais caindo nos pixels calculados. Exportar, limpar e reimportar reproduziu o estado empacotado de forma hash-idêntica. Duas implementações, as duas direções, nenhum código compartilhado.
Nas entrelinhas
Os cortes mais profundos vieram de detalhes que nenhuma lista de recursos jamais traria à tona — coisas que você só encontra lendo.
A grade é uma máscara de bits. O encaixe na grade de 8 pixels do MacPaint não se aplica a toda ferramenta. ChooseTool decide a elegibilidade testando o índice da ferramenta contra uma constante hexadecimal crua, $50BF3000, com o conjunto Pascal legível por humanos deixado para trás como comentário. O encaixe em si é arredondamento para o mais próximo, implementado como truncar-para-8 depois de somar 4 (GridPoint, MacPaint.p:513). O PixelPaint respeita o conjunto exato de ferramentas: seleção retangular, texto, linhas, retângulos, ovais e polígonos encaixam; as ferramentas de mão livre nunca.
A restrição com Shift é mais inteligente do que só horizontal-ou-vertical. Constrain (MacPaint.p:875) encaixa uma linha em 45° limitando ambos os deltas ao menor deles — e adicionalmente encaixa em horizontal ou vertical puro quando um eixo domina o outro na proporção de dois para um. Todo clone que já vi implementa a metade H/V e ignora o modelo de dominância diagonal. O código-fonte tem o algoritmo completo em trinta linhas.
O padrão é a tinta. A assinatura de BrushPaint recebe o pincel e um padrão (MacPaint.p:2024). O pincel e o spray não pintam em “preto”; eles pintam através do padrão selecionado no momento, sempre. Adotei isso exatamente, e mudou a sensação de desenhar — a seleção de padrão deixa de ser uma opção de preenchimento e passa a ser a própria tinta.
O Traçar Bordas tem uma variante oculta. Segure Shift e o deslocamento do contorno muda de 2 para 3, anotado no código-fonte com o comentário { asymmetric shadow } (MacPaint.p:1898). Um easter egg de uma linha, de 1984, preservado.
O texto é tinta sólida, e o código-fonte corrigiu meu bug. Nos testes por toque, o texto digitado às vezes gravava zero pixel. A causa: minha gravação de texto filtrava os pixels dos glifos através do padrão de preenchimento, então um padrão esparso engolia as letras silenciosamente. O original nunca faz isso — o texto é desenhado como tinta sólida de primeiro plano, independentemente do padrão (UpdateText/PatchText, MacPaint.p:992–1106). Ler o procedimento foi mais rápido do que depurar minha própria suposição, e resolveu a correção sem margem para discussão.
Mais um achado, para constar: o PaintAsm.a contém uma função chamada Monkey — o ponto de entrada para o testador de estresse por entrada aleatória que a equipe do Macintosh usava, protegido por uma flag chamada MonkeyLives. Atkinson entregou seu arcabouço de testes no mesmo arquivo que seu blitter. Artesãos deixam seus gabaritos sobre a bancada. Movimento em pixel art: caminhadas, câmeras e portas no iPhone encontra uma sobra parecida em Pokémon Emerald: uma câmera de bicicleta pronta, entregue desligada.
O que deixei intacto e o que mudei
A fidelidade era o princípio de design, então os desvios são poucos, deliberados e registrados dentro do aplicativo — a caixa de diálogo Sobre os lista, do jeito que uma edição fac-símile revela seus desvios:
- Uma paleta de 16 cores sobre o motor de 1 bit. O dither e o caminho de exportação .mac lhe entregam o monocromático autêntico sempre que você quiser.
- Uma pilha de 100 níveis de desfazer. O original tinha exatamente um nível de desfazer, porque Atkinson mantinha dois buffers fora da tela do tamanho da janela — estado atual e estado anterior — e os alternava.4 Era uma resposta heroica a 128K de RAM. Recriar a limitação seria cosplay; o modelo de memória que ela respondia não existe mais.
- Tamanhos de borracha selecionáveis, um modo opcional de spray espalhado e o decalque a partir de imagem de referência. Acréscimos, todos desativados por padrão ou claramente modernos, nenhum deles deslocando um comportamento original.
Com igual deliberação, parte da superfície do original não foi portada: o ciclo de vida de documento em disco (Save, Save As, Revert, Close) pertence a uma máquina baseada em disquete e foi substituído por autosave contínuo mais exportações explícitas. Mas o File > Print sobrevive — PrintDoc (MacPaint.p:4307) encerra o menu File original, e a impressão renderiza somente a arte, nítida no pixel, nunca o chrome do navegador.
O bug mais estranho de todo o projeto não era, de forma alguma, um problema de 1984. Salvar arquivos falhava silenciosamente havia semanas porque meu próprio script de analytics interceptava os cliques em âncoras — inclusive cliques em URLs blob: — e o código de salvamento revogava a blob URL de forma síncrona logo após o clique, antes de o navegador iniciar o download. Um programa de 1984 não briga com a própria telemetria. Recriar um em 2026, aparentemente, briga.
Vá desenhar alguma coisa
Os ícones das ferramentas que você vai reconhecer — laço, grabber, lata de spray, balde de tinta — foram desenhados por Susan Kare, cuja disciplina de 32×32 pixels comentei na série sobre filosofia de design. Os comportamentos por trás deles foram escritos por Bill Atkinson, que morreu em junho de 2025.6 A liberação do Computer History Museum significa que o programa dele pode ser estudado, conferido e reconstruído de forma honesta, em vez de aproximada — o que é, eu acho, o melhor tipo de monumento para um software.
O PixelPaint está no ar em /paint, ao lado das outras explorações interativas deste site. Funciona num iPad com o dedo. Dê um duplo-clique no lápis para ver o FatBits. Desenhe alguma coisa, salve como um arquivo .mac e saiba que um Macintosh de 1984 conseguiria abri-lo.
Perguntas frequentes
O código-fonte original do MacPaint está disponível?
Sim. O Computer History Museum liberou o código-fonte do MacPaint 1.3 (e a biblioteca gráfica QuickDraw) em julho de 2010, com a permissão da Apple, para uso não comercial.1 O acervo é o de número de tombo 1026580762 do catálogo do CHM e inclui o programa Pascal principal (MacPaint.p) mais os arquivos de apoio em assembly 68000. Um espelho oficial está no GitHub sob a conta do Computer History Museum.3
O que é a compressão PackBits?
PackBits é o esquema de codificação run-length que o MacPaint usava para comprimir documentos: cada scanline é empacotada como sequências literais e sequências de repetição, o que funciona bem em imagens de 1 bit cheias de espaço em branco e padrões repetidos. O Pascal do MacPaint declara PackBits e UnpackBits como rotinas externas (MacPaint.p:420–421) e chega à implementação 68000 do sistema por meio de sua cola em assembly. Um arquivo MacPaint é um cabeçalho de 512 bytes seguido de 720 linhas comprimidas com PackBits de 72 bytes cada — a página inteira de 576×720.
O que é o dithering de Atkinson?
O dithering de Atkinson é o algoritmo de difusão de erro que Bill Atkinson concebeu para converter imagens em tons de cinza para o display de 1 bit do Macintosh. Cada pixel é limiarizado para preto ou branco, e o erro resultante é dividido por 8 e distribuído para seis pixels vizinhos — com os dois oitavos restantes intencionalmente descartados, em vez de propagados. Descartar parte do erro é o que dá às imagens tratadas com dithering de Atkinson seu alto contraste característico. O PixelPaint o usa para converter desenhos coloridos para 1 bit na exportação .mac e para a prévia ao vivo em um bit.
Qual é o tamanho de um documento MacPaint?
576×720 pixels, fixo — declarado como constantes no código-fonte (MacPaint.p:108–109). Nos 72 DPI do Macintosh isso dá exatamente 8×10 polegadas, uma página imprimível. A tela nunca mostrava o documento inteiro de uma vez: a janela de desenho era uma viewport móvel sobre a página, deslocada com o grabber ou reposicionada pelo Show Page. O PixelPaint recria o mesmo modelo de documento, viewport e tudo.
Fontes
-
Leonard J. Shustek, “MacPaint and QuickDraw Source Code,” blog do Computer History Museum, 18 de julho de 2010. O anúncio da liberação; documenta a permissão da Apple e a licença não comercial, e inclui a história do programa. ↩↩
-
Catálogo do acervo do Computer History Museum, “MacPaint source code,” número de tombo 102658076. ↩↩
-
Computer History Museum, Historical Source Code: MacPaint repository, GitHub. Espelho oficial dos arquivos de código-fonte liberados. ↩
-
Andy Hertzfeld, “MacPaint Evolution,” Folklore.org. Fonte primária para a história do desenvolvimento do MacPaint, incluindo os dois buffers fora da tela do tamanho da janela (estado atual e estado anterior) por trás do desenho sem cintilação e do desfazer de um nível. ↩↩
-
Infinite Mac — sistemas Macintosh clássicos, incluindo o MacPaint, emulados no navegador. Usado como o oráculo do original em execução para comparações de comportamento. ↩
-
Adam Engst, “Bill Atkinson Dies from Pancreatic Cancer at 74,” TidBITS, 7 de junho de 2025. ↩