Lulucat

Două radiere și de ce ștergerea aparține tușei

Gaoge ZhangGaoge Zhang

O tușă de radieră ca element propriu pare alegerea evidentă. Mutarea obiectelor strică acest model, așa că în Lulucat Notes ștergerea aparține tușei din care îndepărtează cerneala.

Actualizat

Când șterg într-un caiet, oamenii se referă la două lucruri diferite. Uneori un semn este greșit și trebuie să dispară cu totul: o linie de ghidaj, un cuvânt tăiat, o tușă rătăcită lăsată de palma în repaus. Alteori doar o parte a unui semn este greșită: jumătate de caracter, o ramură a unei diagrame, coada unei treceri de evidențiator care a depășit propoziția. Lulucat Notes le oferă acum pe amândouă, ca două instrumente separate.

Întrebarea de design mai dificilă este unde se află ștergerea.

Radiera de tușe a apărut prima

Prima radieră ștergea tușe întregi. La fiecare atingere, testăm fiecare tușă: casetele de încadrare resping aproape toate tușele, iar supraviețuitoarele trec printr-un test al distanței pătrate punct-segment față de raza radierei plus raza tușei interpolată de-a lungul acelui segment. Lățimea variază cu presiunea, așa că o singură lățime nominală ar șterge tușe subțiri pe care utilizatorul nu le-a atins și ar rata tușe groase pe care le-a atins.

Un gest devine o singură intrare de anulare, iar intrarea stochează fiecare tușă eliminată împreună cu indexul ei original. Anularea le inserează înapoi în ordine inversă, astfel încât ordinea de suprapunere revine exact la starea inițială.

Această radieră este rapidă, previzibilă și incapabilă să elimine jumătate dintr-un cuvânt.

MaLiang transformă radiera într-o pensulă

MaLiang, framework-ul de desen cu licență MIT din care am împrumutat deja modelul de amprente al evidențiatorului, răspunde ștergerii parțiale în 24 de linii. Eraser este o subclasă a lui Brush care suprascrie un singur lucru, opțiunile de compunere, pentru a scădea în loc să adune. O tușă de radieră este un element în aceeași listă cu orice altă tușă. Anularea elimină acel element. Nicio tușă existentă nu este modificată, deoarece ștergerea are loc doar în timpul redării din nou, în ordinea listei.

Am pus acest model în producție, iar două sesiuni pe dispozitive l-au stricat.

Mutarea obiectelor dezvăluie proprietarul lipsă

Primul raport: selectează tușe cu lasso-ul, trage-le și golul rămâne în urmă.

Patru panouri compară două modele de radieră înainte și după mutarea tușelor. Când radiera este propriul element, golul rămâne în poziția veche; când ștergerea aparține tușei, golul se deplasează odată cu cerneala.

Aceleași tușe șterse, mutate la dreapta. Sus: radiera este un element propriu. Jos: ștergerea aparține tușelor.

Selecția a înrăutățit lucrurile. Tușele selectate sunt scoase din bitmap într-o suprapunere care le desenează direct, fără elementul de radieră, așa că în clipa în care selectai o tușă ștearsă ea apărea din nou întreagă.

Al doilea raport: radiera plutea deasupra evidențiatorului. Rendererul nostru redă în două treceri, evidențiatoarele mai întâi și pixurile apoi, deoarece cerneala de evidențiator trebuie să fie sub cerneală. Un element de radieră trebuie să participe la ambele treceri, altfel nu poate șterge ambele tipuri. Participarea la trecerea pixului înseamnă rularea după ce fiecare evidențiator a fost desenat, așa că o radieră tăia și tușele de evidențiator desenate mai târziu.

Ambele eșecuri au aceeași sursă. MaLiang nu are lasso, mutare sau ordine de strat între pensule, așa că un element fără proprietar este suficient acolo. Pânza noastră le are pe toate trei, iar o ștergere fără proprietar nu are un răspuns definit la întrebările „ce se întâmplă când cerneala se mută?” sau „pe ce strat operează aceasta?”.

Ștergerea aparține tușei

Fiecare Stroke își poartă acum propriile trasee de ștergere. Radiera testează ce tușe atinge și adaugă câte un traseu la fiecare, în coordonatele canvasului. La redarea unei tușe, cerneala și ștergerile proprii sunt desenate într-un strat de transparență:

context.beginTransparencyLayer(auxiliaryInfo: nil)
drawInk(stroke, in: context, clip: clip, colorOverride: colorOverride)
context.setBlendMode(.destinationOut)
for path in stroke.erasePaths { fillErasePath(path, in: context) }
context.endTransparencyLayer()

Destination-out păstrează destinația și o elimină proporțional cu alfa sursei:

Într-un strat de transparență, destinația este acea singură tușă, așa că operația nu poate ajunge la hârtie, la tușele de dedesubt sau la tușele desenate ulterior. Stratul nu costă nimic pentru tușele fără ștergeri, adică aproape toate: acestea folosesc traseul original.

Această asociere răspunde direct celor două eșecuri. Translația mută traseele de ștergere împreună cu punctele eșantionate, astfel încât o tușă mutată își poartă golurile. Suprapunerea de selecție desenează același obiect tușă, așa că o tușă selectată afișează aceleași goluri. Ordinea straturilor nu mai contează, deoarece o ștergere nu se aplică niciodată altui lucru decât propriei tușe.

Anularea funcționează la nivelul gestului: o intrare enumeră fiecare pereche (stroke, path) produsă de acel gest, anularea elimină traseele, iar refacerea le adaugă din nou. Cerneala de sub o ștergere nu este modificată niciodată, deci nu trebuie reconstruit nimic.

Feedbackul live trece prin bitmap-ul tile-ului

Tușa desenată este redată în mod normal ca o suprapunere deasupra paginii compuse. Ștergerea nu poate funcționa acolo, deoarece acea suprapunere stă deasupra hârtiei și a cernelii deopotrivă, iar destination-out ar perfora hârtia.

Așadar, radiera scrie imediat în tușe, iar fiecare lot de eșantioane noi declanșează o redare locală doar a dreptunghiului afectat. Pipeline-ul de tile-uri șterge deja un dreptunghi și redă din nou tușele care îl intersectează; radiera refolosește această cale și nu adaugă o a doua semantică de desen.

Ambele radiere, una lângă alta

Bara de instrumente are două butoane de radieră, nu unul cu un mod ascuns. Radiera parțială elimină o parte dintr-o tușă. Radiera de tușe elimină tușe întregi la contact. Razele lor sunt constante separate, ambele fiind în prezent de 4 puncte.

Ștergerea unei tușe întregi elimină și traseele sale de ștergere, deoarece fac parte din acea tușă, iar anularea le aduce pe amândouă înapoi. Traseele de ștergere fac și ele parte din fișierul salvat, cuantizate la fel ca restul: raza și coordonatele sunt exprimate în zecimi de punct.

Rămâne o limitare. O tușă ștearsă complet există încă sub formă de date. Nu redă nimic, dar lasso-ul o poate selecta în continuare, radiera de tușe o poate șterge, iar ea ocupă încă spațiu în document. Eliminarea tușelor șterse complet necesită un test de acoperire pe care încă nu l-am scris, așa că deocamdată rămân.

Când un model din alt proiect încetează să funcționeze în al tău, diferența nu este de obicei în codul copiat. Este în funcționalitățile pe care acel proiect nu le are.