Miksi korvasimme kolme korostuskynän geometriamoottoria leimoilla
Aidot Apple Pencil -näytteet paljastivat synteettisiltä testeiltä jääneet virheet ja veivät Lulucat Notesin kolmesta ääriviivamoottorista MaLiang-leimamalliin.
Lulucat Notes aloitti tavanomaisella korostuskynän renderöijällä: rakensimme näytteistetyn polun ympärille geometrisen ääriviivan ja täytimme sen kerran läpikuultavalla värillä. Yksi täyttö estää tummemmat juovat, joita syntyy läpikuultavien segmenttien limittyessä. Samalla jokainen liitos, itseleikkaus, pääty ja lyhyt veto päätyy saman geometriaongelman osaksi.
Kolme itse tehtyä geometriaversiota poisti kukin yhden virheluokan ja paljasti toisen. Nykyinen renderöijä käyttää MaLiang-pohjaisia pehmeitä pyöreitä leimoja. MaLiang on Harley-xk:n MIT-lisensoima avoimen lähdekoodin piirtojärjestelmä. Kun poistimme kokonaisen ääriviivavirheiden luokan, hyväksyimme tummemmat itseleikkaukset ja paineen mukaan kapenevat päät.

Alkuperäinen laitteen kuvakaappaus 228 %:n suurennoksella. Useiden vetojen molemmissa päissä näkyy pieniä koukkuja.
Aito käsiala tuotti koukkuja, joita synteettiset näkymät eivät tuottaneet
Synteettiset näkymämme kattoivat suorat viivat, kaaret, lyhyet napautukset, takaisinvedot, paineen muutokset ja jyrkät käännökset, ja kaikki renderöityivät siististi. Aito kiinankielinen käsiala tuotti silti monen vedon alkuun ja loppuun noin 1–3 pisteen pituisia koukkuja.
Siksi lisäsimme sovellukseen tallennuspainikkeen ja tallensimme 29 kokonaista vetoa. Jokainen näyte säilytti sijaintinsa, säteensä, paineensa, atsimuuttinsa, korkeutensa, aikaleimansa ja UIKitin estimointiliput. Offline-ohjelma purki kyseisen JSONin ja käänsi samat Stroke.swift-, StrokeRenderer.swift- ja HighlighterStrokeBuilder.swift-tiedostot, joita sovellus käyttää. Sen tulos vastasi laitteen kuvakaappausta veto vedolta, koukut mukaan lukien.
Koska offline-renderöijä toisti vian pelkän tallennetun syötteen perusteella, syy oli näytteissä ja geometriassa eikä näytön toiminnassa. Pystyimme nyt lukemaan renderöijälle päätyneet tarkat näytteet.
Ensimmäinen hypoteesimme epäili kosketuksen alussa ja irrotuksessa arvioitua Pencilin suuntaa. Data kumosi sen. Kaikissa 29 vedossa atsimuutti muuttui ensimmäisen ja viimeisen kahdeksan näytteen aikana enintään 0.09 radiaania ja korkeus enintään 0.02 radiaania, ja jokainen estimointilippu oli kyseisellä iPadilla epätosi. Suunta pysyi vakaana, vaikka paine ja kulkusuunta muuttuivat jyrkästi.
Paineen alaraja löytyi datan selkeästä aukosta
Jokaisen tallennetun vedon viimeisen näytteen paine oli 0.000. Kirjoitettaessa otetut näytteet olivat vähintään 0.01:n suuruisia, kun taas näytteet, jotka tallentuivat Pencilin irrottua pinnasta, olivat enintään 0.002. Nämä ilmassa otetut näytteet jatkuivat usein 2–7 pistettä ja kulkivat 0.8–9.0 pistettä, ja niiden suunta saattoi kääntyä noin 90 astetta kirjoitetusta vedosta poispäin.
Veto 2 näyttää mekanismin. Sen varsinainen runko päättyi 143–180 asteen välillä. Matalapaineinen häntä kääntyi sen jälkeen −90:stä −67 asteeseen ja kulki vielä 8.5 pistettä. Ääriviivarenderöijä käsitteli viimeistä ilmassa otettua pistettä määräävänä päätepisteenä, joten se venytti kapean geometrian osan kohti tuota pistettä.
Kirjoittamisen ja ilmassa tapahtuvan liikkeen mitatuissa paineissa on lähes yhden suuruusluokan ero, joten sijoitimme katkaisun tähän väliin:
private static let pressureFloor: CGFloat = 0.005
Vain päissä olevat matalapaineiset näytteet poistetaan. Vedon keskellä oleva matalapaineinen näyte pysyy osana vetoa. Sääntö seuraa mitattua tapahtumarajaa eikä muokkaa kirjoitettua polkua.

