Lulucat

हमने तीन हाइलाइटर ज्यामिति इंजनों को स्टैम्प से क्यों बदला

Gaoge ZhangGaoge Zhang

वास्तविक Apple Pencil डेटा ने वे खामियाँ उजागर कीं जिन्हें synthetic tests नहीं पकड़ पाए और Lulucat Notes को तीन outline engines से MaLiang stamp model तक ले आया।

अपडेट किया गया

Lulucat Notes की शुरुआत एक पारंपरिक highlighter renderer से हुई: sampled path के चारों ओर एक ज्यामितीय outline बनाना, फिर उस outline को translucent color से एक बार भरना। एक ही fill translucent segments के overlap होने पर दिखने वाली गहरी पट्टियों से बचाता है। साथ ही, हर join, self-intersection, cap और short stroke को उसी geometry problem का हिस्सा बना देता है।

Custom geometry की तीन पीढ़ियों ने एक-एक तरह की defect हटाई और दूसरी उजागर कर दी। अब हम जिस renderer को ship करते हैं, वह MaLiang पर आधारित soft circular stamps इस्तेमाल करता है। MaLiang, Harley-xk का MIT-licensed open-source drawing framework है। Outline failures की एक पूरी श्रेणी हटाने के बदले हमने darker self-intersections और pressure-tapered ends स्वीकार किए।

iPad पर कैप्चर की गई नीली highlighter handwriting, जिसमें कई strokes की शुरुआत और अंत पर पतले hooks दिखाई देते हैं।

228% zoom पर डिवाइस का मूल screenshot। कई strokes के दोनों सिरों पर छोटे hooks दिखाई देते हैं।

वास्तविक handwriting ने ऐसे hooks बनाए जो synthetic scenes में नहीं दिखे

हमारे synthetic scenes में straight lines, arcs, short taps, backtracks, pressure changes और sharp turns शामिल थे, और वे सभी साफ़-सुथरे render हुए। वास्तविक Chinese handwriting फिर भी कई strokes की शुरुआत और अंत में लगभग 1–3 points लंबे hooks बना रही थी।

इसलिए हमने app में एक save button जोड़ा और 29 complete strokes रिकॉर्ड किए। हर sample ने अपनी position, radius, pressure, azimuth, altitude, timestamp और UIKit estimation flags बनाए रखे। एक offline program ने उस JSON को decode किया और वही Stroke.swift, StrokeRenderer.swift और HighlighterStrokeBuilder.swift files compile कीं जिनका app इस्तेमाल करता है। उसका output stroke-दर-stroke डिवाइस screenshot से मेल खाता था, hooks समेत।

क्योंकि offline renderer ने केवल recorded input से defect को दोबारा बना दिया, कारण display behavior के बजाय samples और geometry में था। अब हम उन exact samples को पढ़ सकते थे जो renderer में दाखिल हुए थे।

हमारी पहली hypothesis ने touchdown और lift-off पर estimated Pencil orientation को दोषी माना। डेटा ने इसे खारिज कर दिया। सभी 29 strokes में, पहले और आखिरी आठ samples के भीतर azimuth अधिकतम 0.09 radians और altitude अधिकतम 0.02 radians बदले, और उस iPad पर हर estimation flag false था। Orientation स्थिर रहा, जबकि pressure और direction तेज़ी से बदल रहे थे।

डेटा के एक साफ़ gap से pressure floor मिला

हर recorded stroke के आखिरी sample का pressure 0.000 था। Writing samples 0.01 या उससे ऊपर थे, जबकि Pencil के surface से उठने के बाद रिकॉर्ड किए गए samples 0.002 या उससे नीचे थे। वे airborne samples अक्सर 2–7 points तक चलते रहे और 0.8–9.0 points की दूरी तय की; उनकी direction लिखे गए stroke से लगभग 90 degrees तक मुड़ सकती थी।

