Lulucat

Comment Kimi K3 a construit un tracé de stylo-plume numérique

Gaoge ZhangGaoge Zhang

Nous voulions intégrer dans Lulucat Notes le stylo-plume directionnel de Freeform. La spécification tenait dans une capture d’écran d’écriture manuscrite. Deux modèles, une question discriminante et une plume elliptique plus tard, le stylo à eau écrit comme il doit. Kimi K3 a réalisé la recherche, le code et la validation sur Fireworks.

Mis à jour

Une cursive anglaise manuscrite portant les mots « fountain pen » au-dessus d’une ligne de calligraphie chinoise, tracée avec une plume elliptique sur du papier crème : épaisse dans les traits verticaux et fine dans les horizontaux.

Rendu par le pipeline Metal de Lulucat Notes dont parle cet article.

Lulucat Notes possède un stylo ordinaire et un surligneur. Le prochain outil prévu était un stylo à eau — le stylo-plume directionnel que vous connaissez dans Apple Notes et Freeform, où les traits verticaux sont épais et les horizontaux fins. La spécification de cet outil n’était pas un document, mais une capture d’écran : trois lignes d’écriture manuscrite, les mots « pencilkit », « 无边记 » et « 这种有方向的水笔能力 » — cette capacité directionnelle de stylo à eau.

Comme pour le pipeline Metal précédent, Kimi K3, le modèle ouvert de Moonshot exécuté sur Fireworks, s’est chargé de la mesure, de la modélisation, du code et de la validation. La personne a fourni la capture d’écran, répondu à une question et évalué le résultat sur un véritable iPad.

La spécification est une capture d’écran

Une image fixe ne peut pas expliquer pourquoi un tracé est fin. Elle peut seulement indiquer à quel point il l’est et à quel endroit. La première étape a donc consisté à mesurer. Nous avons balayé la capture d’écran ligne par ligne et colonne par colonne, en suivant le déplacement de la ligne médiane de chaque tracé pour en déduire la direction, puis nous avons converti la largeur balayée en largeur réelle à l’aide du sinus de cet angle :

TracéDirectionLargeur réelle
Hampe du « l » dans « pencilkit »≈ 78°27.3 px
Fût du « k » dans « pencilkit »≈ 90°28 px
Mouvement descendant vers la gauche de 力≈ 66°25.7 px
Traits de liaison cursifs≈ 8°11 px
Horizontales chinoises (横)≈ 0°6–11 px

Le rapport entre l’épais et le fin est d’environ 2.5. Ajuster sur le point à 66° donne : la relation est presque linéaire par rapport à , avec un maximum sur l’axe vertical. C’est, en somme, une plume italique : une fente horizontale que l’écriture horizontale n’ouvre jamais.

Version un : la largeur selon la direction

Le premier modèle était le plus évident. Il calculait une direction pour chaque point d’entrée, transformait cette direction en largeur au moyen de la courbe ajustée, puis intégrait le résultat au rayon du point lors de la capture. La direction était estimée de façon causale — une moyenne pondérée à décroissance exponentielle des derniers points de l’arc, calculée dans l’espace des angles doublés afin qu’un changement de sens ne neutralise pas l’estimation. Seuls les points précédents sont utilisés : le tracé en direct et le tracé validé correspondent donc octet par octet.

Le constructeur a passé sa vérification d’unités : les parcours synthétiques horizontal, vertical, à 45° et inversé ont produit les largeurs théoriques — 0.99, 2.52, 2.00 et 0.99 points. Le rendu ne nécessitait aucun nouveau code : un tracé dont le rayon est intégré n’est qu’une suite de tampons ronds, et notre pipeline de point-sprite les dessinait déjà.

Sur l’iPad, la personne a mis environ dix secondes à le rejeter : « c’est un stylo artistique, pas le stylo à eau ».

Deux explications pour une image

Pourquoi le résultat semblait-il faux ? Deux explications étaient possibles, et la capture d’écran ne permettait pas de les départager :

  • Verrouillage directionnel. La géométrie de la plume impose des horizontales fines et des verticales épaisses, quoi que fasse la main. C’est ce qu’implémentait la version un.
  • Pression et vitesse. Le stylo est piloté par la pression, et le motif de l’échantillon n’est que la dynamique de l’écriture manuscrite : les traits descendants sont naturellement appuyés, tandis que les liaisons sont naturellement rapides et légères.

Les deux explications donnent une capture où les horizontales sont fines et les verticales épaisses. La différence apparaît lorsqu’on appuie fort sur un trait horizontal. La version un le garde fin. Un stylo sensible à la pression le rend épais. Nous avons donc posé une question à la personne : un trait horizontal sur lequel on appuie fort doit-il s’épaissir ?

« Non. Les horizontales restent fines. »

Le verrouillage directionnel était confirmé. Pourtant, quelque chose d’autre n’allait pas, puisque la version un était elle aussi verrouillée par la direction.

La réponse était au bout des tracés

L’indice suivant se trouvait en haut des hampes. En agrandissant les fûts du « l » et du « k » dans l’échantillon, on observait deux choses : un trait droit conserve la même largeur du début à la fin, et ses extrémités sont des coupes diagonales plates, comme celles d’une plume biseautée qui se soulève. Pas de points ronds. Pas de rétrécissement dû à la pression.

