Lulucat

Per què vam substituir tres motors de geometria del ressaltador per segells

Gaoge ZhangGaoge Zhang

Les dades reals de l'Apple Pencil van revelar defectes que les proves sintètiques no detectaven i van portar Lulucat Notes de tres motors de contorn al model de segells de MaLiang.

Actualitzat

Lulucat Notes va començar amb un renderitzador de ressaltador convencional: construir un contorn geomètric al voltant del camí mostrejat i després omplir-lo una sola vegada amb un color translúcid. Un sol farciment evita les bandes més fosques que apareixen quan se superposen segments translúcids. També situa cada unió, autointersecció, acabament i traç curt dins del mateix problema geomètric.

Tres generacions de geometria a mida van eliminar cadascuna una classe de defectes i en van fer aparèixer una altra. El renderitzador que fem servir ara utilitza segells circulars tous basats en MaLiang, un framework de dibuix de codi obert amb llicència MIT creat per Harley-xk. A canvi d’eliminar tota una categoria de fallades del contorn, vam acceptar autointerseccions més fosques i extrems que s’estrenyen amb la pressió.

Escriptura amb ressaltador blau capturada en un iPad, amb ganxos prims visibles als inicis i finals de diversos traços.

La captura de pantalla original del dispositiu amb un zoom del 228 %. Es veuen petits ganxos als dos extrems de diversos traços.

L’escriptura real va produir ganxos que les escenes sintètiques no produïen

Les nostres escenes sintètiques cobrien línies rectes, arcs, tocs curts, retrocessos, canvis de pressió i girs pronunciats, i totes es renderitzaven netament. L’escriptura xinesa real encara produïa ganxos d’aproximadament 1–3 punts al principi i al final de molts traços.

Per això vam afegir un botó de desar a l’aplicació i vam enregistrar 29 traços complets. Cada mostra conservava la posició, el radi, la pressió, l’azimut, l’altitud, la marca de temps i els indicadors d’estimació d’UIKit. Un programa fora de línia descodificava aquell JSON i compilava els mateixos fitxers Stroke.swift, StrokeRenderer.swift i HighlighterStrokeBuilder.swift que utilitza l’aplicació. La seva sortida coincidia traç per traç amb la captura de pantalla del dispositiu, inclosos els ganxos.

Com que el renderitzador fora de línia reproduïa el defecte només a partir de l’entrada enregistrada, la causa es trobava en les mostres i en la geometria, no pas en el comportament de la pantalla. Ara podíem llegir les mostres exactes que havien entrat al renderitzador.

La nostra primera hipòtesi culpava l’orientació estimada del Pencil en el contacte amb la superfície i en la separació. Les dades la van rebutjar. En els 29 traços, l’azimut canviava com a màxim 0.09 radians i l’altitud com a màxim 0.02 radians dins de les vuit primeres i les vuit últimes mostres, i tots els indicadors d’estimació eren false en aquell iPad. L’orientació es mantenia estable mentre la pressió i la direcció canviaven bruscament.

Un llindar de pressió va sorgir d’un buit net en les dades

L’última mostra de cada traç enregistrat tenia una pressió de 0.000. Les mostres d’escriptura se situaven a 0.01 o per sobre, mentre que les mostres enregistrades després que el Pencil hagués deixat la superfície se situaven a 0.002 o per sota. Aquelles mostres en l’aire sovint continuaven durant 2–7 punts i recorrien 0.8–9.0 punts, i la seva direcció podia girar aproximadament 90 graus respecte del traç escrit.

El traç 2 mostra el mecanisme. El seu cos acabava entre 143 i 180 graus. La cua de baixa pressió girava llavors de −90 a −67 graus i recorria 8.5 punts més. El renderitzador de contorn tractava l’últim punt en l’aire com l’extrem de referència, de manera que estirava una peça estreta de geometria cap a aquell punt.

Les pressions mesurades deixen gairebé un ordre de magnitud entre el moviment d’escriptura i el moviment en l’aire, de manera que vam situar el tall dins d’aquest buit:

private static let pressureFloor: CGFloat = 0.005

Només s’eliminen mostres de baixa pressió als dos extrems. Una mostra de baixa pressió al mig d’un traç continua formant-ne part. La regla segueix la frontera de l’esdeveniment mesurat i deixa el camí escrit sense editar.

Primer pla instrumentat abans de retallar: les mostres d'entrada verdes s'allunyen del traç mentre el contorn vermell forma un ganxo estret.

Abans de retallar, el renderitzador ancora l’acabament a mostres en l’aire. Els punts verds són mostres en brut; la línia vermella és el contorn generat.

Primer pla instrumentat després de retallar: el farciment blau acaba en les últimes mostres escrites i els punts verds de baixa pressió queden fora.

