Lulucat

Come Kimi K3 ha costruito un tratto di penna stilografica digitale

Gaoge ZhangGaoge Zhang

Volevamo portare in Lulucat Notes la penna stilografica direzionale di Freeform. La specifica era uno screenshot di scrittura a mano. Dopo due modelli, una domanda discriminante e un pennino ellittico, la penna ad acqua scrive come dovrebbe. Ricerca, codice e validazione sono stati realizzati da Kimi K3 su Fireworks.

Aggiornato

Corsivo inglese scritto a mano che forma la scritta "fountain pen" sopra una riga di calligrafia cinese, reso con una penna stilografica dal pennino ellittico su carta color crema: spesso sui tratti verticali, sottile su quelli orizzontali.

Renderizzato dalla pipeline Metal di Lulucat Notes di cui parla questo articolo.

Lulucat Notes ha una penna normale e un evidenziatore. Lo strumento successivo sulla roadmap era una penna ad acqua — la penna stilografica direzionale che conosci da Apple Notes e Freeform, in cui i tratti verticali risultano spessi e quelli orizzontali sottili. La specifica di questo strumento non era un documento. Era uno screenshot: tre righe di scrittura a mano, con le parole “pencilkit”, “无边记” e “这种有方向的水笔能力” — questa capacità di penna ad acqua direzionale.

Come per la pipeline Metal precedente, il lavoro è stato svolto da Kimi K3, il modello open di Moonshot in esecuzione su Fireworks: misurazione, modellazione, codice e validazione. L’essere umano ha fornito lo screenshot, risposto a una domanda e valutato la sensazione su un iPad reale.

La specifica è uno screenshot

Un’immagine statica non può dirti perché un tratto è sottile. Può dirti solo quanto è sottile e dove. Il primo passo è stato quindi misurare. Abbiamo scansionato lo screenshot riga per riga e colonna per colonna, seguendo la deriva della linea mediana di ogni tratto per ricavarne la direzione, poi abbiamo convertito la larghezza della scansione nella larghezza reale usando il seno di quell’angolo:

TrattoDirezioneLarghezza reale
Asta ascendente della “l”, “pencilkit”≈ 78°27.3 px
Asta della “k”, “pencilkit”≈ 90°28 px
Tratto discendente verso sinistra di 力≈ 66°25.7 px
Collegamenti del corsivo≈ 8°11 px
Orizzontali cinesi (横)≈ 0°6–11 px

Il rapporto tra spesso e sottile è ≈ 2.5. Adattando al punto a 66° si ottiene — quasi lineare rispetto a , con il picco sull’asse verticale. In pratica, un pennino italico: una fessura orizzontale che la scrittura orizzontale non apre mai.

Versione uno: la larghezza dalla direzione

Il primo modello era quello ovvio. Calcolare una direzione per ogni punto in ingresso, trasformare la direzione in larghezza attraverso la curva adattata e incorporare il risultato nel raggio del punto al momento della cattura. La direzione veniva stimata causalmente — una media ponderata con decadimento esponenziale sugli ultimi punti dell’arco, calcolata nello spazio degli angoli raddoppiati in modo che un’inversione del movimento non annullasse la stima. Si usano soltanto i punti passati, quindi il tratto live e quello committato coincidono byte per byte.

Il builder ha superato il suo unit check: i percorsi sintetici orizzontale, verticale, a 45° e con inversione hanno tutti incorporato le larghezze teoriche — 0.99, 2.52, 2.00 e 0.99 punti. Per il rendering non serviva alcun codice nuovo: un tratto con raggio incorporato è solo una catena di timbri rotondi, e la nostra pipeline point-sprite li disegnava già.

Su iPad, all’essere umano sono bastati circa dieci secondi per respingerlo: “Questa è una penna artistica, non la penna ad acqua.”

Due spiegazioni per un’unica immagine

Perché dava una sensazione sbagliata? C’erano due spiegazioni candidate, e lo screenshot non permetteva di distinguerle:

  • Blocco della direzione. La geometria del pennino obbliga gli orizzontali a restare sottili e i verticali spessi, qualunque cosa faccia la mano. È ciò che implementava la versione uno.
  • Pressione e velocità. La penna è guidata dalla pressione, e il disegno del campione è solo la dinamica della scrittura a mano: i tratti discendenti vengono naturalmente premuti, i collegamenti sono naturalmente rapidi e leggeri.

Entrambe le spiegazioni producono uno screenshot con orizzontali sottili e verticali spessi. La differenza sta in ciò che succede quando premi forte su un tratto orizzontale. La versione uno lo mantiene sottile. Una penna sensibile alla pressione lo rende spesso. Abbiamo quindi posto all’essere umano una domanda: un tratto orizzontale premuto con forza deve diventare più spesso?

“No. Gli orizzontali restano sottili.”

Il blocco della direzione era confermato. Ma c’era ancora qualcosa che non andava, perché anche la versione uno bloccava la direzione.

La risposta era nelle estremità dei tratti

L’indizio successivo si trovava in cima alle aste ascendenti. Ingrandendo le aste della “l” e della “k” nel campione sono emerse due cose: un tratto rettilineo mantiene la stessa larghezza dall’inizio alla fine, e le estremità dei tratti sono tagli diagonali piatti — la forma di un pennino a scalpello che si solleva. Non punti rotondi. Non assottigliamenti dovuti alla pressione.