Gros plan sur deux hampes manuscrites ascendantes de l’échantillon : chacune se termine en haut par une coupe diagonale plate, avec une épaisseur uniforme sur toute sa longueur.

C’est cela que signifiait réellement la « sensation de stylo artistique ». La version un modélisait l’apparence de l’échantillon — la largeur en fonction de la direction estimée —, mais pas le stylo. Un estimateur de direction est un capteur : il tremble face à une entrée bruitée, prend du retard dans les angles et arrondit chaque extrémité de tracé en cercle. Une vraie plume n’a aucun de ces problèmes, parce qu’elle ne calcule rien. La largeur est une géométrie.

Version deux : une plume elliptique

Le modèle final ne comporte aucun estimateur de direction. La plume est une ellipse orientée : son grand axe est horizontal et son petit axe est fixe. Des tampons de cette ellipse sont placés densément le long du tracé, et tout le reste découle de la géométrie :

  • Un tracé horizontal longe le bord ouvert et a donc toujours une largeur de — une ligne fine constante, quelle que soit la pression. C’est exactement la spécification confirmée.

  • Un tracé vertical traverse tout le grand axe : , l’extrémité épaisse.

  • Un tracé diagonal prend la largeur de la corde de l’ellipse perpendiculairement au déplacement,

  • Les extrémités sont des coupes elliptiques : les bouts plats en forme de pointe de l’échantillon, sans travail supplémentaire.

  • La pression ne redimensionne que le grand axe, , ce qui ajoute du volume d’encre aux traits descendants sans jamais épaissir un horizontal.

D’après les mesures de l’échantillon, nous avons conservé pt et pt, soit le même rapport de 2.5. Les tampons sont espacés de 0.5 point le long de l’arc ; avec un petit rayon de 0.55 point, l’ondulation du bord dans le pire des cas est d’environ un dixième de pixel.

Le rendu a nécessité un seul nouveau fragment shader. Le format de sommet — position, diamètre, couleur — contenait déjà tout : le diamètre est le grand axe et le petit axe est une variable uniform par passe. Le shader évalue une SDF d’ellipse avec la même rampe de couverture d’un demi-pixel que nos tampons ronds, ce qui rend le résultat comparable au pixel près avec l’implémentation de référence de Core Graphics (fillEllipse par tampon). La validation est passée par le contrôle habituel : un corpus synthétique avec deux tracés de stylo-plume, rendu des deux façons et comparé pixel par pixel — aucune différence structurelle, et un tracé horizontal vérifié au hasard mesurait 12 px dans les deux moteurs de rendu.

Deux rendus du même mot manuscrit « fountain » côte à côte : à gauche, des tampons ronds au diamètre modulé par la direction donnent des extrémités arrondies ; à droite, des tampons elliptiques donnent des extrémités plates taillées par la plume et une largeur plus régulière.

Version un à gauche, version deux à droite. Même saisie manuscrite, même pipeline. Les extrémités racontent toute l’histoire.

Sur l’iPad, le nouveau stylo a été accepté immédiatement : « 好,很好 » — Bien. Très bien.

Garder l’essai raté

La version un n’est pas partie à la poubelle. C’est un pinceau intéressant ; ce n’est simplement pas un stylo à eau. Il a donc été livré comme première entrée d’un nouveau menu de pinceaux expérimentaux, sous le nom que la personne lui a donné : 漏水的圆珠笔, le stylo-bille qui fuit. La barre d’outils a reçu un bouton en forme de fiole qui ouvre une liste textuelle d’expériences ; ajouter la suivante ne coûte qu’une ligne dans un registre. Un modèle rejeté n’est pas du travail perdu s’il peut être livré comme expérience clairement étiquetée.

Une soirée d’itérations

Le cycle complet — mesurer, modéliser, construire, tester sur l’appareil, questionner, remodéliser, reconstruire, valider — a pris une soirée. Kimi K3 sur Fireworks a pris en charge tout le volet technique : concevoir les balayages de mesure, proposer la question discriminante plutôt que deviner une seconde fois, supprimer son propre estimateur de direction lorsque les faits ont changé et étendre le harnais de différence de pixels avant de toucher à l’application. La vitesse d’inférence de Fireworks a gardé le cycle interactif : les longs diffs de Metal et de Swift, les scripts d’analyse de pixels et les outils de corpus arrivaient assez vite pour que le dernier goulot d’étranglement soit le jugement humain.

Le mode de collaboration était le même que dans l’article sur le pipeline Metal, et il a de nouveau fait ses preuves : le modèle est rapide et précis ; la personne se charge du sens esthétique et de la vérification de bout en bout. Une seule phrase de retour fondé sur le ressenti — « stylo artistique, pas stylo à eau » — a suffi pour que le modèle localise l’erreur exacte de modélisation et la remplace par une conception plus simple.

Le bon modèle s’est révélé plus petit que le mauvais. La version deux a été livrée avec moins de pièces mobiles que la version un — pas d’estimateur, pas de fenêtre de lissage, pas d’exposant ajusté. La plume ne calcule pas la largeur. La plume est la largeur.