Üç Fosforlu Kalem Geometri Motorunu Neden Damgalarla Değiştirdik
Gerçek Apple Pencil verileri sentetik testlerin kaçırdığı kusurları ortaya çıkardı; Lulucat Notes üç kontur motorundan MaLiang damga modeline geçti.
Lulucat Notes, geleneksel bir fosforlu kalem oluşturucusuyla başladı: örneklenmiş yolun çevresinde geometrik bir kontur oluşturup bu konturu yarı saydam renkle bir kez dolduruyordu. Tek bir dolgu, yarı saydam parçaların üst üste geldiği yerlerde görünen koyu bantlardan kaçınır. Ayrıca her birleşimi, kendiyle kesişimi, uç kapağını ve kısa çizgiyi aynı geometri probleminin içine sokar.
Özel geometrinin üç nesli bir kusur sınıfını ortadan kaldırırken başka bir sınıfı açığa çıkardı. Artık gönderdiğimiz renderlayıcı, MaLiang temel alınarak oluşturulmuş yumuşak dairesel damgaları kullanıyor; MaLiang, Harley-xk tarafından geliştirilen MIT lisanslı açık kaynaklı bir çizim çatısıdır. Kontur hatalarının bütün bir kategorisini ortadan kaldırma karşılığında, daha koyu kendiyle kesişimleri ve basınca göre daralan uçları kabul ettik.

228% yakınlaştırmadaki özgün cihaz ekran görüntüsü. Birkaç çizginin her iki ucunda küçük kancalar görülüyor.
Gerçek el yazısı, sentetik sahnelerin üretmediği kancaları oluşturdu
Sentetik sahnelerimiz düz çizgileri, yayları, kısa dokunuşları, geri izlemeleri, basınç değişikliklerini ve keskin dönüşleri kapsıyordu; hepsi temiz render edildi. Gerçek Çince el yazısı yine de birçok çizginin başında ve sonunda yaklaşık 1–3 punto uzunluğunda kancalar oluşturdu.
Bu nedenle uygulamaya bir kaydet düğmesi ekledik ve 29 eksiksiz çizgi kaydettik. Her örnek konumunu, yarıçapını, basıncını, azimutunu, yüksekliğini, zaman damgasını ve UIKit tahmin bayraklarını korudu. Çevrimdışı bir program bu JSON’u çözdü ve uygulamanın kullandığı aynı Stroke.swift, StrokeRenderer.swift ve HighlighterStrokeBuilder.swift dosyalarını derledi. Çıktısı, kancalar da dahil olmak üzere cihaz ekran görüntüsündeki çizgilerle çizgi çizgi eşleşti.
Çevrimdışı renderlayıcı, kusuru yalnızca kaydedilmiş girdiden yeniden ürettiği için neden görüntüleme davranışında değil, örneklerde ve geometride yatıyordu. Artık renderlayıcıya giren tam örnekleri okuyabiliyorduk.
İlk hipotezimiz, temas ve kalkış anlarında tahmin edilen Pencil yönelimini suçladı. Veriler bunu reddetti. 29 çizginin tamamında azimut, ilk ve son sekiz örnek içinde en fazla 0.09 radyan; yükseklik ise en fazla 0.02 radyan değişti ve o iPad’de her tahmin bayrağı false idi. Basınç ve yön keskin biçimde değişirken yönelim sabit kaldı.
Basınç eşiği verilerdeki temiz bir boşluktan geldi
Kaydedilen her çizginin son örneğinde basınç 0.000 idi. Yazı örnekleri 0.01 veya üzerindeyken, Pencil yüzeyden ayrıldıktan sonra kaydedilen örnekler 0.002 veya altındaydı. Bu havadaki örnekler genellikle 2–7 nokta boyunca devam ediyor ve 0.8–9.0 punto yol alıyordu; yönleri de yazılan çizgiden yaklaşık 90 derece uzağa dönebiliyordu.
Çizgi 2 mekanizmayı gösteriyor. Gövdesi 143 ile 180 derece arasında sona erdi. Düşük basınçlı kuyruk ardından −90 ile −67 derece arasına dönüp 8.5 punto daha ilerledi. Kontur renderlayıcısı son havadaki noktayı yetkili uç noktası olarak ele aldı ve bu noktaya doğru dar bir geometri parçası uzattı.
Ölçülen basınçlar, yazı ile havadaki hareket arasında neredeyse bir büyüklük mertebesi fark olduğunu gösteriyor; biz de kesme noktasını bu aralığın içine yerleştirdik:
private static let pressureFloor: CGFloat = 0.005
Yalnızca iki uçtaki düşük basınçlı örnekler kaldırılır. Bir çizginin ortasındaki düşük basınçlı örnek onun parçası olarak kalır. Kural, ölçülen olay sınırını izler ve yazılan yolu değiştirmeden bırakır.

