Porque substituímos três motores de geometria do marcador por carimbos
Dados reais do Apple Pencil revelaram falhas que os testes sintéticos não detetaram, levando o Lulucat Notes a trocar três motores de contorno por um modelo de carimbos MaLiang.
O Lulucat Notes começou com um renderizador convencional de marcador fluorescente: construir um contorno geométrico em torno do caminho amostrado e depois preencher esse contorno uma vez com uma cor translúcida. Um único preenchimento evita as faixas mais escuras que aparecem onde os segmentos translúcidos se sobrepõem. Também coloca cada junção, auto-interseção, terminação e traço curto dentro do mesmo problema geométrico.
Três gerações de geometria personalizada eliminaram cada uma uma classe de falhas e expuseram outra. O renderizador que enviamos agora usa carimbos circulares suaves baseados na MaLiang, uma framework de desenho open source licenciada sob MIT por Harley-xk. Em troca de eliminar uma categoria inteira de falhas do contorno, aceitámos auto-interseções mais escuras e terminações afuniladas pela pressão.

A captura de ecrã original do dispositivo com zoom de 228%. Pequenos ganchos aparecem nas duas extremidades de vários traços.
A escrita manual real produziu ganchos que as cenas sintéticas não produziram
As nossas cenas sintéticas abrangiam linhas retas, arcos, toques curtos, recuos, mudanças de pressão e viragens bruscas, e todas eram renderizadas sem problemas. A escrita manual chinesa real continuava a produzir ganchos com cerca de 1–3 pontos de comprimento no início e no fim de muitos traços.
Por isso, adicionámos um botão de guardar à aplicação e gravámos 29 traços completos. Cada amostra mantinha a sua posição, raio, pressão, azimute, altitude, marca temporal e sinalizadores de estimativa do UIKit. Um programa offline descodificava esse JSON e compilava os mesmos ficheiros Stroke.swift, StrokeRenderer.swift e HighlighterStrokeBuilder.swift que a aplicação usa. O resultado correspondia à captura de ecrã do dispositivo traço a traço, incluindo os ganchos.
Como o renderizador offline reproduzia a falha apenas a partir dos dados gravados, a causa estava nas amostras e na geometria, não no comportamento do ecrã. Podíamos agora ler as amostras exatas que tinham entrado no renderizador.
A nossa primeira hipótese culpava a orientação estimada do Pencil no contacto inicial e na saída da superfície. Os dados rejeitaram-na. Nos 29 traços, o azimute variava no máximo 0.09 radianos e a altitude no máximo 0.02 radianos dentro das primeiras e últimas oito amostras, e todos os sinalizadores de estimativa eram false naquele iPad. A orientação mantinha-se estável enquanto a pressão e a direção mudavam bruscamente.
Um limiar de pressão veio de um intervalo limpo nos dados
A última amostra de cada traço gravado tinha pressão 0.000. As amostras de escrita estavam em ou acima de 0.01, enquanto as amostras gravadas depois de o Pencil ter saído da superfície estavam em ou abaixo de 0.002. Essas amostras no ar continuavam frequentemente durante 2–7 pontos e percorriam 0.8–9.0 pontos, e a sua direção podia afastar-se cerca de 90 graus do traço escrito.
O traço 2 mostra o mecanismo. O seu corpo terminava entre 143 e 180 graus. A cauda de baixa pressão virava depois para −90 a −67 graus e percorria mais 8.5 pontos. O renderizador de contornos tratava o último ponto no ar como a extremidade definitiva, pelo que esticava uma faixa estreita de geometria em direção a esse ponto.
As pressões medidas deixam quase uma ordem de grandeza entre o movimento de escrita e o movimento no ar, por isso colocámos o corte dentro desse intervalo:
private static let pressureFloor: CGFloat = 0.005
Apenas se removem amostras de baixa pressão nas duas extremidades. Uma amostra de baixa pressão no meio de um traço continua a fazer parte dele. A regra segue o limite do evento medido e deixa o caminho escrito intacto.

Antes de aparar, o renderizador ancora a terminação às amostras no ar. Os pontos verdes são amostras brutas; a linha vermelha é o contorno gerado.

