Lulucat

Чому ми замінили три рушії геометрії маркера на відбитки

Gaoge ZhangGaoge Zhang

Реальні дані Apple Pencil виявили дефекти, яких не помітили синтетичні тести, і привели Lulucat Notes до моделі відбитків MaLiang.

Оновлено

Lulucat Notes почав зі звичайного засобу візуалізації маркера: побудувати геометричний контур навколо дискретизованої траєкторії, а потім один раз залити цей контур напівпрозорим кольором. Одне заливання уникає темніших смуг, які з’являються там, де напівпрозорі сегменти накладаються. Воно також перетворює кожен стик, самоперетин, кінцевий зріз і короткий штрих на частину однієї й тієї самої геометричної проблеми.

Три покоління власної геометрії щоразу усували один клас дефектів і виявляли інший. Теперішній рушій використовує м’які круглі відбитки на основі MaLiang, фреймворку для малювання з відкритим кодом під ліцензією MIT, автор якого — Harley-xk. В обмін на усунення цілої категорії помилок контурів ми прийняли темніші самоперетини та кінці, звужені тиском.

Синій рукописний запис маркером, знятий на iPad, із тонкими гачками на початку й кінці кількох штрихів.

Оригінальний знімок екрана з пристрою при масштабі 228%. На обох кінцях кількох штрихів видно маленькі гачки.

Реальний рукопис породив гачки, яких не було в синтетичних сценах

Наші синтетичні сцени охоплювали прямі лінії, дуги, короткі дотики, зворотні рухи, зміни тиску й різкі повороти — і все це відтворювалося чисто. Реальний китайський рукопис усе одно породжував на початку й наприкінці багатьох штрихів гачки завдовжки близько 1–3 точок.

Тому ми додали до застосунку кнопку збереження й записали 29 повних штрихів. Кожен зразок зберігав свої координати, радіус, тиск, азимут, висоту, часову мітку та прапорці оцінювання UIKit. Офлайн-програма декодувала цей JSON і скомпілювала ті самі файли Stroke.swift, StrokeRenderer.swift та HighlighterStrokeBuilder.swift, які використовує застосунок. Її результат збігався зі знімком екрана пристрою штрих за штрихом, разом із гачками.

Оскільки офлайн-рендерер відтворив дефект лише із записаних вхідних даних, причина була у зразках і геометрії, а не в поведінці дисплея. Тепер ми могли прочитати точні зразки, які потрапили до рендерера.

Спочатку ми звинуватили оцінену орієнтацію Pencil під час торкання й відриву. Дані це спростували. У всіх 29 штрихах азимут змінювався не більш як на 0.09 радіана, а висота — не більш як на 0.02 радіана в межах перших і останніх восьми зразків; на тому iPad кожен прапорець оцінювання був false. Орієнтація залишалася стабільною, тоді як тиск і напрямок різко змінювалися.

Поріг тиску випливав із чіткого проміжку в даних

Останній зразок кожного записаного штриха мав тиск 0.000. Зразки під час письма мали значення не нижче 0.01, тоді як зразки, записані після відриву Pencil від поверхні, — не вище 0.002. Такі повітряні зразки часто тривали ще 2–7 точок і проходили 0.8–9.0 точок, а їхній напрямок міг відхилитися приблизно на 90 градусів від написаного штриха.

Штрих 2 показує механізм. Його тіло закінчувалося в діапазоні від 143 до 180 градусів. Хвіст із низьким тиском потім повертав від −90 до −67 градусів і проходив ще 8.5 точок. Рендерер контуру сприймав останню повітряну точку як визначальну кінцеву точку, тому розтягував вузький фрагмент геометрії в її напрямку.

Між письмом і рухом у повітрі виміряні значення тиску відрізняються майже на порядок величини, тож ми розмістили відсікання всередині цього проміжку:

private static let pressureFloor: CGFloat = 0.005

Видаляються лише зразки з низьким тиском на двох кінцях. Зразок із низьким тиском посередині штриха залишається його частиною. Це правило відповідає виміряній межі між подіями й не змінює написану траєкторію.

Інструментований крупний план до обрізання: зелені вхідні зразки відхиляються від штриха, а червоний контур утворює вузький гачок.

До обрізання рендерер прив’язує кінцевий зріз до повітряних зразків. Зелені крапки — сирі зразки; червона лінія — згенерований контур.

Інструментований крупний план після обрізання: синє заливання закінчується на останніх написаних зразках, а зелені зразки з низьким тиском залишаються за його межами.

Після обрізання зразків нижче 0.005 кінцевий зріз закінчується на останній написаній точці. Цей поріг тиску залишається в теперішньому рушії відбитків.

