Lulucat

Por qué sustituimos tres motores de geometría del resaltador por sellos

Gaoge ZhangGaoge Zhang

Los datos reales del Apple Pencil revelaron defectos que las pruebas sintéticas no detectaron y llevaron a Lulucat Notes de tres motores de contorno a un modelo de sellos de MaLiang.

Actualizado

Lulucat Notes comenzó con un renderizador de resaltador convencional: construir un contorno geométrico alrededor de la trayectoria muestreada y rellenarlo una sola vez con un color translúcido. Un único relleno evita las bandas oscuras que aparecen cuando se solapan segmentos translúcidos. También convierte cada unión, autointersección, remate y trazo corto en parte del mismo problema geométrico.

Tres generaciones de geometría personalizada eliminaron cada una una clase de defecto y dejaron al descubierto otra. El renderizador que distribuimos ahora utiliza sellos circulares suaves basados en MaLiang, un framework de dibujo de código abierto con licencia MIT creado por Harley-xk. A cambio de eliminar toda una categoría de fallos de contorno, aceptamos autointersecciones más oscuras y extremos afinados por la presión.

Escritura con resaltador azul capturada en un iPad, con ganchos finos visibles al principio y al final de varios trazos.

La captura de pantalla original del dispositivo ampliada al 228 %. Aparecen pequeños ganchos en ambos extremos de varios trazos.

La escritura real produjo ganchos que las escenas sintéticas no producían

Nuestras escenas sintéticas cubrían líneas rectas, arcos, toques cortos, retrocesos, cambios de presión y giros bruscos, y todos se renderizaban limpiamente. La escritura real en chino seguía produciendo ganchos de aproximadamente 1–3 puntos al principio y al final de muchos trazos.

Por eso añadimos un botón para guardar en la aplicación y grabamos 29 trazos completos. Cada muestra conservó su posición, radio, presión, azimut, altitud, marca temporal y banderas de estimación de UIKit. Un programa sin conexión decodificó ese JSON y compiló los mismos archivos Stroke.swift, StrokeRenderer.swift y HighlighterStrokeBuilder.swift que utiliza la aplicación. Su salida coincidió trazo por trazo con la captura del dispositivo, incluidos los ganchos.

Como el renderizador sin conexión reproducía el defecto únicamente a partir de la entrada grabada, la causa estaba en las muestras y en la geometría, no en el comportamiento de la pantalla. Ahora podíamos leer las muestras exactas que habían entrado en el renderizador.

Nuestra primera hipótesis culpaba a la orientación estimada del Pencil al tocar la superficie y al levantarlo. Los datos la rechazaron. En los 29 trazos, el azimut cambiaba como máximo 0.09 radianes y la altitud como máximo 0.02 radianes dentro de las primeras y las últimas ocho muestras, y todas las banderas de estimación eran falsas en ese iPad. La orientación se mantenía estable mientras la presión y la dirección cambiaban bruscamente.

Un umbral de presión surgió de una separación clara en los datos

La última muestra de cada trazo grabado tenía una presión de 0.000. Las muestras durante la escritura estaban en 0.01 o por encima, mientras que las registradas después de que el Pencil abandonara la superficie estaban en 0.002 o por debajo. Esas muestras en el aire a menudo continuaban durante 2–7 puntos y recorrían 0.8–9.0 puntos, y su dirección podía girar aproximadamente 90 grados alejándose del trazo escrito.

El trazo 2 muestra el mecanismo. Su cuerpo terminaba entre 143 y 180 grados. La cola de baja presión giraba después de −90 a −67 grados y recorría otros 8.5 puntos. El renderizador de contornos trataba el último punto en el aire como el extremo de referencia, así que estiraba una pieza estrecha de geometría hacia ese punto.

Las presiones medidas muestran una diferencia de casi un orden de magnitud entre la escritura y el movimiento en el aire, así que colocamos el corte dentro de esa separación:

private static let pressureFloor: CGFloat = 0.005

Solo se eliminan las muestras de baja presión de los dos extremos. Una muestra de baja presión en el centro de un trazo sigue formando parte de él. La regla sigue el límite medido entre eventos y deja intacta la trayectoria escrita.

Primer plano instrumentado antes del recorte: las muestras verdes de entrada se alejan del trazo mientras el contorno rojo forma un gancho estrecho.

Antes del recorte, el renderizador ancla el remate a las muestras en el aire. Los puntos verdes son muestras sin procesar; la línea roja es el contorno generado.

Primer plano instrumentado después del recorte: el relleno azul termina en las últimas muestras escritas y los puntos verdes de baja presión quedan fuera.

Después de recortar las muestras por debajo de 0.005, el remate termina en el último punto escrito. Este umbral de presión permanece en el renderizador de sellos actual.