Depois de aparar as amostras abaixo de 0.005, a terminação acaba no último ponto escrito. Este limiar de pressão continua no atual renderizador de carimbos.
O limiar eliminou os ganchos longos no ar. Os arrastos curtos de contacto inicial e os arcos de levantamento cuja pressão se mantinha acima de 0.005 sobreviveram, porque são movimento escrito. Tratar esses movimentos dentro de um modelo de contornos conduziu às rondas seguintes de geometria.
Três motores de geometria deslocaram as falhas de lugar
Primeira geração: um contorno, um preenchimento
O primeiro motor sensível à pressão seguia um pipeline familiar. Suavizava uma linha central, calculava os deslocamentos esquerdo e direito, juntava-os num único polígono e preenchia esse polígono uma vez. A ponta era uma elipse determinada pelo azimute e pela altitude do Apple Pencil, pelo que a sua secção transversal mudava com a direção da escrita. Os toques curtos tinham uma forma separada, e as terminações planas usavam pequenas assimetrias para evitar bolsas de número de enrolamento zero.
O preenchimento único resolvia a acumulação de opacidade dentro de um traço e exigia um polígono válido em cada viragem. Os arrastos de contacto inicial criavam cunhas entre a terminação e o corpo. Os recuos podiam criar bolsas crescentes brancas. As viragens bruscas podiam fazer a parte interior do contorno auto-intersetar-se e deixar um entalhe sem preenchimento.
Segunda geração: reparar o contorno junto às extremidades
O segundo motor manteve o contorno e adicionou reparações locais: aparagem de recuos, amortecimento das propriedades estimadas iniciais, endireitamento das extremidades, aparagem da direção transitória e uma pequena extensão da terminação. Cada correção tratava uma falha reproduzida. Em conjunto, fizeram com que o comportamento das extremidades dependesse de vários limiares e janelas.
A escrita real produziu depois casos fora dessas janelas. Uma janela fixa de endireitamento de 24 pontos apagava ganchos intencionais em traços curtos. Uma janela adaptativa preservava esses ganchos, e os arcos de levantamento ainda podiam tornar-se cantos visíveis. A aparagem sensível à direção removia mais movimento transitório, ao custo de descartar cerca de 2.5 pontos de um início genuinamente curvo num traço gravado.
Cada correção resolvia o caso a que se destinava. O modelo subjacente continuava a exigir um contorno rígido que se mantivesse válido perante uma entrada ruidosa e curva.
Terceira geração: união da cobertura com terminações quadradas recortadas
O terceiro motor deixou de construir um único contorno. Desenhava um trapézio para cada segmento e um disco em cada amostra interior numa máscara em tons de cinzento, calculava a união das coberturas e depois aplicava a cor do marcador fluorescente uma vez. As crescentes de recuo, o escurecimento das auto-interseções e os buracos de número de enrolamento zero desapareceram, porque o renderizador já não dependia do número de enrolamento do polígono.
A união arredondava as extremidades, e restaurámos as terminações quadradas recortando a cobertura contra dois semiplanos nas extremidades. Esse recorte corrigiu linhas retas de pressão constante e introduziu uma falha mais grave: o corpo de um traço curvo pode atravessar legitimamente um semiplano de extremidade, e o recorte da terminação removia então parte do meio do traço. Durante a entrada em tempo real, a direção da extremidade continuava a mudar, pelo que a área recortada se movia e o traço tremeluzia.

A restrição global da terminação corta o corpo de um traço curvo.