Поріг усунув довгі повітряні гачки. Короткі рухи під час торкання й дуги під час відриву, у яких тиск залишався вище 0.005, пережили це обрізання, бо є написаним рухом. Обробка таких рухів усередині моделі контуру привела до наступних раундів роботи з геометрією.

Три геометричні рушії лише переносили дефекти

Покоління перше: один контур, одне заливання

Перший чутливий до тиску рушій дотримувався знайомого конвеєра. Він згладжував осьову лінію, обчислював ліві й праві зміщення, з’єднував їх в один полігон і один раз заливав цей полігон. Перо було еліпсом, керованим азимутом і висотою Apple Pencil, тому його поперечний переріз змінювався разом із напрямком письма. Для коротких дотиків використовувалася окрема форма, а плоскі кінцеві зрізи отримували невеликі асиметрії, щоб уникати кишень із нульовим числом обходів.

Одне заливання усувало накопичення непрозорості всередині штриха, але вимагало коректного полігона на кожному повороті. Рухи під час торкання створювали клини між кінцевим зрізом і тілом. Зворотні рухи могли створювати білі серпоподібні кишені. Різкі повороти могли змусити внутрішній контур самоперетнутися й залишити незаповнену виїмку.

Покоління друге: ремонт контуру біля його кінців

Другий рушій зберіг контур і додав локальні виправлення: обрізання зворотного руху, демпфування ранніх оцінених властивостей, вирівнювання кінцевої точки, обрізання перехідного напрямку та невелике розширення кінцевого зрізу. Кожен патч усував відтворений дефект. Разом вони зробили поведінку кінців залежною від кількох порогів і вікон.

Реальне письмо потім породило випадки за межами цих вікон. Фіксоване вікно випрямлення на 24 точки стирало навмисні гачки в коротких штрихах. Адаптивне вікно зберігало ці гачки, але дуги відриву все одно могли перетворюватися на видимі кути. Обрізання з урахуванням напрямку усувало більше перехідного руху, ціною втрати приблизно 2.5 точок справді вигнутого початку в одному записаному штриху.

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

Покоління третє: об’єднання покриття з обрізаними квадратними кінцевими зрізами

Третій рушій перестав будувати єдиний контур. Він малював трапецію для кожного сегмента й диск у кожному внутрішньому зразку в маску відтінків сірого, брав об’єднання їхнього покриття, а потім один раз застосовував колір маркера. Серпоподібні області зворотного руху, потемніння самоперетинів і дірки з нульовим числом обходів зникли, бо рендерер більше не залежав від обходу полігона.

Об’єднання заокруглювало кінці, і ми відновили квадратні кінцеві зрізи, обрізаючи покриття двома півплощинами кінцевих точок. Це обрізання виправило прямі лінії з постійним тиском і створило серйозніший дефект: тіло вигнутого штриха може цілком законно перетинати півплощину кінцевої точки, а обрізання кінця тоді вилучало частину середини штриха. Під час введення наживо напрямок кінцевої точки постійно змінювався, тож обрізана область рухалася, і штрих мерехтів.

Вигнутий синій штрих до виправлення обрізання кінця: його верхнє тіло зрізане по діагональній лінії; зелені крапки показують повну дискретизовану траєкторію.

Глобальне обмеження кінця проходить крізь тіло вигнутого штриха.

Той самий вигнутий синій штрих після додавання локальної умови за довжиною дуги; обидві гілки навколо зеленої дискретизованої траєкторії відновлено.

Обмеження обрізання кінця примітивами в межах однієї напівширини від кінцевої точки відновлює цей штрих.

Умова за довжиною дуги локалізувала обрізання кінця й виправила кожен записаний випадок. Екстремальний синтетичний гачок усе ще показав невелику білу виїмку там, де наконечник із низьким тиском стикався з обрізаним кінцем. Третій рушій знову обміняв один геометричний дефект на інший.

MaLiang усуває жорстку межу

MaLiang будує штрих із повторюваних текстурних відбитків, а не з одного замкненого контуру. Його генератор траєкторії використовує квадратичні сегменти Bézier, що проходять через середини сусідніх зразків, а рендерер ліній розміщує відбитки з регулярним кроком уздовж цієї траєкторії. Розмір, обертання, колір і непрозорість відбитка можуть змінюватися вздовж штриха.

М’який відбиток не має окремого кінцевого зрізу, стику чи правила обходу. Різка зміна напрямку залишається послідовністю накладених одна на одну позначок. Зворотні рухи не можуть створити порожню кишеню полігона, а півплощина кінцевої точки не може перерізати середину штриха, бо модель не містить обрізання кінця.