El umbral eliminó los ganchos largos en el aire. Los arrastres cortos al tocar la superficie y los arcos al levantar el Pencil cuya presión se mantuvo por encima de 0.005 sobrevivieron, porque son movimiento escrito. Gestionar esos movimientos dentro de un modelo de contorno condujo a las siguientes rondas de geometría.

Tres motores de geometría desplazaron los defectos

Primera generación: un contorno, un relleno

El primer motor sensible a la presión seguía una secuencia conocida. Suavizaba una línea central, calculaba desplazamientos a izquierda y derecha, los unía en un polígono y rellenaba ese polígono una sola vez. La punta era una elipse controlada por el azimut y la altitud del Apple Pencil, por lo que su sección transversal cambiaba con la dirección de escritura. Los toques cortos tenían una forma separada, y los remates planos usaban pequeñas asimetrías para evitar huecos de winding cero.

El relleno único resolvía la acumulación de opacidad dentro de un trazo y exigía un polígono válido en cada giro. Los arrastres al tocar la superficie creaban cuñas entre el remate y el cuerpo. Los retrocesos podían crear huecos blancos en forma de media luna. Los giros bruscos podían hacer que el contorno interior se autointersectara y dejara una muesca sin rellenar.

Segunda generación: reparar el contorno cerca de sus extremos

El segundo motor conservó el contorno y añadió reparaciones locales: recorte de retrocesos, amortiguación de propiedades estimadas al principio, enderezamiento de extremos, recorte de direcciones transitorias y una pequeña extensión del remate. Cada parche abordaba un fallo reproducido. Juntos hacían que el comportamiento de los extremos dependiera de varios umbrales y ventanas.

La escritura real produjo entonces casos fuera de esas ventanas. Una ventana fija de enderezamiento de 24 puntos borraba ganchos intencionados en trazos cortos. Una ventana adaptativa conservaba esos ganchos, y los arcos al levantar el Pencil aún podían convertirse en esquinas visibles. El recorte consciente de la dirección eliminaba más movimiento transitorio, a costa de descartar aproximadamente 2.5 puntos de un inicio realmente curvado en uno de los trazos grabados.

Cada parche corregía su caso objetivo. El modelo subyacente seguía necesitando un contorno rígido que permaneciera válido con entradas curvas y ruidosas.

Tercera generación: unión de cobertura con remates cuadrados recortados

El tercer motor dejó de construir un único contorno. Dibujaba un trapecio para cada segmento y un disco en cada muestra interior dentro de una máscara en escala de grises, calculaba la unión de sus coberturas y aplicaba el color del resaltador una sola vez. Las medias lunas de los retrocesos, el oscurecimiento de las autointersecciones y los huecos de winding cero desaparecieron, porque el renderizador ya no dependía del winding de los polígonos.

La unión redondeaba los extremos, así que restauramos los remates cuadrados recortando la cobertura contra dos semiplanos de los extremos. Ese recorte corregía las líneas rectas de presión constante e introducía un defecto más grave: el cuerpo de un trazo curvado puede atravesar legítimamente un semiplano del extremo, y el recorte del remate eliminaba entonces parte del centro del trazo. Durante la entrada en directo, la dirección del extremo cambiaba constantemente, así que el área recortada se movía y el trazo parpadeaba.

Un trazo azul curvado antes de corregir el recorte del remate, con su cuerpo superior cortado por una línea diagonal; los puntos verdes muestran toda la trayectoria muestreada.

La restricción global del remate atraviesa el cuerpo de un trazo curvado.

El mismo trazo azul curvado después de añadir un filtro local de longitud de arco, con ambas ramas restauradas alrededor de la trayectoria muestreada en verde.

Restringir el recorte del remate a los elementos primitivos situados a una anchura media del extremo restaura este trazo.

Un filtro de longitud de arco hizo local el recorte del remate y reparó todos los casos grabados. Un gancho sintético extremo aún mostraba una pequeña muesca blanca donde una punta de baja presión se encontraba con el remate recortado. El tercer motor había vuelto a cambiar un fallo geométrico por otro.

MaLiang elimina el límite rígido

MaLiang construye un trazo a partir de sellos de textura repetidos, en lugar de un único contorno cerrado. Su generador de trayectorias utiliza segmentos de Bézier cuadráticos que pasan por los puntos medios de muestras adyacentes, y su renderizador de líneas coloca sellos a intervalos regulares a lo largo de esa trayectoria. El tamaño, la rotación, el color y la opacidad del sello pueden variar a lo largo del trazo.