Ennen trimmausta renderöijä ankkuroidaan päädyn ilmassa otettuihin näytteisiin. Vihreät pisteet ovat raakanäytteitä; punainen viiva on muodostettu ääriviiva.

Kun alle 0.005:n näytteet on trimmattu, pääty päättyy viimeiseen kirjoitettuun pisteeseen. Tämä paineen alaraja on edelleen käytössä nykyisessä leimarenderöijässä.
Alaraja poisti pitkät ilmassa syntyneet koukut. Lyhyet kosketusvedot ja irrotuskaaret, joiden paine pysyi yli 0.005:n, säilyivät, koska ne ovat kirjoitettua liikettä. Niiden käsittely ääriviivamallissa johti seuraaviin geometriakierroksiin.
Kolme geometriamoottoria siirteli virheitä paikasta toiseen
Ensimmäinen sukupolvi: yksi ääriviiva, yksi täyttö
Ensimmäinen paineherkkä moottori seurasi tuttua putkea. Se tasoitti keskiviivan, laski vasemman ja oikean siirtymän, yhdisti ne yhdeksi polygoniksi ja täytti polygonin kerran. Kynänkärki oli Apple Pencilin atsimuutin ja korkeuden ohjaama ellipsi, joten sen poikkileikkaus muuttui kirjoitussuunnan mukana. Lyhyillä napautuksilla oli oma muotonsa, ja litteissä päädyissä käytettiin pieniä epäsymmetrioita nollakierretaskujen välttämiseksi.
Yksi täyttö ratkaisi peittävyyden kertymisen vedon sisällä, mutta se vaati kelvollisen polygonin jokaisessa käännöksessä. Kosketukseen tulon vedot loivat kiiloja päädyn ja rungon väliin. Takaisinvedot saattoivat synnyttää valkoisia sirppimäisiä aukkoja. Jyrkät käännökset saattoivat saada sisemmän ääriviivan leikkaamaan itsensä ja jättää täyttämättömän loven.
Toinen sukupolvi: ääriviivan korjaaminen sen päiden läheltä
Toinen moottori säilytti ääriviivan ja lisäsi paikallisia korjauksia: takaisinvetojen trimmausta, varhaisten arvioitujen ominaisuuksien vaimennusta, päätepisteen suoristamista, hetkellisen suunnan trimmausta ja pientä päädyn pidennystä. Jokainen paikkaus korjasi toistetun virheen. Yhdessä ne tekivät päätyjen käyttäytymisestä riippuvaista useista kynnyksistä ja ikkunoista.
Aito kirjoittaminen tuotti sitten tapauksia näiden ikkunoiden ulkopuolelta. Kiinteä 24 pisteen suoristusikkuna pyyhki lyhyiden vetojen tarkoitukselliset koukut. Mukautuva ikkuna säilytti nämä koukut, mutta irrotuskaaret saattoivat silti muuttua näkyviksi kulmiksi. Suunnan huomioiva trimmaus poisti enemmän hetkellistä liikettä, mutta yhden tallennetun vedon aidosti kaartuvasta alusta katosi noin 2.5 pistettä.
Jokainen paikkaus korjasi oman kohdetapauksensa. Niiden alla oleva malli vaati silti kovaa ääriviivaa, jonka piti pysyä kelvollisena kohinaisella ja kaartuvalla syötteellä.
Kolmas sukupolvi: peittojen unioni ja leikatut neliömäiset päädyt
Kolmas moottori lopetti yhden ääriviivan muodostamisen. Se piirsi kullekin segmentille puolisuunnikkaan ja jokaisen sisemmän näytteen kohdalle ympyrän harmaasävymaskiin, muodosti niiden peittojen unionin ja lisäsi korostuskynän värin kerran. Takaisinvetojen sirpit, itseleikkausten tummuminen ja nollakierroksen reiät katosivat, koska renderöijä ei enää riippunut polygonin kierrosta.
Unioni pyöristi päät, joten palautimme neliömäiset päädyt leikkaamalla peiton kahden päätepisteen puolitasojen mukaan. Tämä leikkaus korjasi tasapaineiset suorat viivat, mutta toi mukanaan vakavamman vian: kaartuvan vedon runko voi aivan oikein ylittää päätepisteen puolitasoalueen, jolloin päädyn leikkaus poisti osan vedon keskeltä. Reaaliaikaisen syötteen aikana päätepisteen suunta muuttui jatkuvasti, joten leikattu alue liikkui ja veto välkkyi.

