Lulucat

Как Kimi K3 самостоятельно создала цифровой штрих перьевой ручки

Gaoge ZhangGaoge Zhang

Мы хотели добавить в Lulucat Notes направленную перьевую ручку из Freeform. Техническим заданием был скриншот почерка. Две модели, один разграничивающий вопрос и эллиптическое перо спустя — водяная ручка пишет так, как должна. Исследование, код и валидацию выполнила Kimi K3 на Fireworks.

Обновлено

Рукописный английский курсив со словами "fountain pen" над строкой китайской каллиграфии, нарисованный перьевой ручкой с эллиптическим пером на кремовой бумаге: толстый на вертикалях, тонкий на горизонталях.

Результат работы конвейера Metal из Lulucat Notes, которому посвящён этот пост.

В Lulucat Notes есть простая ручка и маркер. Следующим инструментом в плане была водяная ручка — направленная перьевая ручка, знакомая по Apple Notes и Freeform: вертикальные штрихи у неё получаются толстыми, а горизонтальные — тонкими. Техническим заданием для этого инструмента был не документ. Это был скриншот: три строки рукописного текста, слова “pencilkit”, “无边记” и “这种有方向的水笔能力” — эта направленная способность водяной ручки.

Как и в случае с предыдущим конвейером Metal, работу выполнила Kimi K3, открытая модель Moonshot, работающая на Fireworks: измерения, моделирование, код и валидация. Человек предоставил скриншот, ответил на один вопрос и оценил ощущения на настоящем iPad.

Техническое задание — это скриншот

Статичное изображение не объясняет, почему штрих тонкий. Оно может показать лишь, насколько он тонкий и где именно. Поэтому первым шагом стали измерения. Мы просканировали скриншот строка за строкой и столбец за столбцом, отслеживая смещение центральной линии каждого штриха, чтобы определить его направление, а затем перевели ширину сканирования в истинную ширину через синус этого угла:

ШтрихНаправлениеИстинная ширина
Восходящий штрих «l», «pencilkit»≈ 78°27.3 px
Штамб «k», «pencilkit»≈ 90°28 px
Левый нисходящий взмах в 力≈ 66°25.7 px
Соединения курсивного письма≈ 8°11 px
Китайские горизонтали (横)≈ 0°6–11 px

Отношение толстого штриха к тонкому — ≈ 2.5. Подгонка по точке 66° даёт — зависимость от почти линейна и достигает максимума на вертикальной оси. По сути, это курсивное перо: горизонтальная щель, которую горизонтальное письмо никогда не раскрывает.

Версия один: ширина из направления

Первая модель была очевидной. Для каждой входной точки вычислить направление, сопоставить его с шириной через подобранную кривую и в момент захвата вшить результат в радиус точки. Направление оценивалось причинно — как экспоненциально затухающее взвешенное среднее по нескольким последним точкам дуги, вычисленное в пространстве удвоенного угла, чтобы разворот движения не обнулял оценку. Используются только прошлые точки, поэтому живой и зафиксированный штрихи совпадают байт в байт.

Builder прошёл модульную проверку: синтетические горизонтальные, вертикальные, траектории под 45° и траектории с разворотом все получили теоретические ширины — 0.99, 2.52, 2.00 и 0.99 пункта. Для отрисовки вообще не понадобился новый код: штрих с заранее записанным радиусом — это просто цепочка круглых штампов, а наш конвейер point-sprite уже их рисовал.

На iPad человек отклонил модель примерно за десять секунд: «это художественная ручка, а не водяная ручка».

Две гипотезы описывают один снимок

Почему результат ощущался неправильным? Было два возможных объяснения, а скриншот не позволял их различить:

  • Фиксация направления. Геометрия пера заставляет горизонтали быть тонкими, а вертикали — толстыми, что бы ни делала рука. Именно это реализовала версия один.
  • Давление и скорость. Толщина ручки зависит от давления, а рисунок образца — всего лишь динамика почерка: на нисходящих штрихах естественно нажимают сильнее, а соединения естественно быстрые и лёгкие.

Оба объяснения дают снимок с тонкими горизонталями и толстыми вертикалями. Разница проявляется, когда вы сильно нажимаете на горизонтальный штрих. Версия один оставляет его тонким. Ручка, реагирующая на давление, делает его толстым. Поэтому мы задали человеку один вопрос: должен ли горизонтальный штрих, на который сильно надавили, стать толще?

«Нет. Горизонтали остаются тонкими.»

Фиксация направления подтвердилась. Но что-то ещё было не так, ведь версия один тоже фиксировала направление.

Ответ был в концах штрихов

Следующая подсказка нашлась на верхушках восходящих штрихов. Увеличение штамбов «l» и «k» на образце показало две вещи: прямой штрих сохраняет одну ширину от начала до конца, а его концы — плоские диагональные срезы, форма перьевого зубила, отрывающегося от бумаги. Не круглые точки. Не сужения от давления.