Kesmeden önce renderlayıcı, uç kapağını havadaki örneklere sabitliyor. Yeşil noktalar ham örnekleri, kırmızı çizgi oluşturulan konturu gösteriyor.

0.005 altındaki örnekler kesildikten sonra uç kapağı son yazılı noktada sona eriyor. Bu basınç eşiği mevcut damga renderlayıcısında da korunuyor.
Bu eşik, uzun havadaki kancaları ortadan kaldırdı. Basıncı 0.005 üzerinde kalan kısa temas sürüklemeleri ve kalkış yayları yazı hareketi oldukları için bundan etkilenmeden kaldı. Bu hareketleri bir kontur modelinin içinde ele almak, sonraki geometri turlarına yol açtı.
Üç geometri motoru kusurları yer değiştirdi
Birinci nesil: tek kontur, tek dolgu
İlk basınca duyarlı motor tanıdık bir işlem hattını izliyordu. Bir omurgayı yumuşatıyor, sol ve sağ ofsetleri hesaplıyor, bunları tek bir çokgende birleştiriyor ve bu çokgeni bir kez dolduruyordu. Uç, Apple Pencil azimut ve yüksekliğiyle yönlendirilen bir elipsti; dolayısıyla kesiti yazı yönüne göre değişiyordu. Kısa dokunuşların ayrı bir şekli vardı ve düz uç kapakları, sıfır sarımlı cepleri önlemek için küçük asimetriler kullanıyordu.
Tek dolgu, çizgi içindeki opaklık birikimini çözdü ve her dönüşte geçerli bir çokgen gerektirdi. Temas sürüklemeleri uç kapağı ile gövde arasında kama şekilleri oluşturdu. Geri izlemeler beyaz hilal cepleri oluşturabiliyordu. Keskin dönüşler iç konturun kendiyle kesişmesine ve dolgusuz bir çentik bırakmasına yol açabiliyordu.
İkinci nesil: uçlarına yakın konturu onarmak
İkinci motor konturu korudu ve yerel onarımlar ekledi: geri izleme kesme, erken tahmin edilen özellikler için sönümleme, uç noktası düzleştirme, geçici yön kesme ve küçük bir uç kapağı uzatması. Her yama yeniden üretilen bir kusuru düzeltti. Birlikte, uç davranışını birkaç eşik ve pencereye bağımlı hale getirdiler.
Gerçek yazı daha sonra bu pencerelerin dışındaki durumları üretti. Sabit 24 noktalık düzleştirme penceresi, kısa çizgilerdeki kasıtlı kancaları sildi. Uyarlanabilir bir pencere bu kancaları korudu; yine de kalkış yayları görünür köşelere dönüşebiliyordu. Yön farkındalıklı kesme daha fazla geçici hareketi ortadan kaldırdı, ancak kaydedilmiş bir çizgide gerçekten kıvrımlı bir başlangıcın yaklaşık 2.5 noktasını da feda etti.
Her yama hedeflediği durumu düzeltti. Ancak bunların altındaki model, gürültülü ve kıvrımlı giriş altında geçerli kalması gereken sert bir kontur gerektiriyordu.
Üçüncü nesil: kırpılmış kare uç kapaklarıyla kapsama birleşimi
Üçüncü motor tek bir kontur oluşturmaktan vazgeçti. Gri tonlu bir maskeye her parça için bir yamuk, her iç örnek için de bir disk çizdi; bunların kapsama birleşimini aldı ve ardından fosforlu kalem rengini bir kez uyguladı. Geri izleme hilalleri, kendiyle kesişme koyulaşması ve sıfır sarımlı delikler ortadan kalktı; çünkü renderlayıcı artık çokgen sarımına bağlı değildi.
Birleşim uçları yuvarladı; biz de kapsama alanını iki uç yarı düzlemine göre kırparak kare uç kapaklarını geri getirdik. Bu kırpma sabit basınçlı düz çizgileri düzeltti ve daha ciddi bir kusur ortaya çıkardı: kıvrımlı bir çizginin gövdesi meşru olarak bir uç yarı düzlemini kesebilir, uç kapağı kırpması da çizginin ortasından bir parçayı kaldırabilir. Canlı giriş sırasında uç yönü sürekli değiştiği için kırpılan alan hareket ediyor ve çizgi titriyordu.

