Lulucat

Cómo construyó Kimi K3 un trazo digital de pluma estilográfica

Gaoge ZhangGaoge Zhang

Queríamos llevar a Lulucat Notes la pluma estilográfica direccional de Freeform. La especificación era una captura de pantalla de escritura a mano. Tras dos modelos, una pregunta discriminante y un plumín elíptico, la pluma de agua escribe como debe. Kimi K3 hizo la investigación, el código y la validación en Fireworks.

Actualizado

Cursiva inglesa manuscrita que lee «fountain pen» sobre una línea de caligrafía china, trazada con el plumín elíptico de una pluma estilográfica sobre papel crema: gruesa en los trazos verticales y fina en los horizontales.

Renderizado por el pipeline de Metal de Lulucat Notes del que trata este artículo.

Lulucat Notes tiene un bolígrafo normal y un resaltador. La siguiente herramienta de la hoja de ruta era una pluma de agua: la pluma estilográfica direccional que conoces de Apple Notes y Freeform, en la que los trazos verticales salen gruesos y los horizontales, finos. La especificación de esta herramienta no era un documento, sino una captura de pantalla: tres líneas de escritura a mano, con las palabras «pencilkit», «无边记» y «这种有方向的水笔能力» — esta capacidad direccional de pluma de agua.

Como el pipeline de Metal anterior, el trabajo lo hizo Kimi K3, el modelo abierto de Moonshot que se ejecuta en Fireworks: la medición, el modelado, el código y la validación. La persona aportó la captura de pantalla, respondió una pregunta y evaluó la sensación en un iPad real.

La especificación es una captura de pantalla

Una imagen estática no puede decirte por qué un trazo es fino. Solo puede decirte cuánto mide y dónde. Así que el primer paso fue medir. Escaneamos la captura de pantalla fila a fila y columna a columna, siguiendo el desplazamiento de la línea central de cada trazo para obtener su dirección; después convertimos el ancho escaneado en el ancho real usando el seno de ese ángulo:

TrazoDirecciónAncho real
Ascendente de la «l» en «pencilkit»≈ 78°27.3 px
Asta de la «k» en «pencilkit»≈ 90°28 px
Barrido descendente hacia la izquierda de 力≈ 66°25.7 px
Conectores de la cursiva≈ 8°11 px
Horizontales chinas (横)≈ 0°6–11 px

La relación entre el trazo grueso y el fino es ≈ 2.5. Ajustar al punto de 66° da : casi lineal respecto de , con el máximo en el eje vertical. En la práctica, es un plumín itálico: una ranura horizontal que la escritura horizontal nunca abre.

Versión uno: el ancho a partir de la dirección

El primer modelo era el evidente. Calculaba una dirección para cada punto de entrada, la convertía en ancho mediante la curva ajustada e incrustaba el resultado en el radio del punto al capturarlo. La dirección se estimaba de forma causal: un promedio ponderado con decaimiento exponencial de los últimos puntos del arco, calculado en el espacio del ángulo doble para que una inversión del sentido de recorrido no cancelara la estimación. Solo se usan puntos anteriores, así que el trazo en directo y el trazo confirmado coinciden byte a byte.

El constructor pasó su comprobación de unidades: los recorridos sintéticos horizontal, vertical, a 45° y de inversión produjeron los anchos teóricos — 0.99, 2.52, 2.00 y 0.99 puntos. El renderizado no necesitó código nuevo: un trazo con el radio precalculado no es más que una cadena de sellos redondos, y nuestro pipeline de point-sprite ya los dibujaba.

En el iPad, la persona tardó unos diez segundos en rechazarlo: «esto es una pluma artística, no la pluma de agua».

Dos historias caben en una imagen

¿Por qué se sentía mal? Había dos explicaciones posibles, y la captura de pantalla no podía distinguirlas:

  • Bloqueo direccional. La geometría del plumín obliga a que los trazos horizontales sean finos y los verticales, gruesos, haga lo que haga la mano. Eso es lo que implementó la versión uno.
  • Presión y velocidad. La pluma responde a la presión, y el patrón de la muestra no es más que dinámica de escritura a mano: los trazos descendentes reciben presión de forma natural, mientras que los conectores son naturalmente rápidos y ligeros.

Ambas explicaciones producen una captura con horizontales finos y verticales gruesos. La diferencia está en lo que ocurre al presionar con fuerza un trazo horizontal. La versión uno lo mantiene fino. Una pluma sensible a la presión lo vuelve grueso. Así que le hicimos una pregunta a la persona: ¿debería engrosarse un trazo horizontal cuando se presiona con fuerza?

«No. Los horizontales siguen siendo finos».

Bloqueo direccional confirmado. Pero algo más estaba mal, porque la versión uno también tenía bloqueo direccional.

La respuesta estaba en los extremos del trazo

La siguiente pista estaba en la parte superior de los ascendentes. Al ampliar las astas de la «l» y la «k» de la muestra aparecieron dos cosas: un trazo recto conserva un único ancho de principio a fin, y sus extremos son cortes diagonales planos, la forma de un plumín de cincel al levantarse. No son puntos redondos. No son estrechamientos por presión.

