Hvorfor vi erstattet tre geometrimotorer for markeringstusj med stempler
Ekte Apple Pencil-data avdekket feil som syntetiske tester overså, og førte Lulucat Notes fra tre omrissmotorer til en MaLiang-stempelmodell.
Lulucat Notes begynte med en konvensjonell renderer for markeringstusj: bygg et geometrisk omriss rundt den samplede banen, og fyll deretter omrisset én gang med en gjennomskinnelig farge. Én fylling unngår de mørkere båndene som oppstår der gjennomskinnelige segmenter overlapper. Den legger også alle sammenføyninger, selvkryssinger, endestykker og korte strøk i det samme geometriproblemet.
Tre generasjoner med spesialtilpasset geometri fjernet hver sin type feil og avdekket en annen. Rendereren vi leverer nå bruker myke, sirkulære stempler basert på MaLiang, et MIT-lisensiert rammeverk for åpen kildekode-tegning laget av Harley-xk. For å fjerne en hel kategori med omrissfeil aksepterte vi mørkere selvkryssinger og ender som avsmalner med trykket.

Det originale skjermbildet fra enheten ved 228 % zoom. Små kroker vises i begge ender av flere strøk.
Ekte håndskrift ga kroker som syntetiske scener ikke gjorde
De syntetiske scenene våre dekket rette linjer, buer, korte trykk, tilbakesporinger, trykkendringer og skarpe svinger, og alle ble gjengitt rent. Ekte kinesisk håndskrift ga likevel kroker på omtrent 1–3 punkter i begynnelsen og slutten av mange strøk.
Derfor la vi til en lagreknapp i appen og tok opp 29 komplette strøk. Hver prøve beholdt posisjon, radius, trykk, asimut, høyde, tidsstempel og UIKit-flagg for estimering. Et frakoblet program dekodet JSON-en og kompilerte de samme filene Stroke.swift, StrokeRenderer.swift og HighlighterStrokeBuilder.swift som appen bruker. Resultatet stemte med skjermbildet fra enheten, strøk for strøk og med krokene inkludert.
Siden den frakoblede rendereren gjenskapte feilen utelukkende fra de innsamlede dataene, lå årsaken i prøvene og geometrien, ikke i skjermvisningen. Nå kunne vi lese de nøyaktige prøvene som hadde gått inn i rendereren.
Den første hypotesen vår skyldte på estimert Pencil-orientering ved nedtrykk og løft. Dataene avviste den. På tvers av alle 29 strøk endret asimut seg med høyst 0.09 radianer og høyden med høyst 0.02 radianer i de første og siste åtte prøvene, og alle estimeringsflagg var satt til false på den iPaden. Orienteringen holdt seg stabil mens trykk og retning endret seg brått.
En trykkgrense kom fra et tydelig gap i dataene
Den siste prøven i hvert innsamlede strøk hadde trykket 0.000. Prøver under skriving lå på eller over 0.01, mens prøver som ble tatt opp etter at Pencil hadde forlatt overflaten, lå på eller under 0.002. Disse prøvene i luften fortsatte ofte i 2–7 punkter og beveget seg 0.8–9.0 punkter, og retningen kunne svinge omtrent 90 grader bort fra det skrevne strøket.
Strøk 2 viser mekanismen. Selve kroppen sluttet mellom 143 og 180 grader. Den lavtrykkede halen svingte deretter fra −90 til −67 grader og beveget seg ytterligere 8.5 punkter. Omriss-rendereren behandlet det siste luftbårne punktet som det autoritative endepunktet, og strakte derfor et smalt stykke geometri mot dette punktet.
De målte trykkene viser nesten en størrelsesorden i forskjell mellom skriving og bevegelse i luften, så vi la cutoff-verdien i dette gapet:
private static let pressureFloor: CGFloat = 0.005
Bare prøver med lavt trykk i de to endene fjernes. En prøve med lavt trykk midt i et strøk forblir en del av det. Regelen følger den målte hendelsesgrensen og lar den skrevne banen være urørt.

Før trimming forankrer rendereren enden til prøver i luften. Grønne prikker er råprøver; den røde linjen er det genererte omrisset.