Globaali päätyrajoite leikkaa kaartuvan vedon rungon läpi.

Kun päädyn leikkaus rajoitetaan yhden puolileveyden etäisyydellä päätepisteestä oleviin primitiiveihin, tämä veto palautuu.
Kaarenpituusrajoitus teki päädyn leikkauksesta paikallisen ja korjasi kaikki tallennetut tapaukset. Äärimmäinen synteettinen koukku osoitti silti pienen valkoisen loven kohdassa, jossa matalapaineinen kärki liittyi leikattuun päätyyn. Kolmas moottori oli jälleen vaihtanut yhden geometriavirheen toiseen.
MaLiang poistaa kovan rajan
MaLiang rakentaa vedon toistuvista tekstuurileimoista yhden suljetun ääriviivan sijaan. Sen polun generaattori käyttää toisen asteen Bézier-segmenttejä, jotka kulkevat vierekkäisten näytteiden keskipisteiden kautta, ja sen viivarenderöijä sijoittaa leimat säännöllisin välein polulle. Leiman koko, kierto, väri ja peittävyys voivat kaikki vaihdella vedon aikana.
Pehmeällä leimalla ei ole erillistä päätyä, liitosta tai winding-sääntöä. Jyrkkä suunnanmuutos säilyy päällekkäisten merkkien sarjana. Takaisinvedot eivät voi luoda tyhjää polygonitaskua, eikä päätepisteen puolitaso voi leikata vedon keskiosaa, koska mallissa ei ole päädyn leikkausta.
Otimme käyttöön mallin, emme koodia. Nykyinen HighlighterStrokeBuilder suorittaa neljä operaatiota:
- Poista vain päätepisteiden näytteet, joiden paine on alle
0.005. - Tasoita sijainnit näytteiden keskipisteiden kautta kulkevilla toisen asteen Bézier-segmenteillä.
- Sijoita yksi pyöreä leima jokaista kaaren pituuden pistettä kohden.
- Aseta leiman halkaisija normalisoidun paineen perusteella ja yhdistä leimat source-over-alfalla.
Builder kutistui 686 rivistä 190:een. Atsimuutti, korkeus, ääriviivan rakentaminen, unionimaskit, päätepisteen suuntaikkunat, päädyn pidennys ja päädyn leikkaus poistuivat korostuskynän polusta. Tallennamme edelleen Apple Pencilin suunnan, koska syötemalli tukee sitä, mutta itse korostuskynä ei enää lue näitä kenttiä.
MaLiangin painekaava tarvitsi laitteen kalibroinnin
MaLiang määrittää leiman koon kaavalla, joka vastaa seuraavaa:
Kaava olettaa, että force käyttää hyödyllistä osaa välillä 0–1. Noin 3,400 Apple Pencil -näytteessä paineen mediaani oli 0.047 ja enimmäisarvo 0.178. Kun nämä arvot syötettiin kaavaan suoraan, tavallinen kirjoitus oli paljon nimellistä sivellinkokoa ohuempaa.
Siksi normalisoimme paineen havaitun mediaanin ympärille, käytimme sen jälkeen eksponenttia ja rajasimme tuloksen:
Kirjoitetun paineen mediaani vastaa nyt nimellistä 12 pisteen halkaisijaa. Erittäin kevyt kosketus pysyy näkyvänä 35 %:ssa nimelliskoosta, kun taas voimakas paine lakkaa kasvamasta 120 %:ssa.
Tämä kalibrointi kuuluu syöttölaitteelle ja siveltimen käyttäytymiselle, ei MaLiangille. Alkuperäisen kaavan kopioiminen ilman oman painejakauman mittaamista olisi säilyttänyt arkkitehtuurin mutta tuottanut väärän siveltimen.
Leimakohtainen alfa syntyi tunnetusta päällekkäisyyksien määrästä
MaLiang kompensoi päällekkäisiä läpikuultavia leimoja empiirisellä lausekkeella alpha ÷ overlapping × 2.5. Siveltimemme nimellinen halkaisija on 12 pistettä ja leima-askel 1 piste, joten sen keskilinja saa noin 12 kerrosta. Meidän tapauksessamme päällekkäisyyksien määrä tunnetaan, joten kompensaatio voidaan ratkaista arvaamisen sijaan.
Kun tavoitevedon peittävyys on
joten yksi leima vaatii seuraavan:
Kun