Restringir o recorte da terminação aos elementos primitivos que ficam a uma meia largura da extremidade restaura este traço.
Um critério de comprimento de arco tornou local o recorte das terminações e corrigiu todos os casos gravados. Um gancho sintético extremo ainda mostrou um pequeno entalhe branco onde uma ponta de baixa pressão encontrava a terminação recortada. O terceiro motor tinha novamente trocado uma falha geométrica por outra.
A MaLiang elimina a fronteira rígida
A MaLiang constrói um traço a partir de carimbos de textura repetidos, em vez de um único contorno fechado. O seu gerador de caminhos usa segmentos de Bézier quadráticos que passam pelos pontos médios entre amostras adjacentes, e o seu renderizador de linhas coloca carimbos a um passo regular ao longo desse caminho. O tamanho, a rotação, a cor e a opacidade do carimbo podem variar ao longo do traço.
Um carimbo suave não tem uma terminação, junção ou regra de enrolamento separadas. Uma mudança brusca de direção continua a ser uma sequência de marcas sobrepostas. Os recuos não podem criar uma bolsa de polígono vazia, e um semiplano de extremidade não pode cortar o meio de um traço, porque o modelo não contém nenhum recorte de extremidade.
Adotámos o modelo, não o código. O HighlighterStrokeBuilder atual executa quatro operações:
- Remover apenas as amostras das extremidades cuja pressão seja inferior a
0.005. - Suavizar as posições com segmentos de Bézier quadráticos de ponto médio.
- Colocar um carimbo circular por cada ponto de comprimento de arco.
- Definir o diâmetro do carimbo a partir da pressão normalizada e compor os carimbos com alfa source-over.
O construtor passou de 686 linhas para 190. O azimute, a altitude, a construção do contorno, as máscaras de união, as janelas de direção das extremidades, a extensão das terminações e o recorte das terminações deixaram todos o caminho do marcador fluorescente. Continuamos a registar a orientação do Pencil, porque o modelo de entrada a suporta, mas o próprio marcador fluorescente já não lê esses campos.
A fórmula de pressão da MaLiang precisou de calibração para o dispositivo
A MaLiang dimensiona um carimbo com uma fórmula equivalente a
que pressupõe que force usa uma parte útil do intervalo entre 0 e 1. Os dados do nosso dispositivo não o faziam. Em cerca de 3 400 amostras do Apple Pencil, a pressão mediana era 0.047 e o máximo era 0.178. Alimentar esses valores diretamente na fórmula fazia com que a escrita normal ficasse muito mais fina do que o tamanho nominal do pincel.
Por isso, normalizamos em torno da mediana observada, aplicamos depois o expoente e limitamos o resultado:
A pressão mediana da escrita corresponde agora ao diâmetro nominal de 12 pontos. Um contacto muito leve continua visível a 35% do tamanho nominal, enquanto a pressão forte deixa de aumentar aos 120%.
Esta calibração pertence ao dispositivo de entrada e ao comportamento do pincel, não à MaLiang. Copiar a fórmula original sem medir a nossa própria distribuição de pressão teria preservado a arquitetura e produzido o pincel errado.
O alfa por carimbo veio da contagem conhecida de sobreposições
A MaLiang compensa carimbos translúcidos sobrepostos com a expressão empírica alpha ÷ overlapping × 2.5. O nosso pincel tem um diâmetro nominal fixo de 12 pontos e um passo de carimbo de 1 ponto, pelo que a sua linha central recebe cerca de 12 camadas. No nosso caso, a contagem de sobreposições é conhecida e a compensação pode ser resolvida em vez de estimada.
Para uma opacidade de traço pretendida
por isso um carimbo requer
Com

O renderizador final a reproduzir os caminhos gravados no dispositivo. Não há ganchos, cortes diagonais nem bolsas brancas.
Os dois custos que aceitámos
As auto-interseções ficam mais escuras. A composição source-over conta cada carimbo, incluindo os carimbos colocados por uma passagem anterior pela mesma área, e o motor de união das coberturas tinha evitado isso. Aceitámos a sobreposição mais escura porque é previsível e mantém a forma do traço intacta.
As extremidades estreitam com a pressão. Um levantamento real reduz a pressão, o que reduz o diâmetro do carimbo, pelo que o traço afunila em vez de terminar numa terminação quadrada à largura total. Tínhamos tratado essa terminação quadrada como um requisito ao longo dos três motores de geometria. Abandoná-la eliminou o recorte e a maquinaria das extremidades por trás de várias das falhas acima, por isso aceitámos a terminação afunilada.
O modelo que enviamos faz, portanto, menos do que os anteriores. Preserva o evento de escrita medido, responde à pressão depois da calibração do dispositivo e renderiza caminhos curvos e com auto-interseções sem exigir um contorno globalmente válido. Uma ponta em forma de cinzel, uma terminação perfeitamente quadrada e a polilinha exata entre as amostras brutas são as características de que abdicámos para isso.
O limiar de pressão é o que se transfere mais diretamente para outros projetos. O valor 0.005 veio do intervalo entre dois grupos na entrada real: escrita a 0.01 e acima, rastreio no ar a 0.002 e abaixo. Os mesmos traços gravados mostraram onde falhou cada reparação geométrica. Essas falhas conduziram ao modelo de carimbos e aos dois custos que ele acarreta.