Lulucat

Dve radirki in zakaj brisanje pripada potezi

Gaoge ZhangGaoge Zhang

Poteza radirke kot samostojen element je očitna zasnova. Premikanje objektov jo pokvari, zato je v Lulucat Notes brisanje vezano na potezo, iz katere odstranjuje črnilo.

Posodobljeno

Ko ljudje v zvezku brišejo, imajo v mislih dve različni stvari. Včasih je poteza napačna in mora izginiti cela: vodilna črta, prečrtana beseda, nenamerna poteza dlani, ki počiva na strani. Včasih je napačen le del poteze: polovica znaka, ena veja diagrama, rep poteze označevalnika, ki je segel čez stavek. Lulucat Notes zdaj ponuja oboje kot dve ločeni orodji.

Težje oblikovalsko vprašanje je, kam spada brisanje.

Najprej je prišla radirka potez

Prva radirka je brisala cele poteze. Ob dotiku preveri vsako potezo: omejevalni pravokotniki zavrnejo skoraj vse, preživele pa preveri s testom kvadratne razdalje od točke do odseka glede na vsoto polmera radirke in polmera poteze, interpoliranega vzdolž odseka. Širina se spreminja s pritiskom, zato bi ena sama nazivna širina izbrisala tanke poteze, ki se jih uporabnik ni dotaknil, in zgrešila debele, ki se jih je uporabnik dotaknil.

En gib se združi v en zapis razveljavitve, zapis pa shrani vsako odstranjeno potezo skupaj z njenim prvotnim indeksom. Razveljavitev jih vstavi nazaj v obratnem vrstnem redu, zato se vrstni red zlaganja vrne natanko v prejšnje stanje.

Ta radirka je hitra, predvidljiva in ne more odstraniti polovice besede.

MaLiang naredi radirko čopič

MaLiang, ogrodje za risanje pod licenco MIT, od katerega smo že prevzeli model odtisov označevalnika, rešuje delno brisanje v 24 vrsticah. Njegova Eraser je podrazred Brush, ki prepiše eno stvar, možnosti mešanja, da odšteva namesto prišteva. Poteza radirke je element na istem seznamu kot vsaka druga poteza. Razveljavitev odstrani ta element. Nobena obstoječa poteza ni spremenjena, ker brisanje poteka samo med ponovnim izrisovanjem, v vrstnem redu seznama.

Model smo vključili, v dveh sejah na napravah pa se je pokvaril.

Premikanje objektov razkrije manjkajočega lastnika

Prvo poročilo: izberite poteze z lasom, jih povlecite in luknja ostane zadaj.

Štiri plošče primerjajo dva modela radirke pred premikanjem potez in po njem. Ko je radirka samostojen element, vrzel ostane na starem mestu; ko je brisanje v lasti poteze, vrzel potuje skupaj s črnilom.

Iste izbrisane poteze, premaknjene v desno. Zgoraj: radirka je samostojen element. Spodaj: brisanje pripada potezam.

Izbira je stvar še poslabšala. Izbrane poteze se iz bitne slike dvignejo v prekrivni sloj, ki jih izriše neposredno, brez elementa radirke, zato se je izbrana izbrisana poteza v trenutku znova pokazala v celoti.

Drugo poročilo: radirka se je izrisala nad označevalnikom. Naš izrisovalnik ponovno izriše vse v dveh prehodih, najprej označevalnike in nato peresa, ker mora črnilo označevalnika ostati pod črnilom. Element radirke mora sodelovati v obeh prehodih, sicer ne more izbrisati obeh vrst. Sodelovanje v prehodu peres pomeni, da se izvede za vsakim izrisanim označevalnikom, zato je radirka izrezala tudi poteze označevalnika, ki so bile časovno izrisane pozneje.

Oba neuspeha imata isti izvor. MaLiang nima izbire z lasom, premikanja ali vrstnega reda slojev med čopiči, zato element brez lastnika tam zadošča. Naše platno ima vse troje, brisanje brez lastnika pa nima določenega odgovora na vprašanji »kaj se zgodi, ko se črnilo premakne« in »na katerem sloju to deluje«.

Brisanje pripada potezi

Vsaka Stroke zdaj nosi lastne poti brisanja. Radirka preveri, katerih potez se dotakne, in vsaki doda pot v koordinatah platna. Pri izrisu posamezne poteze se njeno črnilo in lastno brisanje izrišeta znotraj prosojnega sloja:

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 ohrani cilj in ga odstrani v sorazmerju z alfo vira:

V prosojnem sloju je cilj tista ena poteza, zato operacija ne more doseči papirja, potez pod njo ali potez, izrisanih pozneje. Sloj pri potezah brez brisanja ne prinese nobenega stroška, takih pa je skoraj vse: te uporabijo izvirno pot.

Lastništvo neposredno odpravi oba neuspeha. Premik pomakne poti brisanja skupaj z vzorčnimi točkami, zato premaknjena poteza nosi svoje vrzeli s seboj. Prekrivni sloj izbire izriše isti objekt poteze, zato izbrana poteza pokaže iste vrzeli. Vrstni red slojev ni več pomemben, ker brisanje nikoli ne vpliva na nič drugega kot na lastno potezo.

Razveljavitev deluje na ravni geste: en zapis našteje vsak par (stroke, path), ki ga je ustvarila ta gesta, razveljavitev odstrani te poti, ponovitev pa jih znova doda. Črnilo pod brisanjem ni nikoli urejeno, zato ga ni treba rekonstruirati.

Živi predogled poteka prek bitne slike ploščic

Poteza, ki se trenutno izrisuje, se običajno izriše kot prekrivni sloj nad sestavljeno stranjo. Brisanje tam ne more delovati, ker je ta sloj nad papirjem in črnilom, destination-out pa bi prebil papir.

Zato radirka takoj zapisuje v poteze, vsak paket novih vzorcev pa sproži lokalno ponovno izrisovanje samo prizadetega pravokotnika. Cevovod ploščic že počisti pravokotnik in znova izriše poteze, ki se z njim sekajo; radirka to pot ponovno uporabi in ne doda druge semantike izrisovanja.

Obe radirki druga ob drugi

Orodna vrstica ima dva gumba za radirko, ne enega gumba s skritim načinom. Delna radirka odstrani del poteze. Radirka potez ob dotiku odstrani cele poteze. Njuna polmera sta ločeni konstanti, oba trenutno 4 točke.

Ko izbrišemo celo potezo, z njo izginejo tudi njene poti brisanja, saj so del te poteze, razveljavitev pa oboje vrne skupaj. Poti brisanja so tudi del shranjene datoteke in se kvantizirajo kot vse drugo: polmer in koordinate na desetinko točke.

Eno omejitev je treba še omeniti. Poteza, izbrisana do ničesar, še vedno obstaja kot podatek. Ne izriše ničesar, vendar jo lahko laso še vedno izbere in radirka potez izbriše, poleg tega pa še vedno zaseda prostor v dokumentu. Zbiranje v celoti izbrisanih potez zahteva test pokritosti, ki ga še nismo napisali, zato za zdaj ostanejo.

Ko model iz drugega projekta preneha delovati v vašem, razlika običajno ni v kodi, ki ste jo kopirali. Je v funkcijah, ki jih ta projekt nima.