Etter at prøver under 0.005 er trimmet, slutter enden ved det siste skrevne punktet. Denne trykkgrensen finnes fortsatt i dagens stempelrenderer.
Grensen fjernet de lange krokene i luften. Korte drag ved nedtrykk og buer ved løft der trykket holdt seg over 0.005, overlevde fordi de var en del av skrivebevegelsen. Å håndtere disse bevegelsene i en omrissmodell førte til de neste rundene med geometri.
Tre geometrimotorer flyttet bare feilene rundt
Første generasjon: ett omriss, én fylling
Den første trykkfølsomme motoren fulgte en kjent pipeline. Den glattet ut en ryggrad, beregnet venstre og høyre forskyvning, slo dem sammen til ett polygon og fylte polygonet én gang. Spissen var en ellipse styrt av Apple Pencil-asimut og -høyde, så tverrsnittet endret seg med skriveretningen. Korte trykk hadde en egen form, og flate ender brukte små asymmetrier for å unngå lommer med winding lik null.
Den ene fyllingen løste opphopning av opasitet inne i et strøk, men krevde et gyldig polygon i hver sving. Drag ved nedtrykk laget kiler mellom enden og kroppen. Tilbakesporinger kunne lage hvite halvmånelommer. Skarpe svinger kunne få det indre omrisset til å krysse seg selv og etterlate et ufylt hakk.
Andre generasjon: reparere omrisset nær endene
Den andre motoren beholdt omrisset og la til lokale reparasjoner: trimming av tilbakesporinger, demping av tidlige estimerte egenskaper, retting av endepunktet, trimming av forbigående retning og en liten forlengelse av enden. Hver patch tok for seg en gjenskapt feil. Samlet gjorde de endepunktets oppførsel avhengig av flere terskler og vinduer.
Ekte skriving ga deretter tilfeller utenfor disse vinduene. Et fast rettevindu på 24 punkter slettet tilsiktede kroker i korte strøk. Et adaptivt vindu bevarte krokene, men buer ved løft kunne fortsatt bli synlige hjørner. Retningsbevisst trimming fjernet mer forbigående bevegelse, men kostet omtrent 2.5 punkter fra en faktisk buet start i ett innsamlet strøk.
Hver patch rettet sitt måltilfelle. Modellen under dem krevde fortsatt et hardt omriss som holdt seg gyldig under støyende, buet input.
Tredje generasjon: dekningsunion med klippede kvadratiske ender
Den tredje motoren sluttet å konstruere én enkelt kontur. Den tegnet et trapes for hvert segment og en skive ved hver indre prøve i en gråtone-maske, tok unionen av dekningen og la deretter på markeringstusjfargen én gang. Halvmånene fra tilbakesporinger, mørkere selvkryssinger og hull med winding lik null forsvant fordi rendereren ikke lenger var avhengig av polygon-winding.
Unionen rundet endene, så vi gjenopprettet kvadratiske ender ved å klippe dekningen mot to halplan ved endepunktene. Klippet fikset rette linjer med konstant trykk, men innførte en mer alvorlig feil: kroppen i et buet strøk kan legitimt krysse et halplan ved endepunktet, og endeklippen fjernet da en del av midten av strøket. Under direkte input fortsatte endepunktets retning å endre seg, slik at det klippede området flyttet seg og strøket flimret.

Den globale endebegrensningen skjærer gjennom kroppen i et buet strøk.