Ritaglio ravvicinato di due aste ascendenti scritte a mano dal campione, ciascuna terminante in alto con un taglio diagonale piatto e con spessore uniforme lungo tutta l'asta.

Era questo il vero significato della “sensazione di penna artistica”. La versione uno aveva modellato l’aspetto del campione — la larghezza come funzione della direzione stimata — ma non la penna. Un estimatore della direzione è un sensore: oscilla davanti a un input rumoroso, resta indietro nelle curve e arrotonda ogni estremità del tratto trasformandola in un cerchio. Un pennino reale non ha nessuno di questi problemi, perché non calcola nulla. La larghezza è geometria.

Versione due: un pennino ellittico

Il modello finale non ha alcun estimatore della direzione. Il pennino è un’ellisse orientata: asse maggiore orizzontale, asse minore fisso. I timbri di questa ellisse vengono disposti fittamente lungo il percorso del tratto, e tutto il resto deriva dalla geometria:

  • Un tratto orizzontale scorre lungo il bordo aperto, quindi ha sempre larghezza — una linea sottile costante, qualunque sia la pressione. Esattamente la specifica confermata.

  • Un tratto verticale attraversa l’intero asse maggiore: , l’estremità spessa.

  • Un tratto diagonale prende la larghezza della corda dell’ellisse perpendicolare alla direzione del movimento,

  • Le estremità dei tratti sono tagli ellittici — le estremità piatte a forma di pennino del campione, senza aggiungere altro.

  • La pressione scala solo l’asse maggiore, , quindi aumenta il volume d’inchiostro sui tratti discendenti e non può mai ispessire un tratto orizzontale.

Dalle misure del campione abbiamo mantenuto pt e pt, con lo stesso rapporto 2.5. I timbri sono distanziati di 0.5 punti lungo l’arco; con un raggio minore di 0.55 punti, l’ondulazione del bordo nel caso peggiore risulta di circa un decimo di pixel.

Il rendering richiedeva un solo nuovo fragment shader e nient’altro. Il formato dei vertici — posizione, diametro, colore — conteneva già tutto: il diametro è l’asse maggiore, mentre l’asse minore è una variabile uniform per pass. Lo shader valuta una SDF ellittica con la stessa rampa di copertura a mezzo pixel dei nostri timbri rotondi. È questo che la rende confrontabile pixel per pixel con l’implementazione di riferimento di Core Graphics (fillEllipse per timbro). La validazione ha superato il consueto gate: un corpus sintetico con due tratti di penna stilografica è stato renderizzato in entrambi i modi e confrontato pixel per pixel — zero differenze strutturali, e un tratto orizzontale controllato a campione misurava 12 px in entrambi i renderer.

Due rendering affiancati della stessa parola scritta a mano, "fountain": a sinistra, timbri rotondi con diametro scalato in base alla direzione e estremità arrotondate; a destra, timbri ellittici con estremità piatte tagliate dal pennino e larghezza più stabile.

Versione uno a sinistra, versione due a destra. Stesso input scritto a mano, stessa pipeline. Le estremità raccontano tutto.

Su iPad, la nuova penna ha superato subito la prova: “好,很好” — bene. Molto bene.

Conservare l’errore

La versione uno non è finita nel cestino. È un pennello interessante — semplicemente non è una penna ad acqua. Così è stata distribuita come prima voce di un nuovo menu di pennelli sperimentali, con il nome che le ha dato l’essere umano: 漏水的圆珠笔, la penna a sfera che perde. La barra degli strumenti ha ricevuto un pulsante a forma di fiaschetta che apre un elenco testuale degli esperimenti; aggiungere il successivo costa una riga in un registry. Un modello scartato non è lavoro sprecato se può essere distribuito come esperimento chiaramente etichettato.

Una sera di cicli

L’intero percorso — misurare, modellare, costruire, provare sul dispositivo, fare una domanda, rimodellare, ricostruire, validare — è durato una sera. Kimi K3 su Fireworks ha gestito l’intero lato tecnico del ciclo: progettare le scansioni di misurazione, proporre la domanda discriminante invece di indovinare una seconda volta, eliminare il proprio estimatore della direzione quando le prove hanno cambiato verso ed estendere l’harness di pixel-diff prima di toccare l’app. La velocità di inferenza di Fireworks ha mantenuto il ciclo interattivo — lunghi diff di Metal e Swift, script di analisi dei pixel e strumenti per il corpus arrivavano tutti abbastanza in fretta da lasciare come unico collo di bottiglia il giudizio umano.

Il modello di collaborazione era lo stesso del post sulla pipeline Metal, e ha funzionato anche qui: il modello è rapido e preciso; l’essere umano possiede il gusto e la verifica end-to-end. Una sola frase di feedback basato sulla sensazione — “penna artistica, non penna ad acqua” — è bastata perché il modello individuasse l’errore preciso di modellazione e lo sostituisse con un progetto più semplice.

Il modello giusto si è rivelato più piccolo di quello sbagliato. La versione due è stata distribuita con meno parti mobili della versione uno — nessun estimatore, nessuna finestra di smoothing, nessun esponente adattato. Il pennino non calcola la larghezza. Il pennino è la larghezza.