Lulucat

To viskelædere, og hvorfor udviskningen hører til stregen

Gaoge ZhangGaoge Zhang

Et viskelæderstrøg som selvstændigt element er oplagt, men objektflytning bryder det. I Lulucat Notes hører udviskningen derfor til den streg, der mister blæk.

Opdateret

Når man visker ud i en notesbog, kan man mene to forskellige ting. Nogle gange er en markering forkert, og hele markeringen skal væk: en hjælpelinje, et overstreget ord eller en tilfældig streg fra en hvilende håndflade. Nogle gange er kun en del af en markering forkert: halvdelen af et tegn, en gren i et diagram eller halen på et overstregningsstrøg, der fortsatte forbi sætningen. Lulucat Notes leverer nu begge dele som to separate værktøjer.

Det sværere designspørgsmål er, hvor udviskningen hører hjemme.

Stregviskelæderet kom først

Det første viskelæder slettede hele streger. En berøring tester hver streg: afgrænsningsbokse afviser næsten alle, og de resterende gennemgår en test af den kvadrerede punkt-til-segment-afstand mod viskelæderets radius plus stregens radius, interpoleret langs segmentet. Bredden varierer med trykket, så en enkelt nominel bredde ville slette tynde streger, som brugeren ikke rørte, og overse tykke streger, som blev ramt.

Én gestus samles i én fortrydelsespost, og posten gemmer hver fjernet streg sammen med dens oprindelige indeks. Fortryd indsætter dem igen i omvendt rækkefølge, så lagrækkefølgen gendannes nøjagtigt, som den var.

Dette viskelæder er hurtigt, forudsigeligt og ude af stand til at fjerne halvdelen af et ord.

MaLiang gør viskelæderet til en pensel

MaLiang, det MIT-licenserede tegnerammeværk, som vi allerede havde lånt overstregningens stempelmodel fra, løser delvis udviskning på fireogtyve linjer. Dets Eraser er en underklasse af Brush, som tilsidesætter én ting, blandingsindstillingerne, for at trække fra i stedet for at lægge til. Et viskelæderstrøg er et element i den samme liste som alle andre streger. Fortryd fjerner det element. Ingen eksisterende streg ændres, fordi udviskningen kun sker under gengivelsen i listrækkefølge.

Vi lancerede den model, og to enhedssessioner afslørede, at den gik i stykker.

Objektflytning afslører den manglende ejer

Den første rapport lød: Vælg streger med lassoen, træk dem, og hullet bliver liggende.

Fire paneler sammenligner to viskelædermodeller før og efter flytning af stregerne. Når viskelæderet er sit eget element, bliver mellemrummet på den gamle placering; når udviskningen ejes af stregen, følger mellemrummet med blækket.

De samme udviskede streger flyttet mod højre. Øverst: viskelæderet er sit eget element. Nederst: udviskningen hører til stregerne.

Udvælgelse gjorde det værre. Udvalgte streger løftes ud af bitmaplaget til et overlay, der tegner dem direkte uden viskelæderelementet, så en udvisket streg så hel ud igen, så snart den blev valgt.

Den anden rapport lød: Viskelæderet svævede oven på overstregningen. Vores renderer gengiver i to passager, overstregninger først og penne bagefter, fordi overstregningsblæk skal ligge under blæk. Et viskelæderelement skal deltage i begge passager, ellers kan det ikke udviske begge typer. At deltage i pennepassagen betyder, at det kører efter alle overstregninger er tegnet, så et viskelæder også skar overstregningsstreger væk, som blev tegnet senere i tid.

Begge fejl har samme kilde. MaLiang har hverken lasso, flytning eller lagrækkefølge mellem pensler, så et element uden ejer er tilstrækkeligt dér. Vores lærred har alle tre dele, og en udviskning uden ejer har ikke noget fast svar på »hvad sker der, når blækket flytter sig?« eller »hvilket lag arbejder den på?«

Udviskning ejet af stregen

Hver Stroke bærer nu sine egne udviskningsbaner. Viskelæderet tester, hvilke streger det rammer, og tilføjer en bane til hver af dem i lærredskoordinater. Når én streg gengives, tegnes dens blæk og dens egne udviskninger inden i et transparenslag:

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 bevarer destinationen og fjerner den i forhold til kildealfaen:

Inden i et transparenslag er destinationen denne ene streg, så operationen kan ikke nå papiret, stregerne nedenunder eller stregerne, der tegnes senere. Laget koster ingenting for streger uden udviskninger, og det er næsten alle: de følger den oprindelige vej.

Ejerskab løser de to fejl direkte. En translation flytter udviskningsbanerne sammen med samplepunkterne, så en flyttet streg bærer sine huller med sig. Markerings-overlayet tegner det samme stregobjekt, så en valgt streg viser de samme huller. Lagorden er ikke længere vigtig, fordi en udviskning aldrig påvirker andet end sin egen streg.

Fortryd arbejder på gestussen: Én post oplister hvert (stroke, path)-par, som gestussen producerede; fortryd fjerner disse baner, og gentag tilføjer dem igen. Blækket under en udviskning redigeres aldrig, så intet skal rekonstrueres.

Livefeedback går gennem tile-bitmaplaget

Den streg, der tegnes, gengives normalt som et overlay oven på den komponerede side. En udviskning kan ikke fungere dér, fordi overlayet også ligger oven på papir såvel som blæk, og destination-out ville slå hul gennem papiret.

Derfor skriver viskelæderet straks i stregerne, og hver batch af nye samples udløser en lokal gengivelse af kun det berørte rektangel. Tile-pipelinen rydder allerede et rektangel og gengiver de streger, der skærer det; viskelæderet genbruger den vej og tilføjer ingen anden tegningssemantik.

Begge viskelædere side om side

Værktøjslinjen har to viskelæderknapper i stedet for én knap med en skjult tilstand. Det delvise viskelæder fjerner en del af en streg. Stregviskelæderet fjerner hele streger ved kontakt. Deres radier er separate konstanter, begge aktuelt 4 punkter.

Når en hel streg slettes, tager den sine udviskningsbaner med sig, fordi de er en del af stregen, og fortryd bringer begge tilbage sammen. Udviskningsbaner er også en del af den gemte fil, kvantiseret som alt andet: radius og koordinater som tiendedele af et punkt.

Der er stadig en begrænsning. En streg, der er udvisket helt væk, findes stadig som data. Den gengives som ingenting, men lassoen kan stadig vælge den, stregviskelæderet kan stadig slette den, og den optager stadig plads i dokumentet. At samle fuldt udviskede streger kræver en dækningskontrol, som vi ikke har skrevet, så indtil videre bliver de.

Når en model fra et andet projekt holder op med at fungere i dit, ligger forskellen som regel ikke i den kode, du kopierede. Den ligger i de funktioner, som det projekt ikke har.