Ved å begrense endeklippen til primitiver innenfor en halv bredde fra endepunktet gjenopprettes dette strøket.
En buelengdeport gjorde endeklippen lokal og reparerte alle innsamlede tilfeller. En ekstrem syntetisk krok viste likevel et lite hvitt hakk der en lavtrykksspiss møtte den klippede enden. Den tredje motoren hadde igjen byttet én geometrifeil mot en annen.
MaLiang fjerner den harde grensen
MaLiang bygger et strøk av gjentatte teksturstempler i stedet for ett lukket omriss. Banegeneratoren bruker kvadratiske Bézier-segmenter som går gjennom midtpunktene til naboprøver, og linjerendereren plasserer stempler med jevn avstand langs banen. Stempelstørrelse, rotasjon, farge og opasitet kan alle variere langs strøket.
Et mykt stempel har ingen separat ende, sammenføyning eller winding-regel. En brå retningsendring forblir en serie overlappende merker. Tilbakesporinger kan ikke lage en tom polygonlomme, og et halplan ved endepunktet kan ikke skjære gjennom midten av strøket, fordi modellen ikke har noen endeklip.
Vi tok i bruk modellen, ikke koden. Den nåværende HighlighterStrokeBuilder utfører fire operasjoner:
- Fjern bare endepunktprøver med trykk under
0.005. - Glatt ut posisjonene med kvadratiske Bézier-segmenter gjennom midtpunktene.
- Plasser ett sirkulært stempel per punkt med buelengde.
- Sett stempeldiameteren ut fra normalisert trykk og komponer stemplene med source-over-alpha.
Builderen falt fra 686 linjer til 190. Asimut, høyde, konstruksjon av omriss, unionsmasker, vinduer for endepunktets retning, forlengelse av enden og endeklippen forsvant fra markeringstusjbanen. Vi registrerer fortsatt Pencil-orientering fordi inputmodellen støtter det, men selve markeringstusjen leser ikke lenger disse feltene.
MaLiangs trykkformel trengte kalibrering av enheten
MaLiang dimensjonerer et stempel med en formel som tilsvarer
som antar at force bruker en nyttig del av området fra 0 til 1. Det gjorde ikke våre enhetsdata. På tvers av omtrent 3,400 Apple Pencil-prøver var mediantrykket 0.047 og maksimum 0.178. Når disse verdiene ble matet direkte inn i formelen, ble normal skriving mye tynnere enn den nominelle penselstørrelsen.
Derfor normaliserer vi rundt den observerte medianen, bruker eksponenten og klamper resultatet:
Mediantrykket under skriving tilsvarer nå den nominelle diameteren på 12 punkter. Svært lett kontakt forblir synlig ved 35 % av nominell størrelse, mens hardt trykk slutter å øke ved 120 %.
Denne kalibreringen hører til inndataenheten og penselens oppførsel, ikke til MaLiang. Om vi hadde kopiert den opprinnelige formelen uten å måle vår egen trykkfordeling, ville arkitekturen vært bevart, men penselen blitt feil.
Alfa per stempel kom fra det kjente antallet overlappinger
MaLiang kompenserer for overlappende gjennomskinnelige stempler med det empiriske uttrykket alpha ÷ overlapping × 2.5. Penselen vår har en fast nominell diameter på 12 punkter og et stempelsteg på 1 punkt, så senterlinjen mottar omtrent 12 lag. I vårt tilfelle er antallet overlappinger kjent, og kompensasjonen kan løses i stedet for å estimeres.
For en ønsket strøkopasitet
så ett stempel krever
Med

Den endelige rendereren spiller av de innsamlede banene fra enheten. Kroker, diagonale kutt og hvite lommer er borte.
De to kostnadene vi aksepterte
Selvkryssinger blir mørkere. Source-over-komposisjon teller hvert stempel, også stempler som ble lagt ned av en tidligere passering gjennom det samme området, mens dekningsunion-motoren hadde unngått dette. Vi aksepterte den mørkere overlappingen fordi den er forutsigbar og lar strøkets form være intakt.
Endene blir smalere med trykket. Et ekte løft reduserer trykket, som reduserer stempeldiameteren, så strøket avsmalner i stedet for å ende med en kvadratisk ende i full bredde. Vi hadde behandlet den kvadratiske enden som et krav gjennom alle tre geometrimotorene. Å droppe den fjernet klippingen og endepunktmekanismen bak flere av feilene over, så vi aksepterte den avsmalnende enden.
Modellen vi leverer gjør derfor mindre enn de tidligere modellene. Den bevarer den målte skrivehendelsen, reagerer på trykk etter kalibrering av enheten og gjengir buede og selvkryssende baner uten å kreve et omriss som er globalt gyldig. En meisel formet spiss, en perfekt kvadratisk ende og den nøyaktige polylinjen mellom råprøvene er funksjonene vi ga avkall på for å få dette.
Trykkgrensen er det som enklest kan overføres til andre prosjekter. Verdien 0.005 kom fra gapet mellom to klynger i ekte input: skriving ved 0.01 og over, og luftsporing ved 0.002 og under. De samme innsamlede strøkene viste hvor hver geometrireparasjon feilet. Disse feilene førte til stempelmodellen og de to kostnadene den bærer.