Крупный план двух рукописных восходящих штамбов из образца: каждый заканчивается вверху плоским диагональным срезом, а толщина по всей длине штамба остаётся одинаковой.

Именно это и означало «ощущение художественной ручки». Версия один моделировала вид образца — ширину как функцию оценённого направления, — но не ручку. Оценщик направления — это сенсор: на зашумлённом входе он дрожит, на поворотах запаздывает и превращает конец каждого штриха в окружность. У настоящего пера нет ни одной из этих проблем, потому что оно ничего не вычисляет. Ширина — это геометрия.

Версия два: эллиптическое перо

В итоговой модели вообще нет оценщика направления. Перо — это ориентированный эллипс: длинная ось горизонтальна, короткая зафиксирована. Штампы этого эллипса плотно размещаются вдоль траектории штриха, а всё остальное следует из геометрии:

  • Горизонтальный штрих идёт вдоль открытого края, поэтому всегда получается шириной — постоянная тонкая линия при любом давлении. Именно это и требовала подтверждённая спецификация.

  • Вертикальный штрих пересекает всю длинную ось: , толстый конец.

  • Диагональный штрих получает ширину хорды эллипса перпендикулярно направлению движения,

  • Концы штрихов — эллиптические срезы: плоские концы в форме пера из образца, без дополнительной работы.

  • Давление масштабирует только длинную ось, , поэтому добавляет объём чернил на нисходящих штрихах и никогда не утолщает горизонталь.

По измерениям образца мы оставили pt и pt — то же соотношение 2.5. Штампы стоят через 0.5 пункта вдоль дуги; при коротком радиусе 0.55 пункта наихудшая рябь края составляет примерно одну десятую пикселя.

Для отрисовки понадобился один новый fragment shader и больше ничего. Формат вершины — позиция, диаметр, цвет — уже содержал всё необходимое: диаметр — это длинная ось, а короткая ось задаётся uniform на один проход. Шейдер вычисляет SDF эллипса с той же рампой покрытия в полпикселя, что и наши круглые штампы, поэтому его можно сравнивать пиксель в пиксель с эталонной реализацией Core Graphics (fillEllipse per stamp). Валидация прошла через обычные ворота: синтетический корпус с двумя штрихами перьевой ручки, отрисованными обоими способами и сравнёнными попиксельно, — ноль структурных различий, а проверенный точечно горизонтальный штрих имел 12 px в обоих рендерерах.

Две отрисовки одного и того же рукописного слова "fountain" рядом: слева — круглые штампы с диаметром, масштабированным по направлению, и закруглёнными концами; справа — эллиптические штампы с плоскими концами-срезами пера и более стабильной шириной.

Версия один слева, версия два справа. Один и тот же рукописный ввод, один и тот же конвейер. Концы рассказывают историю.

На iPad новая ручка прошла проверку сразу: «好,很好» — хорошо. Очень хорошо.

Сохранить промах

Версия один не отправилась в корзину. Это интересная кисть — просто не водяная ручка. Поэтому она стала первой записью в новом меню экспериментальных кистей под названием, которое дал ей человек: 漏水的圆珠笔, подтекающая шариковая ручка. На панели инструментов появилась кнопка-колба, открывающая текстовый список экспериментов; чтобы добавить следующий, достаточно одной строки в реестре. Отвергнутая модель — не напрасная работа, если её можно выпустить как ясно обозначенный эксперимент.

Один вечер циклов

Весь путь — измерить, смоделировать, собрать, проверить на устройстве, задать вопрос, перемоделировать, пересобрать, проверить — занял один вечер. Kimi K3 на Fireworks выполнила всю техническую часть этого цикла: придумала измерительные сканы, предложила разграничивающий вопрос вместо второй догадки, удалила собственный оценщик направления, когда факты изменились, и расширила pixel-diff harness до того, как коснулась приложения. Скорость инференса Fireworks сохранила интерактивность цикла — длинные диффы Metal и Swift, скрипты анализа пикселей и инструменты для корпуса появлялись достаточно быстро, чтобы узким местом оставалось то, чем оно и должно быть: человеческое суждение.

Схема сотрудничества была той же, что и в посте о конвейере Metal, и снова сработала: модель быстрая и точная; человек отвечает за вкус и сквозную проверку. Одной фразы обратной связи об ощущении — «художественная ручка, а не водяная ручка» — хватило, чтобы модель нашла точную ошибку моделирования и заменила её более простой конструкцией.

Правильная модель оказалась меньше неправильной. В версии два было меньше подвижных частей, чем в версии один: ни оценщика, ни окна сглаживания, ни подобранного показателя степени. Перо не вычисляет ширину. Перо и есть ширина.