Un sello suave no tiene un remate, una unión ni una regla de winding separados. Un cambio brusco de dirección sigue siendo una secuencia de marcas superpuestas. Los retrocesos no pueden crear un hueco vacío en un polígono, y un semiplano del extremo no puede cortar el centro de un trazo, porque el modelo no contiene ningún recorte del extremo.

Adoptamos el modelo, no el código. El HighlighterStrokeBuilder actual realiza cuatro operaciones:

  1. Eliminar solo las muestras de los extremos cuya presión sea inferior a 0.005.
  2. Suavizar las posiciones con segmentos de Bézier cuadráticos de puntos medios.
  3. Colocar un sello circular por cada punto de longitud de arco.
  4. Establecer el diámetro del sello a partir de la presión normalizada y componer los sellos con alfa source-over.

El builder pasó de 686 líneas a 190. El azimut, la altitud, la construcción del contorno, las máscaras de unión, las ventanas de dirección de los extremos, la extensión del remate y el recorte del remate abandonaron la trayectoria del resaltador. Seguimos grabando la orientación del Pencil, porque el modelo de entrada la admite, pero el propio resaltador ya no lee esos campos.

La fórmula de presión de MaLiang necesitó calibración del dispositivo

MaLiang dimensiona un sello con una fórmula equivalente a

que supone que force utiliza una parte útil del intervalo de 0 a 1. Nuestros datos del dispositivo no lo hacían. En unas 3,400 muestras del Apple Pencil, la presión mediana era 0.047 y la máxima 0.178. Introducir esos valores directamente en la fórmula hacía que la escritura normal fuera mucho más fina que el tamaño nominal del pincel.

Por eso normalizamos alrededor de la mediana observada, aplicamos después el exponente y limitamos el resultado:

La presión mediana de escritura ahora se asigna al diámetro nominal de 12 puntos. Un contacto muy ligero sigue siendo visible al 35 % del tamaño nominal, mientras que una presión fuerte deja de crecer al 120 %.

Esta calibración pertenece al dispositivo de entrada y al comportamiento del pincel, no a MaLiang. Copiar la fórmula original sin medir nuestra propia distribución de presión habría conservado la arquitectura y producido un pincel equivocado.

El alfa por sello surgió del número de superposiciones conocido

MaLiang compensa los sellos translúcidos superpuestos con la expresión empírica alpha ÷ overlapping × 2.5. Nuestro pincel tiene un diámetro nominal fijo de 12 puntos y un intervalo de sellos de 1 punto, así que su línea central recibe unas 12 capas. En nuestro caso conocemos el número de superposiciones, y la compensación se puede resolver en lugar de estimarse.

Para una opacidad objetivo de trazo y capas iguales de source-over, una opacidad de sello produce

por lo que un sello requiere

Con y , cada sello utiliza una opacidad de aproximadamente . El centro alcanza el color previsto después de composiciones repetidas, mientras que el borde recibe menos sellos y conserva una transición difuminada de uno o dos puntos.

Una hoja con diversos trazos azules de resaltador renderizados por el modelo final de sellos suaves, incluidos bucles, ganchos, marcas cortas y autointersecciones.

El renderizador final reproduciendo las trayectorias grabadas del dispositivo. No aparecen ganchos, cortes diagonales ni huecos blancos.

Los dos costes que aceptamos

Las autointersecciones se vuelven más oscuras. La composición source-over cuenta cada sello, incluidos los colocados por un recorrido anterior sobre la misma zona, y el motor de unión de cobertura lo había evitado. Aceptamos la superposición más oscura porque es predecible y mantiene intacta la forma del trazo.

Los extremos se estrechan con la presión. Un levantamiento real reduce la presión, lo que reduce el diámetro del sello, así que el trazo se afina en lugar de terminar con un remate cuadrado de anchura completa. Habíamos tratado ese remate cuadrado como un requisito durante los tres motores de geometría. Abandonarlo eliminó el recorte y la maquinaria de los extremos que estaba detrás de varios de los fallos anteriores, así que aceptamos el extremo afinado.

Por tanto, el modelo que enviamos hace menos que los anteriores. Conserva el evento de escritura medido, responde a la presión después de calibrar el dispositivo y renderiza trayectorias curvas y que se cruzan consigo mismas sin exigir un contorno globalmente válido. Una punta con forma de cincel, un remate perfectamente cuadrado y la polilínea exacta entre las muestras sin procesar son las características a las que renunciamos por ello.

El umbral de presión es lo que se transfiere más directamente a otros proyectos. El valor 0.005 surgió de la separación entre dos grupos de la entrada real: escritura en 0.01 o por encima y seguimiento en el aire en 0.002 o por debajo. Esos mismos trazos grabados mostraron dónde fallaba cada reparación geométrica. Esos fallos condujeron al modelo de sellos y a los dos costes que conlleva.