Küresel uç kapağı kısıtı, kıvrımlı bir çizginin gövdesini kesiyor.

Uç kapağı kırpmasını uç noktasının bir yarı genişliği içindeki primitiflerle sınırlamak bu çizgiyi geri getiriyor.
Bir yay uzunluğu kapısı, uç kapağı kırpmasını yerel hale getirdi ve kaydedilmiş her durumu düzeltti. Aşırı bir sentetik kanca, düşük basınçlı bir ucun kırpılmış uç kapağıyla birleştiği yerde yine de küçük bir beyaz çentik gösterdi. Üçüncü motor bir geometri kusurunu yine bir diğeriyle değiştirmişti.
MaLiang sert sınırı kaldırıyor
MaLiang, tek bir kapalı kontur yerine tekrarlanan doku damgalarından bir çizgi oluşturur. Yol oluşturucusu, bitişik örneklerin orta noktalarından geçen ikinci dereceden Bézier parçaları kullanır ve çizgi renderlayıcısı damgaları bu yol boyunca düzenli bir adımla yerleştirir. Damga boyutu, dönüşü, rengi ve opaklığı çizgi boyunca değişebilir.
Yumuşak bir damganın ayrı bir uç kapağı, birleşimi veya sarım kuralı yoktur. Yöndeki keskin bir değişim, üst üste binen işaretler dizisi olarak kalır. Geri izlemeler boş bir çokgen cebi oluşturamaz ve modelde uç noktası kırpması bulunmadığı için bir uç noktası yarı düzlemi çizginin ortasını kesemez.
Kodu değil, modeli benimsedik. Mevcut HighlighterStrokeBuilder dört işlem gerçekleştiriyor:
- Yalnızca basıncı
0.005değerinin altında olan uç noktası örneklerini kaldırır. - Konumları, orta noktalardan geçen ikinci dereceden Bézier parçalarıyla yumuşatır.
- Yay uzunluğunun her noktası için bir dairesel damga yerleştirir.
- Damga çapını normalize edilmiş basınçtan belirler ve damgaları source-over alfasıyla birleştirir.
Oluşturucu 686 satırdan 190 satıra düştü. Azimut, yükseklik, kontur oluşturma, birleşim maskeleri, uç yönü pencereleri, uç kapağı uzatması ve uç kapağı kırpması fosforlu kalem yolundan çıkarıldı. Girdi modeli bunları desteklediği için Pencil yönelimini hâlâ kaydediyoruz; fosforlu kalemin kendisi artık bu alanları okumuyor.
MaLiang’ın basınç formülü cihaz kalibrasyonu gerektirdi
MaLiang, damga boyutunu şu formüle eşdeğer bir formülle belirler:
Bu formül, force değerinin 0 ile 1 arasındaki aralığın kullanılabilir bir bölümünü kullandığını varsayar. Yaklaşık 3,400 Apple Pencil örneğinde medyan basınç 0.047, maksimum basınç ise 0.178 idi. Bu değerleri doğrudan formüle vermek, normal yazıyı nominal fırça boyutundan çok daha ince hale getirdi.
Bu nedenle gözlemlenen medyan etrafında normalleştirme yapıyor, ardından üs uygulayıp sonucu sınırlandırıyoruz:
Medyan yazı basıncı artık nominal 12 punto çapa eşleniyor. Çok hafif temas, nominal boyutun 35% düzeyinde görünür kalıyor; ağır basınç ise büyümeyi 120% düzeyinde durduruyor.
Bu kalibrasyon MaLiang’a değil, giriş cihazına ve fırça davranışına aittir. Kendi basınç dağılımımızı ölçmeden özgün formülü kopyalamak, mimariyi koruyup yanlış fırçayı üretmiş olurdu.
Damga başına alfa, bilinen örtüşme sayısından türetildi
MaLiang, üst üste binen yarı saydam damgaları deneysel alpha ÷ overlapping × 2.5 ifadesiyle dengeler. Fırçamızın sabit 12 punto nominal çapı ve 1 punto damga adımı var; dolayısıyla merkez çizgisi yaklaşık 12 katman alıyor. Bizim durumumuzda örtüşme sayısı biliniyor ve bu dengeleme tahmin edilmek yerine çözülebiliyor.
Hedef çizgi opaklığı
dolayısıyla tek bir damga için gereken değer:

Kaydedilmiş cihaz yollarını yeniden oynatan son renderlayıcı. Kancalar, çapraz kesikler ve beyaz cepler yok.
Kabul ettiğimiz iki maliyet
Kendiyle kesişimler daha koyu hale geliyor. source-over birleştirmesi, aynı alandan önceki bir geçişte bırakılmış damgalar da dahil olmak üzere her damgayı sayıyor; kapsama birleşimi motoru bundan kaçınmıştı. Daha koyu örtüşmeyi kabul ettik, çünkü öngörülebilir ve çizginin şeklini bozulmadan bırakıyor.
Uçlar basınçla daralıyor. Gerçek bir kalkış basıncı azaltır, bu da damga çapını küçültür; böylece çizgi tam genişlikte kare bir uç kapağıyla bitmek yerine daralır. Üç geometri motorunun tamamında bu kare uç kapağını bir gereklilik olarak ele almıştık. Bundan vazgeçmek, yukarıdaki kusurların birkaçının arkasındaki kırpma ve uç noktası mekanizmasını ortadan kaldırdı; bu nedenle daralan ucu kabul ettik.
Gönderdiğimiz model, öncekilerden daha az iş yapıyor. Ölçülen yazı olayını koruyor, cihaz kalibrasyonundan sonra basınca tepki veriyor ve küresel olarak geçerli bir kontur gerektirmeden kıvrımlı ve kendiyle kesişen yolları render ediyor. Keski biçimli bir uç, kusursuz biçimde kare bir uç kapağı ve ham örnekler arasındaki tam polilin, bunun karşılığında vazgeçtiğimiz özellikler.
Bu incelemeden çıkan sonuçlar arasında, basınç eşiği diğer projelere en doğrudan aktarılabilen sonuçtur. 0.005 değeri gerçek girdideki iki küme arasındaki boşluktan geldi: 0.01 ve üzerindeki yazı ile 0.002 ve altındaki havadaki izleme. Aynı kaydedilmiş çizgiler daha sonra her geometri onarımının nerede başarısız olduğunu gösterdi; bu da model değişikliğini gerekçelendirdi ve kabul ettiğimiz iki maliyeti tanımladı.