Després de retallar les mostres per sota de 0.005, l’acabament acaba en l’últim punt escrit. Aquest llindar de pressió es manté en el renderitzador actual de segells.

El llindar va eliminar els ganxos llargs en l’aire. Les arrossegades curtes de contacte i els arcs de separació que mantenien la pressió per sobre de 0.005 van sobreviure, perquè són moviment escrit. Gestionar aquests moviments dins d’un model de contorn va conduir a les rondes següents de geometria.

Tres motors de geometria van desplaçar els defectes

Generació u: un contorn, un farciment

El primer motor sensible a la pressió seguia un flux conegut. Suavitzava una línia central, calculava desplaçaments esquerre i dret, els unia en un únic polígon i omplia aquest polígon una sola vegada. La punta era una el·lipse governada per l’azimut i l’altitud de l’Apple Pencil, de manera que la seva secció transversal canviava amb la direcció d’escriptura. Els tocs curts tenien una forma separada, i els acabaments plans utilitzaven petites asimetries per evitar bosses amb enrotllament nul.

El farciment únic resolia l’acumulació d’opacitat dins d’un traç i exigia un polígon vàlid a cada gir. Les arrossegades de contacte creaven falques entre l’acabament i el cos. Els retrocessos podien crear bosses blanques en forma de mitja lluna. Els girs pronunciats podien fer que el contorn interior s’autointersequés i deixar un buit sense omplir.

Generació dos: reparar el contorn a prop dels extrems

El segon motor conservava el contorn i hi afegia reparacions locals: retall de retrocessos, esmorteïment de propietats inicials estimades, redreçament dels extrems, retall de direccions transitòries i una petita extensió de l’acabament. Cada pedaç abordava una fallada reproduïda. Junts feien que el comportament dels extrems depengués de diversos llindars i finestres.

L’escriptura real va produir llavors casos fora d’aquestes finestres. Una finestra fixa de redreçament de 24 punts esborrava els ganxos intencionats dels traços curts. Una finestra adaptativa conservava aquests ganxos, i els arcs de separació encara es podien convertir en cantonades visibles. El retall conscient de la direcció eliminava més moviment transitori, a costa de descartar aproximadament 2.5 punts d’un inici realment corbat en un traç enregistrat.

Cada pedaç va resoldre el cas que tenia com a objectiu. El model que hi havia a sota encara requeria un contorn dur que es mantingués vàlid amb una entrada sorollosa i corba.

Generació tres: unió de cobertura amb acabaments quadrats retallats

El tercer motor va deixar de construir un únic contorn. Dibuixava un trapezi per a cada segment i un disc a cada mostra interior dins d’una màscara d’escala de grisos, en prenia la unió de cobertura i després aplicava el color del ressaltador una sola vegada. Les mitges llunes dels retrocessos, l’enfosquiment de les autointerseccions i els forats d’enrotllament nul desapareixien, perquè el renderitzador ja no depenia de l’enrotllament del polígon.

La unió arrodonia els extrems, i vam restaurar els acabaments quadrats retallant la cobertura contra dos semiplans dels extrems. Aquest retall resolia les línies rectes de pressió constant i introduïa un defecte més greu: el cos d’un traç corbat pot travessar legítimament un semiplà de l’extrem, i el retall de l’acabament eliminava llavors una part del mig del traç. Durant l’entrada en directe, la direcció de l’extrem continuava canviant, de manera que l’àrea retallada es movia i el traç parpellejava.

Traç corbat blau abans de corregir el retall de l'acabament, amb el cos superior tallat al llarg d'una línia diagonal; els punts verds mostren tot el camí mostrejat.

La restricció global de l’acabament travessa el cos d’un traç corbat.

El mateix traç corbat blau després d'afegir un filtre local de longitud d'arc, amb les dues branques restaurades al voltant del camí mostrejat verd.

Restringir el retall de l’acabament a les primitives situades dins d’una mitja amplada de l’extrem restaura aquest traç.

Un filtre de longitud d’arc va fer local el retall de l’acabament i va reparar tots els casos enregistrats. Un ganxo sintètic extrem encara mostrava un petit buit blanc on una punta de baixa pressió es trobava amb l’acabament retallat. El tercer motor havia tornat a canviar una fallada geomètrica per una altra.

MaLiang elimina la frontera rígida

MaLiang construeix un traç a partir de segells de textura repetits en lloc d’un únic contorn tancat. El seu generador de camins utilitza segments de Bézier quadràtics que passen pels punts mitjans de mostres adjacents, i el renderitzador de línies col·loca segells a un pas regular al llarg d’aquest camí. La mida, la rotació, el color i l’opacitat del segell poden variar al llarg del traç.

Un segell tou no té cap acabament, unió o regla d’enrotllament separats. Un canvi brusc de direcció continua sent una seqüència de marques superposades. Els retrocessos no poden crear una bossa buida de polígon, i un semiplà de l’extrem no pot tallar el mig d’un traç, perquè el model no conté cap retall de l’extrem.