Stroke 2 इस mechanism को दिखाता है। उसका body 143 और 180 degrees के बीच समाप्त हुआ। फिर low-pressure tail −90 से −67 degrees की ओर मुड़ा और 8.5 points आगे गया। Outline renderer ने आखिरी airborne point को authoritative endpoint माना, इसलिए उसने उस point की ओर geometry का एक संकरा हिस्सा खींच दिया।

मापे गए pressures writing और airborne movement के बीच लगभग एक order of magnitude का gap छोड़ते हैं, इसलिए हमने cutoff को उसी gap के भीतर रखा:

private static let pressureFloor: CGFloat = 0.005

केवल दोनों सिरों पर मौजूद low-pressure samples हटाए जाते हैं। Stroke के बीच का low-pressure sample उसका हिस्सा बना रहता है। यह rule मापी गई event boundary का अनुसरण करता है और लिखे गए path को बिना संपादित किए छोड़ता है।

Trimming से पहले का instrumented close-up: green input samples stroke से दूर मुड़ते हैं, जबकि red outline एक संकरा hook बनाती है।

Trimming से पहले renderer cap को airborne samples पर anchor करता है। Green dots raw samples हैं; red line generated outline है।

Trimming के बाद का instrumented close-up: blue fill आखिरी written samples पर समाप्त होती है और low-pressure green dots उसके बाहर रह जाते हैं।

0.005 से नीचे के samples trim करने के बाद cap आखिरी written point पर समाप्त होती है। यह pressure floor मौजूदा stamp renderer में भी बना हुआ है।

इस floor ने लंबे airborne hooks हटा दिए। जिन छोटे touchdown drags और lift-off arcs का pressure 0.005 से ऊपर रहा, वे बचे रहे, क्योंकि वे written motion हैं। इन motions को outline model के भीतर संभालने से geometry के अगले दौर की शुरुआत हुई।

तीन geometry engines ने defects को बस इधर-उधर किया

पीढ़ी एक: एक outline, एक fill

पहले pressure-sensitive engine ने एक परिचित pipeline अपनाई। उसने एक spine को smooth किया, left और right offsets निकाले, उन्हें एक polygon में जोड़ा और उस polygon को एक बार fill किया। Nib Apple Pencil के azimuth और altitude से नियंत्रित एक ellipse था, इसलिए writing direction के साथ उसका cross-section बदलता था। Short taps के लिए अलग shape थी, और flat caps में zero-winding pockets से बचने के लिए छोटी asymmetries इस्तेमाल की जाती थीं।

Single fill ने stroke के भीतर opacity accumulation की समस्या हल की, लेकिन हर turn पर एक valid polygon की आवश्यकता रखी। Touchdown drags ने cap और body के बीच wedges बनाए। Backtracks white crescent pockets बना सकते थे। Sharp turns inner outline को self-intersect करा सकते थे और एक unfilled notch छोड़ सकते थे।

पीढ़ी दो: सिरों के पास outline की मरम्मत

दूसरे engine ने outline को बनाए रखा और local repairs जोड़े: backtrack trimming, शुरुआती estimated properties के लिए damping, endpoint straightening, transient-direction trimming और एक छोटा cap extension। हर patch एक reproduced failure को address करता था। साथ मिलकर उन्होंने endpoint behavior को कई thresholds और windows पर निर्भर बना दिया।

फिर real writing ने उन windows से बाहर के cases पैदा किए। Fixed 24-point straightening window ने short strokes में जानबूझकर बनाए गए hooks मिटा दिए। Adaptive window ने उन hooks को बचाए रखा, लेकिन lift-off arcs फिर भी दिखाई देने वाले corners बन सकते थे। Direction-aware trimming ने transient motion का अधिक हिस्सा हटा दिया, जिसकी कीमत एक recorded stroke की सचमुच curved शुरुआत के लगभग 2.5 points छोड़ने के रूप में चुकानी पड़ी।