Recorte cercano de dos astas ascendentes manuscritas de la muestra; cada una termina arriba en un corte diagonal plano y mantiene un grosor uniforme a lo largo del asta.

Eso era lo que significaba realmente la «sensación de pluma artística». La versión uno modelaba el aspecto de la muestra — el ancho como función de la dirección estimada—, pero no la pluma. Un estimador de dirección es un sensor: tiembla con entradas ruidosas, se retrasa en las esquinas y redondea cada extremo de trazo como un círculo. Un plumín real no tiene ninguno de esos problemas porque no calcula nada. El ancho es geometría.

Versión dos: un plumín elíptico

El modelo final no tiene ningún estimador de dirección. El plumín es una elipse orientada: el eje largo es horizontal y el eje corto, fijo. Se colocan sellos de esta elipse densamente a lo largo del recorrido del trazo, y todo lo demás se desprende de la geometría:

  • Un trazo horizontal recorre el borde abierto, así que siempre tiene un ancho de : una línea fina constante, con cualquier presión. Es exactamente la especificación confirmada.

  • Un trazo vertical cruza todo el eje largo: , el extremo grueso.

  • Un trazo diagonal toma el ancho de la cuerda de la elipse perpendicular al recorrido,

  • Los extremos del trazo son cortes elípticos: los extremos planos con forma de plumín que aparecían en la muestra, sin trabajo adicional.

  • La presión escala únicamente el eje largo, , así que añade volumen de tinta a los trazos descendentes y nunca puede engrosar un horizontal.

A partir de las mediciones de la muestra mantuvimos pt y pt, la misma relación de 2.5. Los sellos están separados 0.5 puntos a lo largo del arco; con un radio corto de 0.55 puntos, la ondulación del borde en el peor caso resulta de aproximadamente una décima de píxel.

El renderizado necesitó un solo fragment shader nuevo. El formato de vértices — posición, diámetro y color — ya contenía todo: el diámetro es el eje largo y el eje corto es un uniform por pasada. El shader evalúa una SDF de elipse con la misma rampa de cobertura de medio píxel que nuestros sellos redondos; eso permite compararlo píxel a píxel con la implementación de referencia de Core Graphics (fillEllipse por sello). La validación pasó por el control habitual: un corpus sintético con dos trazos de pluma estilográfica, renderizados de las dos formas y comparados píxel a píxel — cero diferencias estructurales, y un trazo horizontal comprobado al azar midió 12 px en ambos renderizadores.

Dos renderizados de la misma palabra manuscrita «fountain» uno junto al otro: el de la izquierda usa sellos redondos con el diámetro escalado según la dirección y muestra extremos redondeados; el de la derecha usa sellos elípticos y muestra extremos planos cortados por el plumín y un ancho más estable.

Versión uno a la izquierda, versión dos a la derecha. La misma entrada manuscrita, el mismo pipeline. Los extremos cuentan la historia.

En el iPad, la nueva pluma pasó de inmediato: «好,很好» — Bien. Muy bien.

Conservar el fallo

La versión uno no acabó en la basura. Es un pincel interesante de verdad; simplemente no es una pluma de agua. Así que se envió como la primera entrada de un nuevo menú de pinceles experimentales, con el nombre que le dio la persona: 漏水的圆珠笔, el bolígrafo con fugas. La barra de herramientas recibió un botón con forma de matraz que abre una lista de texto con los experimentos; añadir el siguiente solo cuesta una línea en un registro. Un modelo rechazado no es trabajo desperdiciado si puede enviarse como un experimento claramente etiquetado.

Una velada de iteraciones

El ciclo completo — medir, modelar, construir, probar en el dispositivo, preguntar, volver a modelar, volver a construir y validar — duró una velada. Kimi K3, en Fireworks, llevó todo el lado técnico del ciclo: idear los escaneos de medición, proponer la pregunta discriminante en vez de volver a adivinar, eliminar su propio estimador de dirección cuando la evidencia cambió y ampliar el arnés de diferencias de píxeles antes de tocar la aplicación. La velocidad de inferencia de Fireworks mantuvo el ciclo interactivo: las diferencias largas de Metal y Swift, los scripts de análisis de píxeles y las herramientas para corpus llegaban lo bastante rápido como para que el cuello de botella restante fuera el juicio humano.

El patrón de colaboración fue el mismo del artículo sobre el pipeline de Metal, y volvió a funcionar: el modelo es rápido y preciso; la persona se encarga del gusto y de la verificación de extremo a extremo. Una sola frase de comentarios basada en la sensación — «pluma artística, no pluma de agua» — bastó para que el modelo localizara el error exacto de modelado y lo sustituyera por un diseño más sencillo.

El modelo correcto resultó ser más pequeño que el equivocado. La versión dos se envió con menos piezas móviles que la versión uno: sin estimador, sin ventana de suavizado y sin exponente ajustado. El plumín no calcula el ancho. El plumín es el ancho.