Lopullinen renderöijä toistaa tallennetut laitteen polut. Koukut, diagonaaliset leikkaukset ja valkoiset aukot puuttuvat.
Kaksi hyväksymäämme kustannusta
Itseleikkaukset tummuvat. source-over-yhdistely laskee mukaan jokaisen leiman, myös aiemman saman alueen läpi tehdyn kierroksen asettamat leimat, ja peittouniomoottori oli välttänyt tämän. Hyväksyimme tummemman päällekkäisyyden, koska se on ennustettava ja säilyttää vedon muodon.
Päädyt kapenevat paineen mukana. Aito irrotus vähentää painetta, mikä pienentää leiman halkaisijaa, joten veto kapenee sen sijaan, että se päättyisi täysleveänä neliömäisenä päätynä. Olimme käsitelleet tätä neliömäistä päätyä vaatimuksena kaikissa kolmessa geometriamoottorissa. Sen hylkääminen poisti edellä kuvattujen virheiden taustalla olleen leikkaus- ja päätepistemekanismin, joten hyväksyimme kapenevan pään.
Toimittamamme malli tekee siis vähemmän kuin sitä edeltäneet. Se säilyttää mitatun kirjoitustapahtuman, reagoi paineeseen laitteen kalibroinnin jälkeen ja renderöi kaartuvat sekä itsensä leikkaavat polut vaatimatta maailmanlaajuisesti kelvollista ääriviivaa. Taltanmuotoinen kynänkärki, täysin neliömäinen pääty ja raakanäytteiden välinen tarkka murtoviiva ovat ominaisuuksia, joista luovuimme tämän vuoksi.
Paineen alaraja on tämän tutkimuksen tuloksista suoraan siirrettävissä muihin projekteihin. Arvo 0.005 syntyi aidon syötteen kahden ryhmän välisestä aukosta: kirjoittaminen tapahtui arvolla 0.01 tai sitä suuremmalla, ilmassa seuranta arvolla 0.002 tai sitä pienemmällä. Samat tallennetut vedot osoittivat, missä kukin geometriakorjaus epäonnistui. Nämä epäonnistumiset johtivat leimamalliin ja sen kahteen kustannukseen.