Ми запозичили модель, а не код. Теперішній HighlighterStrokeBuilder виконує чотири операції:

  1. Видаляє лише кінцеві зразки, у яких тиск нижчий за 0.005.
  2. Згладжує координати сегментами квадратичних кривих Bézier через середини зразків.
  3. Розміщує по одному круглому відбитку на кожному кроці довжини дуги.
  4. Визначає діаметр відбитка за нормалізованим тиском і компонує відбитки з альфою source-over.

Код будівника скоротився з 686 рядків до 190. Азимут, висота, побудова контуру, маски об’єднання, вікна напрямку кінцевих точок, розширення кінцевого зрізу й обрізання кінцевого зрізу — усе це зникло з траєкторії маркера. Ми й далі записуємо орієнтацію Pencil, бо вхідна модель її підтримує, але сам маркер більше не читає ці поля.

Формула тиску MaLiang потребувала калібрування пристрою

MaLiang задає розмір відбитка формулою, еквівалентною

яка припускає, що force використовує корисну частину діапазону від 0 до 1. Дані з нашого пристрою цього не підтвердили. Приблизно у 3,400 зразках Apple Pencil медіанний тиск становив 0.047, а максимальний — 0.178. Безпосередня передача цих значень у формулу робила звичайне письмо значно тоншим за номінальний розмір пензля.

Тому ми нормалізуємо значення відносно спостережуваної медіани, потім застосовуємо показник степеня й обмежуємо результат:

Тепер медіанний тиск під час письма відповідає номінальному діаметру 12 pt. Дуже легкий дотик залишається видимим при 35% номінального розміру, а за сильного тиску зростання зупиняється на 120%.

Це калібрування стосується вхідного пристрою та поведінки пензля, а не MaLiang. Копіювання початкової формули без вимірювання власного розподілу тиску зберегло б архітектуру, але породило б неправильний пензель.

Альфа кожного відбитка випливала з відомої кількості накладань

MaLiang компенсує накладання напівпрозорих відбитків емпіричним виразом alpha ÷ overlapping × 2.5. Наш пензель має фіксований номінальний діаметр 12 pt і крок відбитків 1 pt, тож його центральна лінія отримує близько 12 шарів. У нашому випадку кількість накладань відома, і компенсацію можна обчислити, а не оцінювати.

Для цільової непрозорості штриха і однакових шарів source-over відбиток із непрозорістю дає

тому для одного відбитка потрібно

За і кожен відбиток використовує непрозорість приблизно . Центр досягає заданого кольору після повторного компонування, тоді як край отримує менше відбитків і зберігає розмитий перехід завширшки в одну-дві точки.

Аркуш різноманітних синіх штрихів маркера, відтворених фінальною моделлю м'яких відбитків, зокрема петель, гачків, коротких позначок і самоперетинів.

Фінальний рендерер повторно відтворює записані траєкторії з пристрою. Гачки, діагональні зрізи й білі кишені відсутні.

Два компроміси, на які ми пішли

Самоперетини стають темнішими. Композиція source-over враховує кожен відбиток, у тому числі нанесений під час попереднього проходу тією самою ділянкою, а рушій об’єднання покриття цього уникав. Ми прийняли темніше накладання, бо воно передбачуване й залишає форму штриха неушкодженою.

Кінці звужуються разом із тиском. Реальний відрив зменшує тиск, а це зменшує діаметр відбитка, тож штрих звужується, а не закінчується квадратним кінцевим зрізом на всю ширину. У всіх трьох геометричних рушіях ми вважали такий квадратний кінцевий зріз вимогою. Відмова від нього усунула обрізання й механізм кінцевих точок, що стояли за кількома описаними вище дефектами, тож ми прийняли звужений кінець.

Тому модель, яку ми тепер використовуємо, робить менше, ніж попередні. Вона зберігає виміряну подію письма, реагує на тиск після калібрування пристрою й відтворює вигнуті та самоперетинні траєкторії без потреби в глобально коректному контурі. Перо у формі долота, ідеально квадратний кінцевий зріз і точна ламана між сирими зразками — ось функції, від яких ми відмовилися заради цього.

Серед результатів цієї перевірки поріг тиску найпростіше перенести в інші проєкти. Значення 0.005 походило з проміжку між двома кластерами в реальному вводі: письмо на рівні 0.01 і вище, відстеження в повітрі на рівні 0.002 і нижче. Ті самі записані штрихи потім показали, де кожне геометричне виправлення зазнало невдачі, — саме це обґрунтувало зміну моделі й визначило два компроміси, які ми зберегли.