Recrear MacPaint en el navegador: el código fuente de 1984 es la especificación
PixelPaint es una recreación funcional de MacPaint 1.3 que corre en tu navegador en /paint, validada comportamiento por comportamiento contra el código fuente Pascal original de Bill Atkinson, que el Computer History Museum liberó en 2010. Haz doble clic en el borrador y borra toda la ventana; luego te devuelve la herramienta que tenías antes, porque eso es justo lo que hace ChooseTool en la línea 3651 de MacPaint.p.
{.answer-block}
En 2010, el Computer History Museum, con permiso de Apple, publicó el código fuente de MacPaint 1.3: la aplicación que Bill Atkinson lanzó con el Macintosh original en enero de 1984.1 La versión liberada figura en el catálogo del museo con el número de acceso 102658076.2 En mi máquina, MacPaint.p tiene 5.804 líneas de Apple Pascal y PaintAsm.a tiene 2.738 líneas de ensamblador 68000. La línea 3 del archivo Pascal dice, íntegramente:
{ BitMap Painting Program by Bill Atkinson }
Esa publicación cambia el nivel de exigencia que se le puede imponer a una recreación. Antes, reconstruir MacPaint significaba entrecerrar los ojos ante capturas de pantalla y emuladores, y adivinar. Después, existe una verdad de referencia. Cuando decidí terminar PixelPaint como es debido, la regla que me impuse fue simple: ningún comportamiento se publica a partir de una conjetura cuando la respuesta está en un archivo que puedo leer. La licencia es no comercial y el port es de comportamiento: leí el Pascal para entender qué hace el programa y luego implementé eso en JavaScript desde cero, sin traducir jamás una línea de código.
Este artículo trata sobre lo que costó esa regla y lo que aportó. La versión corta: el código fuente es necesario e insuficiente. El comportamiento del programa vive entre líneas —en constantes, en máscaras de bits, en comentarios, en la forma de un procedimiento— y extraerlo es arqueología, no transcripción.
El código fuente no es el comportamiento
Una recreación fiel necesita tres instrumentos, y terminé usando los tres:
- El código fuente como especificación. Cada comportamiento en disputa se resolvió leyendo el procedimiento que lo implementa, citado por línea.
- Un original en ejecución como oráculo. Infinite Mac arranca Macs reales de la era System en el navegador, con el MacPaint real en el disco.5 Cuando el código fuente era ambiguo sobre la sensación —la cadencia del aerosol, la interpolación del pincel a alta velocidad—, el emulador lo zanjaba.
- Una implementación independiente como verificación cruzada. Para el formato de archivo escribí un segundo decodificador en Python, sin compartir código con la aplicación, y exigí que ambos coincidieran byte por byte en las dos direcciones.
El instrumento que no puedes usar es la memoria. Ni la mía, ni la de internet. La mayoría de lo que “todo el mundo sabe” sobre MacPaint resulta estar mal definido en el momento en que tienes que hacer aparecer un píxel en una coordenada exacta.
Qué significaba hacer doble clic en 1984
Este es un comportamiento que ninguna captura de pantalla puede revelarte. En MacPaint, hacer doble clic en una herramienta de la paleta es un comando. El despacho vive en un solo procedimiento, ChooseTool, en MacPaint.p:3651–3699:
- Borrador: borra toda la ventana y luego vuelve a la herramienta seleccionada anteriormente.
- Pincel: abre el selector de forma del pincel.
- Marco de selección: selecciona toda la ventana.
- Mano (grabber): abre Mostrar página.
- Lápiz: activa o desactiva FatBits, el zoom a nivel de píxel.
El caso del borrador tiene un detalle que solo el código fuente revela. En la línea 3643, antes de todo esto, está la guarda:
IF theTool <> eraseTool THEN prevTool := theTool;
El borrador nunca se convierte en la “herramienta anterior”. Así que cuando un doble clic borra todo, el programa te devuelve el pincel o el lápiz con el que en realidad estabas trabajando: el borrador fue una visita, no un destino. El comentario de Atkinson en la línea del retorno lo dice sin rodeos: { we wont need the eraser anymore }. Eso es diseño de interacción expresado como un único condicional, e invisible desde afuera hasta que notas que MacPaint nunca te deja varado en el borrador después de un borrado. PixelPaint implementa los cinco comportamientos de doble clic de este procedimiento, y una suite de navegador automatizada verifica cada uno de principio a fin.
Hay un segundo detalle escondido en el caso del marco de selección. Cuando el doble clic selecciona toda la ventana, el código fuente suma uno a la derecha y a la parte inferior del rectángulo antes de fijar la selección. Lo que nos lleva a los errores de desfase por uno (off-by-one).
Dos errores de desfase por uno, y quién tenía razón
A mitad del proyecto, una pasada de revisión detectó dos discrepancias entre la vista previa y el resultado real en mi build:
- El marco de selección parecía capturar un píxel menos que su vista previa de banda elástica.
- La marca del borrador era un píxel más grande que la vista previa de su cursor.
Ambos son el tipo de bug que podrías “arreglar” en treinta segundos moviendo un +1, en cualquiera de las dos direcciones. El sentido mismo de tener el código fuente es que no te toca elegir. Buscas cuál de los dos lados está mal.
El marco de selección no era un bug. Los rectángulos de QuickDraw son exclusivos por abajo y por la derecha: un rectángulo de (10,10) a (20,20) abarca diez píxeles, no once. La banda elástica y la captura en mi build ya coincidían bajo esa convención: arrastrar de 10,10 a 20,20 selecciona exactamente 10×10. Lo que la revisión había comparado en realidad era la vista previa de las herramientas de forma (que incluye correctamente el último píxel de un rango inclusivo) contra la captura exclusiva del marco de selección. Dos convenciones distintas, ambas correctas, una junto a la otra. Resolución: no cambiar nada y anotar por qué.
El borrador sí era un bug, y mío. En el original, el bloque del borrador equivale exactamente a su cursor: un cuadrado de 16×16, estampado tal cual (EraseSome, MacPaint.p:2210, usando la máscara propia del cursor de la herramienta). Mi marca se calculaba como 2*floor(size/2)+1, lo que hacía que un borrador de 8 píxeles borrara un hueco de 9 píxeles de ancho. Lo corregí para que la marca abarque exactamente size píxeles: un borrador de 8 píxeles ahora borra las columnas 16 a 23 y deja intactas la 15 y la 24, verificado píxel por píxel. Dentro de FatBits, el borrador baja a exactamente 2×2, lo cual también está en el código fuente (MacPaint.p:2214).
La regla que surgió de este par se volvió la columna vertebral del proyecto: cuando la vista previa y la acción no concuerdan, el original decide cuál de las dos miente.
La página, no el lienzo
La idea más estructural de MacPaint es fácil de pasar por alto porque es espacial. El documento no es la ventana. El documento es una página fija de 576×720 píxeles —declarada como constantes de tiempo de compilación en MacPaint.p:108–109— y el área de dibujo en pantalla es una ventana hacia ella. La mano desplaza la ventana a lo largo de la página (ScrollDoc, :2778); Mostrar página (ShowPage, :4074) aleja la vista hasta la hoja completa y te deja arrastrar el rectángulo de la ventana a un nuevo lugar. A 72 DPI, 576×720 son exactamente 8×10 pulgadas: el documento se dimensionó para el papel, no para la pantalla.
PixelPaint tenía originalmente un búfer del tamaño del viewport, lo que significaba que tenía un lienzo donde MacPaint tenía un documento. Reconstruirlo en torno al modelo real fue el cambio individual más grande del proyecto, y sacó a la luz una restricción absolutamente de 2026: iOS limita el búfer de respaldo de un lienzo a unos 16,7 megapíxeles. Reservar ingenuamente la página completa multiplicada por el zoom de FatBits exigiría 26,5 megapíxeles: un lienzo que se renderiza en blanco sin avisar en los iPads donde yo quería que esto funcionara. El port mantiene los lienzos de visualización del tamaño del viewport y aplica, en su lugar, una transformación de vista en el espacio del documento; el búfer de respaldo midió 0,42 megapíxeles con zoom de 8×, y un cuadro completo de dibujar y renderizar la página tomó 6,4 milisegundos, por debajo de un cuadro a 60 Hz. Atkinson resolvió un presupuesto de memoria de 128K con búferes ocultos fuera de pantalla;4 el port al navegador resuelve un techo de asignación oculto con una transformación. La misma disciplina, un muro distinto.
Archivos que una Mac de 1984 puede leer
Una recreación que no puede intercambiar documentos con el original es un diorama. El propio código fuente documenta el formato de archivo de MacPaint: un encabezado de 512 bytes y, después, la página como 720 líneas de barrido de 72 bytes cada una, comprimidas con PackBits, un esquema de codificación por longitud de series que el lado Pascal nunca implementa, solo declara (PackBits/UnpackBits, marcadas como EXTERNAL en MacPaint.p:420–421; el pegamento en ensamblador de MyTools.a las despacha como traps del sistema). El encabezado lleva la paleta de patrones del programa, de modo que un documento recuerda los patrones con los que fue pintado.
PixelPaint lee y escribe ese formato. La exportación pasa la página por el propio dithering de difusión de error de Atkinson para llegar a 1 bit: umbral en 128, el error de cada píxel dividido en octavos y empujado hacia seis vecinos, con dos octavos descartados a propósito, que es lo que le da al dithering de Atkinson su contraste contundente. Los dibujos en blanco y negro puros pasan sin tocarse, porque su error es idénticamente cero. Hay una circularidad grata en usar el algoritmo de dithering de Bill Atkinson para escribir el formato de archivo de Bill Atkinson.
La verificación es donde el instrumento de la implementación independiente demostró su valor. El códec PackBits de la aplicación recorre los fixtures de ida y vuelta sin cambiar un solo byte. Un archivo exportado, decodificado por la implementación aparte en Python, produjo la versión de encabezado correcta, los patrones intactos y exactamente 720 líneas de barrido de 72 bytes con cada byte consumido. Un archivo codificado por el lado Python —envuelto en MacBinary, que el importador detecta por el tipo de archivo en el offset 65— se abrió en PixelPaint con su borde y sus diagonales cayendo en los píxeles calculados. Exportar, limpiar y reimportar reprodujo el estado empaquetado con un hash idéntico. Dos implementaciones, ambas direcciones, sin código compartido.
Entre líneas
Los cortes más profundos vinieron de detalles que ninguna lista de funciones sacaría jamás a la luz: cosas que solo encuentras leyendo.
La cuadrícula es una máscara de bits. El ajuste a la cuadrícula de 8 píxeles de MacPaint no se aplica a todas las herramientas. ChooseTool decide la elegibilidad comparando el índice de la herramienta contra una constante hexadecimal en crudo, $50BF3000, con el conjunto Pascal legible por humanos dejado como comentario. El ajuste en sí es redondeo al más cercano, implementado como truncar a 8 después de sumar 4 (GridPoint, MacPaint.p:513). PixelPaint respeta el conjunto exacto de herramientas: el marco de selección, el texto, las líneas, los rectángulos, los óvalos y los polígonos se ajustan; las herramientas a mano alzada nunca lo hacen.
La restricción con Shift es más inteligente que “horizontal o vertical”. Constrain (MacPaint.p:875) ajusta una línea a 45° recortando ambos deltas al menor de los dos y, además, ajusta a horizontal o vertical pura cuando un eje domina al otro en una proporción de dos a uno. Cada clon que he visto implementa la mitad H/V y se salta el modelo de dominancia diagonal. El código fuente tiene el algoritmo completo en treinta líneas.
El patrón es la tinta. La firma de BrushPaint recibe el pincel y un patrón (MacPaint.p:2024). El pincel y la lata de aerosol no pintan en “negro”; siempre pintan a través del patrón seleccionado en ese momento. Adopté esto tal cual, y cambió la sensación de dibujar: la selección de patrón deja de ser una opción de relleno y se convierte en la pintura misma.
Trazar bordes tiene una variante oculta. Mantén presionado Shift y el desplazamiento del contorno cambia de 2 a 3, anotado en el código fuente con el comentario { asymmetric shadow } (MacPaint.p:1898). Un easter egg de una sola línea, de 1984, preservado.
El texto es tinta sólida, y el código fuente arregló mi bug. En las pruebas táctiles, el texto escrito a veces confirmaba cero píxeles. La causa: mi confirmación de texto filtraba los píxeles de los glifos a través del patrón de relleno, así que un patrón disperso se tragaba las letras sin avisar. El original nunca hace esto: el texto se dibuja como tinta sólida de primer plano, sin importar el patrón (UpdateText/PatchText, MacPaint.p:992–1106). Leer el procedimiento fue más rápido que depurar mi propia suposición, y zanjó la corrección sin discusión.
Un hallazgo más, para que conste: PaintAsm.a contiene una función llamada Monkey, el punto de enganche para el probador de estrés de entrada aleatoria que usaba el equipo del Macintosh, protegida por una bandera llamada MonkeyLives. Atkinson incluyó su arnés de pruebas en el mismo archivo que su blitter. Los artesanos dejan sus plantillas en el banco de trabajo.
Lo que dejé intacto y lo que cambié
La fidelidad fue el principio de diseño, así que las desviaciones son pocas, deliberadas y están anotadas dentro de la aplicación: el cuadro de diálogo Acerca de las enumera, tal como una edición facsímil declara sus desviaciones:
- Una paleta de 16 colores sobre el motor de 1 bit. El dithering y la ruta de exportación a .mac te dan el monocromo auténtico cuando lo quieras.
- Una pila de deshacer de 100 pasos. El original tenía exactamente un nivel de deshacer, porque Atkinson mantenía dos búferes fuera de pantalla del tamaño de la ventana —el estado actual y el estado anterior— y los intercambiaba.4 Esa fue una respuesta heroica a 128K de RAM. Recrear la limitación sería cosplay; el modelo de memoria al que respondía ya no existe.
- Tamaños de borrador seleccionables, un modo de aerosol disperso opcional y el calcado de imágenes de referencia. Añadidos, todos desactivados por defecto o claramente modernos, ninguno desplaza un comportamiento original.
Con igual deliberación, parte de la superficie del original no se portó: el ciclo de vida del documento en disco (Guardar, Guardar como, Revertir, Cerrar) pertenece a una máquina basada en disquetes y se reemplaza por autoguardado continuo más exportaciones explícitas. Pero Archivo > Imprimir sobrevive: PrintDoc (MacPaint.p:4307) cierra el menú Archivo original, y la impresión renderiza únicamente la obra, nítida al píxel, nunca la interfaz del navegador.
El bug más extraño de todo el proyecto no era, en absoluto, un problema de 1984. Guardar archivos fallaba en silencio desde hacía semanas porque mi propio script de analítica interceptaba los clics en los enlaces —incluidos los clics en URLs blob:— y el código de guardado revocaba la URL del blob de forma síncrona después del clic, antes de que el navegador iniciara la descarga. Un programa de 1984 no pelea contra su propia telemetría. Recrear uno en 2026, al parecer, sí.
Ve a dibujar algo
Los iconos de herramientas que reconocerás —el lazo, la mano, la lata de aerosol, el bote de pintura— los dibujó Susan Kare, cuya disciplina de 32×32 píxeles comenté en la serie sobre filosofía del diseño. Los comportamientos bajo ellos los escribió Bill Atkinson, que murió en junio de 2025.6 La publicación del Computer History Museum significa que su programa puede estudiarse, contrastarse y reconstruirse con honestidad en lugar de con aproximaciones, lo cual es, creo, la mejor clase de monumento para el software.
PixelPaint está en vivo en /paint, junto a las demás exploraciones interactivas de este sitio. Funciona en un iPad con el dedo. Haz doble clic en el lápiz para ver FatBits. Dibuja algo, guárdalo como archivo .mac y ten la certeza de que un Macintosh de 1984 podría abrirlo.
Preguntas frecuentes
¿Está disponible el código fuente original de MacPaint?
Sí. El Computer History Museum liberó el código fuente de MacPaint 1.3 (y la biblioteca de gráficos QuickDraw) en julio de 2010 con permiso de Apple, para uso no comercial.1 La versión liberada es la del número de acceso 102658076 del catálogo del CHM2 e incluye el programa principal en Pascal (MacPaint.p) más los archivos de soporte en ensamblador 68000. Hay un mirror oficial en GitHub bajo la cuenta del Computer History Museum.3
¿Qué es la compresión PackBits?
PackBits es el esquema de codificación por longitud de series que MacPaint usaba para comprimir documentos: cada línea de barrido se empaqueta como series literales y series repetidas, lo que funciona bien en imágenes de 1 bit llenas de espacio en blanco y patrones que se repiten. El Pascal de MacPaint declara PackBits y UnpackBits como rutinas externas (MacPaint.p:420–421) y alcanza la implementación 68000 del sistema a través de su pegamento en ensamblador. Un archivo de MacPaint es un encabezado de 512 bytes seguido de 720 filas comprimidas con PackBits de 72 bytes cada una: la página completa de 576×720.
¿Qué es el dithering de Atkinson?
El dithering de Atkinson es el algoritmo de difusión de error que Bill Atkinson ideó para convertir imágenes en escala de grises a la pantalla de 1 bit del Macintosh. Cada píxel se lleva a blanco o negro por umbral, y el error resultante se divide entre 8 y se distribuye a seis píxeles vecinos, descartando a propósito los dos octavos restantes en lugar de propagarlos. Deshacerse de parte del error es lo que les da a las imágenes con dithering de Atkinson su alto contraste característico. PixelPaint lo usa para convertir dibujos en color a 1 bit para la exportación a .mac y para la vista previa en vivo de un bit.
¿De qué tamaño es un documento de MacPaint?
576×720 píxeles, fijo, declarado como constantes en el código fuente (MacPaint.p:108–109). A los 72 DPI del Macintosh, eso son exactamente 8×10 pulgadas, una página imprimible. La pantalla nunca mostraba el documento completo de una vez: la ventana de dibujo era un viewport móvil sobre la página, desplazado con la mano o reubicado mediante Mostrar página. PixelPaint recrea el mismo modelo de documento, viewport incluido.
Fuentes
-
Leonard J. Shustek, “MacPaint and QuickDraw Source Code,” blog del Computer History Museum, 18 de julio de 2010. El anuncio de la publicación; documenta el permiso de Apple y la licencia no comercial, e incluye la historia del programa. ↩↩
-
Catálogo de la colección del Computer History Museum, “MacPaint source code,” número de acceso 102658076. ↩↩
-
Computer History Museum, Historical Source Code: MacPaint repository, GitHub. Mirror oficial de los archivos fuente liberados. ↩
-
Andy Hertzfeld, “MacPaint Evolution,” Folklore.org. Fuente primaria sobre la historia del desarrollo de MacPaint, incluidos los dos búferes fuera de pantalla del tamaño de la ventana (estado actual y anterior) detrás del dibujo sin parpadeo y el deshacer de un solo nivel. ↩↩
-
Infinite Mac: sistemas Macintosh clásicos, incluido MacPaint, emulados en el navegador. Se usó como oráculo del original en ejecución para las comparaciones de comportamiento. ↩
-
Adam Engst, “Bill Atkinson Dies from Pancreatic Cancer at 74,” TidBITS, 7 de junio de 2025. ↩