हर patch ने अपने target case को ठीक किया। फिर भी इनके नीचे का model noisy, curved input के साथ valid रहने वाली एक hard outline पर निर्भर था।

पीढ़ी तीन: clipped square caps के साथ coverage union

तीसरे engine ने single contour बनाना बंद कर दिया। उसने grayscale mask में हर segment के लिए एक trapezoid और हर interior sample पर एक disc draw किया, फिर उनका coverage union लिया और highlighter color को एक बार apply किया। Backtrack crescents, self-intersection darkening और zero-winding holes गायब हो गए, क्योंकि renderer अब polygon winding पर निर्भर नहीं था।

Union ने सिरों को round कर दिया, इसलिए हमने दो endpoint half-planes के विरुद्ध coverage को clip करके square caps वापस जोड़े। उस clip ने constant-pressure straight lines को ठीक किया, लेकिन एक अधिक गंभीर defect पैदा किया: curved stroke का body वैध रूप से किसी endpoint half-plane को पार कर सकता है, और cap clip तब stroke के बीच का हिस्सा हटा देता है। Live input के दौरान endpoint direction बदलती रहती थी, इसलिए clipped area हिलता रहता था और stroke flicker करता था।

Cap-clip fix से पहले का curved blue stroke, जिसका ऊपरी body एक diagonal line के साथ कट गया है; green dots पूरा sampled path दिखाते हैं।

Global cap constraint curved stroke के body को काट देती है।

Local arc-length gate जोड़ने के बाद वही curved blue stroke, जिसमें green sampled path के आसपास दोनों branches वापस आ गई हैं।

Cap clipping को endpoint के एक half-width के भीतर के primitives तक सीमित करने से यह stroke वापस ठीक हो जाता है।

एक arc-length gate ने cap clipping को local बना दिया और हर recorded case को ठीक कर दिया। फिर भी एक extreme synthetic hook ने वहाँ छोटा white notch दिखाया जहाँ low-pressure tip clipped cap से मिलती थी। तीसरे engine ने फिर एक geometry failure को दूसरी failure से बदल दिया था।

MaLiang ने hard boundary हटा दी

MaLiang एक बंद outline के बजाय repeated texture stamps से stroke बनाता है। इसका path generator quadratic Bézier segments इस्तेमाल करता है, जो adjacent sample midpoints से होकर गुजरते हैं, और इसका line renderer उस path पर regular step से stamps रखता है। Stroke के साथ stamp size, rotation, color और opacity सभी बदल सकते हैं।

एक soft stamp में अलग cap, join या winding rule नहीं होता। Direction में तेज़ बदलाव overlapping marks की sequence बना रहता है। Backtracks empty polygon pocket नहीं बना सकते, और endpoint half-plane stroke के बीच को काट नहीं सकता, क्योंकि model में endpoint clip होता ही नहीं है।

हमने code के बजाय model अपनाया। मौजूदा HighlighterStrokeBuilder चार operations करता है:

  1. केवल उन endpoint samples को हटाना जिनका pressure 0.005 से कम है।
  2. Midpoint quadratic Bézier segments से positions को smooth करना।
  3. Arc length के हर point पर एक circular stamp रखना।
  4. Normalized pressure से stamp diameter तय करना और stamps को source-over alpha के साथ composite करना।

Builder 686 lines से घटकर 190 lines का रह गया। Azimuth, altitude, outline construction, union masks, endpoint direction windows, cap extension और cap clipping सभी highlighter path से बाहर हो गए। हम अभी भी Pencil orientation रिकॉर्ड करते हैं, क्योंकि input model उसे support करता है, लेकिन highlighter स्वयं अब उन fields को पढ़ता नहीं है।

MaLiang के pressure formula के लिए device calibration ज़रूरी था

MaLiang किसी stamp का आकार एक समतुल्य formula से तय करता है:

इस formula की धारणा है कि force range 0 से 1 के उस उपयोगी हिस्से में आता है। हमारे device data में ऐसा नहीं था। लगभग 3,400 Apple Pencil samples में median pressure 0.047 और maximum 0.178 था। इन values को formula में सीधे डालने पर normal writing nominal brush size से बहुत पतली हो गई।

इसलिए हमने observed median के आसपास normalize किया, फिर exponent लगाया और result को clamp किया:

अब median written pressure nominal 12-point diameter पर map होता है। बहुत हल्का contact nominal size के 35% पर दिखाई देता रहता है, जबकि heavy pressure 120% पर बढ़ना बंद कर देता है।

यह calibration MaLiang के बजाय input device और brush behavior से संबंधित है। अपने pressure distribution को मापे बिना original formula को copy करने से architecture तो वही रहता, लेकिन brush गलत बनता।

Per-stamp alpha ज्ञात overlap count से आया

MaLiang overlapping translucent stamps के लिए empirical expression alpha ÷ overlapping × 2.5 से compensation करता है। हमारे brush का nominal diameter 12-point और stamp step 1-point fixed है, इसलिए उसके centerline को लगभग 12 layers मिलती हैं। हमारे मामले में overlap count ज्ञात है, इसलिए compensation का अनुमान लगाने के बजाय उसे solve किया जा सकता है।

Target stroke opacity और समान source-over layers के लिए, एक stamp की opacity से यह परिणाम मिलता है:

इसलिए एक stamp के लिए आवश्यक है:

और के साथ, हर stamp लगभग opacity इस्तेमाल करता है। Repeated composition के बाद center intended color तक पहुँचता है, जबकि edge को कम stamps मिलती हैं और वहाँ one- या two-point की feathered transition बनी रहती है।

Final soft-stamp model से render किए गए अलग-अलग नीले highlighter strokes की एक sheet, जिसमें loops, hooks, short marks और self-intersections शामिल हैं।

Final renderer recorded device paths को replay कर रहा है। Hooks, diagonal cuts और white pockets मौजूद नहीं हैं।

हमने जो दो लागतें स्वीकार कीं

Self-intersections अधिक dark हो जाते हैं। Source-over composition हर stamp को गिनता है, जिसमें उसी area से पहले गुज़रते समय रखे गए stamps भी शामिल हैं, जबकि coverage-union engine इससे बच गया था। हमने इस darker overlap को स्वीकार किया, क्योंकि यह predictable है और stroke shape को intact रखता है।

Pressure के साथ endpoints संकरे हो जाते हैं। वास्तविक lift-off pressure घटाता है, जिससे stamp diameter घटता है; इसलिए stroke full-width square cap पर समाप्त होने के बजाय taper होता है। तीनों geometry engines में हमने उस square cap को requirement माना था। उसे छोड़ने से ऊपर बताई गई कई failures के पीछे की clipping और endpoint machinery हट गई, इसलिए tapered end स्वीकार करना उचित लगा।

इसलिए जो model हम ship करते हैं, वह पहले वाले models से कम करता है। यह measured writing event को सुरक्षित रखता है, device calibration के बाद pressure पर प्रतिक्रिया देता है और globally valid outline की आवश्यकता के बिना curved तथा self-crossing paths render करता है। Chisel-shaped nib, perfectly square cap और raw samples के बीच exact polyline वे features हैं जिन्हें हमने इसके बदले छोड़ दिया।

इस investigation से निकली बातों में pressure floor सबसे सीधे दूसरे projects में transfer होता है। 0.005 का मान real input में दो clusters के बीच के gap से आया: writing 0.01 और उससे ऊपर, airborne tracking 0.002 और उससे नीचे। उन्हीं recorded strokes ने फिर दिखाया कि geometry की हर repair कहाँ विफल हुई। इन विफलताओं ने stamp model और उससे जुड़ी उन दो लागतों तक पहुँचाया जिन्हें हमने स्वीकार किया।