Vam adoptar el model, no pas el codi. L’actual HighlighterStrokeBuilder fa quatre operacions:

  1. Eliminar només les mostres dels extrems amb una pressió inferior a 0.005.
  2. Suavitzar les posicions amb segments de Bézier quadràtics basats en punts mitjans.
  3. Col·locar un segell circular per cada punt de longitud d’arc.
  4. Establir el diàmetre del segell a partir de la pressió normalitzada i compondre els segells amb alfa source-over.

El builder va passar de 686 línies a 190. L’azimut, l’altitud, la construcció del contorn, les màscares d’unió, les finestres de direcció de l’extrem, l’extensió de l’acabament i el retall de l’acabament van deixar de formar part del camí del ressaltador. Encara enregistrem l’orientació del Pencil, perquè el model d’entrada la permet, però el ressaltador ja no llegeix aquests camps.

La fórmula de pressió de MaLiang necessitava calibratge del dispositiu

MaLiang dimensiona un segell amb una fórmula equivalent a

que assumeix que force utilitza una part útil de l’interval de 0 a 1. Les nostres dades del dispositiu no ho feien. En unes 3.400 mostres de l’Apple Pencil, la pressió mediana era 0.047 i la màxima era 0.178. Introduir aquests valors directament a la fórmula feia que l’escriptura normal fos molt més prima que la mida nominal del pinzell.

Per això normalitzem al voltant de la mediana observada, després apliquem l’exponent i limitem el resultat:

La pressió mediana d’escriptura ara correspon al diàmetre nominal de 12 punts. Un contacte molt lleuger continua sent visible al 35 % de la mida nominal, mentre que una pressió forta deixa de fer-lo créixer al 120 %.

Aquest calibratge pertany al dispositiu d’entrada i al comportament del pinzell, no pas a MaLiang. Copiar la fórmula original sense mesurar la nostra pròpia distribució de pressió hauria conservat l’arquitectura i hauria produït el pinzell equivocat.

L’alfa per segell va sorgir del recompte de superposicions conegut

MaLiang compensa els segells translúcids superposats amb l’expressió empírica alpha ÷ overlapping × 2.5. El nostre pinzell té un diàmetre nominal fix de 12 punts i un pas de segell d’1 punt, de manera que la seva línia central rep aproximadament 12 capes. En el nostre cas, el recompte de superposicions és conegut i la compensació es pot resoldre en lloc d’estimar-la.

Per a una opacitat de traç objectiu i capes iguals de source-over, una opacitat de segell produeix

de manera que un segell requereix

Amb i , cada segell utilitza una opacitat aproximada de . El centre assoleix el color previst després de la composició repetida, mentre que la vora rep menys segells i conserva una transició difuminada d’un o dos punts.

Full amb diversos traços de ressaltador blau renderitzats pel model final de segells tous, inclosos bucles, ganxos, marques curtes i autointerseccions.

El renderitzador final reproduint els camins enregistrats del dispositiu. No hi ha ganxos, talls diagonals ni bosses blanques.

Els dos costos que vam acceptar

Les autointerseccions es tornen més fosques. La composició source-over compta cada segell, inclosos els segells col·locats en un pas anterior sobre la mateixa àrea, i el motor d’unió de cobertura ho havia evitat. Vam acceptar la superposició més fosca perquè és predictible i manté intacta la forma del traç.

Els extrems s’estrenyen amb la pressió. Una separació real redueix la pressió, cosa que redueix el diàmetre del segell, de manera que el traç s’afua en lloc d’acabar amb un acabament quadrat d’amplada completa. Havíem tractat aquest acabament quadrat com un requisit en els tres motors de geometria. Eliminar-lo va treure el retall i la maquinària dels extrems que hi havia darrere de diverses fallades anteriors, de manera que vam acceptar l’extrem afuat.

El model que distribuïm, per tant, fa menys que els anteriors. Conserva l’esdeveniment d’escriptura mesurat, respon a la pressió després del calibratge del dispositiu i renderitza camins corbats i que es creuen a si mateixos sense requerir un contorn globalment vàlid. Una punta amb forma de cisell, un acabament perfectament quadrat i la polilínia exacta entre les mostres en brut són les característiques a les quals vam renunciar per aconseguir-ho.

De tot el que va produir la investigació, el llindar de pressió és el que es transfereix més directament a altres projectes. El valor 0.005 provenia del buit entre dos clústers de l’entrada real: escriptura a 0.01 i per sobre, seguiment en l’aire a 0.002 i per sota. Els mateixos traços enregistrats van mostrar on fallava cada reparació geomètrica. Aquelles fallades van conduir al model de segells i als dos costos que comporta.