Lulucat

तीन हायलाइटर भूमिती इंजिन्स सोडून स्टॅम्प्सकडे का वळलो

Gaoge ZhangGaoge Zhang

Apple Pencil च्या प्रत्यक्ष डेटाने कृत्रिम चाचण्यांमधून सुटलेल्या त्रुटी दाखवल्या आणि Lulucat Notes ला तीन आउटलाइन इंजिन्समधून MaLiang स्टॅम्प मॉडेलकडे नेले.

अद्ययावत

Lulucat Notes ची सुरुवात नेहमीच्या हायलाइटर रेंडररने झाली: सॅम्पल केलेल्या पाथभोवती भौमितिक आउटलाइन काढायची आणि ती अर्धपारदर्शक रंगाने फक्त एकदा भरायची. एकदाच केलेला फिल अर्धपारदर्शक सेगमेंट्स एकमेकांवर येतात तेव्हा तयार होणारे गडद पट्टे टाळतो. पण त्यामुळे प्रत्येक जॉइन, सेल्फ-इंटरसेक्शन, कॅप आणि छोटा स्ट्रोक एकाच भूमितीच्या समस्येचा भाग बनतो.

कस्टम भूमितीच्या तीन पिढ्यांनी प्रत्येकी एक प्रकारची चूक दूर केली, आणि त्याच वेळी दुसरी उघड केली. सध्या आम्ही MaLiang वर आधारित मऊ वर्तुळाकार स्टॅम्प्स वापरतो; Harley-xk यांनी बनवलेला हा MIT-परवानाधारक ओपन-सोर्स ड्रॉइंग फ्रेमवर्क आहे. आउटलाइनच्या अपयशांचा एक मोठा वर्ग काढून टाकण्याच्या बदल्यात, गडद सेल्फ-इंटरसेक्शन्स आणि दाब कमी होताना निमुळती होणारी टोके आम्ही मान्य केली.

iPad वर टिपलेले निळे हायलाइटर हस्ताक्षर; अनेक स्ट्रोक्सच्या सुरुवातीला आणि शेवटी पातळ हुक्स दिसतात.

228% झूमवरचा मूळ डिव्हाइस स्क्रीनशॉट. अनेक स्ट्रोक्सच्या दोन्ही टोकांवर लहान हुक्स दिसतात.

कृत्रिम दृश्यांनी न दाखवलेले हुक्स प्रत्यक्ष हस्ताक्षरात दिसले

आमच्या synthetic scenes मध्ये सरळ रेषा, आर्क्स, छोटे टॅप्स, बॅकट्रॅक्स, दाबातील बदल आणि तीव्र वळणे होती, आणि ती सर्व नीट रेंडर झाली. तरी प्रत्यक्ष चिनी हस्ताक्षराने अनेक स्ट्रोक्सच्या सुरुवातीला आणि शेवटी साधारण 1–3 पॉइंट्स लांबीचे हुक्स निर्माण केले.

म्हणून अॅपमध्ये सेव्ह बटण घातले आणि 29 पूर्ण स्ट्रोक्स रेकॉर्ड केले. प्रत्येक sample मध्ये position, radius, pressure, azimuth, altitude, timestamp आणि UIKit estimation flags जतन झाले. Offline program ने तो JSON decode करून अॅप वापरत असलेल्या Stroke.swift, StrokeRenderer.swift आणि HighlighterStrokeBuilder.swift याच फाइल्स compile केल्या. हुक्ससह त्याचा output डिव्हाइस स्क्रीनशॉटशी स्ट्रोक-नुसार जुळला.

Offline renderer ने recorded input शिवाय दुसरे काही न वापरता दोष पुन्हा निर्माण केला. त्यामुळे कारण display behavior मध्ये नसून samples आणि geometry मध्ये होते. Renderer मध्ये गेलेले नेमके samples आता तपासता येत होते.

Touchdown आणि lift-off वेळी Pencil ची estimated orientation कारणीभूत असावी असे पहिले गृहीतक होते. डेटाने ते चुकीचे ठरवले. सर्व 29 स्ट्रोक्समध्ये पहिल्या आणि शेवटच्या आठ samples च्या आत azimuth जास्तीत जास्त 0.09 radians आणि altitude जास्तीत जास्त 0.02 radians बदलले; त्या iPad वर प्रत्येक estimation flag false होता. Pressure आणि direction झपाट्याने बदलले, पण orientation स्थिर राहिले.

डेटामधील स्पष्ट फट pressure floor ठरवते

नोंदवलेल्या प्रत्येक स्ट्रोकचा शेवटचा sample pressure 0.000 घेऊन आला. लिहितानाचे samples 0.01 किंवा त्याहून जास्त होते; Pencil पृष्ठभागावरून उठल्यानंतरचे samples 0.002 किंवा त्याहून कमी होते. हवेतले हे samples अनेकदा 2–7 पॉइंट्स चालू राहिले आणि 0.8–9.0 पॉइंट्स पुढे गेले; त्यांची दिशा लिहिलेल्या स्ट्रोकपासून साधारण 90 degrees दूर वळू शकत होती.

Stroke 2 मध्ये याची पद्धत दिसते. त्याचा body 143 ते 180 degrees दरम्यान संपला. त्यानंतर low-pressure tail −90 ते −67 degrees पर्यंत वळून आणखी 8.5 points गेली. Outline renderer ने शेवटच्या airborne point ला खरा endpoint समजले आणि त्या बिंदूकडे भूमितीचा अरुंद तुकडा ताणला.

मोजलेल्या pressure मध्ये writing आणि airborne movement यांच्यात जवळजवळ एका order of magnitude ची फट होती. म्हणून cutoff त्या फटीत ठेवला:

private static let pressureFloor: CGFloat = 0.005

फक्त दोन्ही टोकांवरील low-pressure samples काढले जातात. Stroke च्या मधोमधला low-pressure sample त्याचा भाग राहतो. हा नियम measured event boundary प्रमाणे चालतो आणि लिहिलेला path बदलत नाही.

ट्रिम करण्यापूर्वीचा instrumented close-up: हिरवे input samples स्ट्रोकपासून दूर वळतात, तर लाल outline अरुंद hook तयार करते.

ट्रिमच्या आधी renderer कॅपला airborne samples वर anchor करतो. हिरवे dots raw samples आहेत; लाल रेषा generated outline आहे.

ट्रिमनंतरचा instrumented close-up: शेवटच्या written samples जवळ निळा fill संपतो आणि कमी-दाबाचे हिरवे dots त्याच्या बाहेर राहतात.

0.005 पेक्षा कमी samples कापल्यानंतर कॅप शेवटच्या written point वर संपतो. हा pressure floor सध्याच्या stamp renderer मध्येही आहे.

या floor ने लांब airborne hooks काढून टाकले. 0.005 च्या वर pressure टिकलेले छोटे touchdown drags आणि lift-off arcs लिहिलेली हालचाल असल्याने वाचले. ही हालचाल outline model मध्ये हाताळणे पुढच्या geometry rounds कडे घेऊन गेले.

तीन भूमिती इंजिन्सनी दोष फक्त हलवले

पहिली पिढी: एक 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 होती, आणि zero-winding pockets टाळण्यासाठी flat caps मध्ये छोट्या asymmetries वापरल्या.

Single fill ने stroke मधील opacity accumulation सोडवले, पण प्रत्येक turn वर valid polygon लागायचा. Touchdown drags मुळे cap आणि body मध्ये wedges झाले. Backtracks मुळे white crescent pockets होऊ शकत होत्या. Sharp turns मुळे inner outline self-intersect होऊन न भरलेली notch राहू शकत होती.

दुसरी पिढी: टोकांभोवती outline ची दुरुस्ती

दुसऱ्या engine ने outline कायम ठेवून local repairs जोडले: backtrack trimming, सुरुवातीला estimated properties साठी damping, endpoint straightening, transient-direction trimming आणि छोटा cap extension. प्रत्येक patch ने reproduce केलेला एक दोष हाताळला. एकत्रित परिणाम असा झाला की endpoint behavior अनेक thresholds आणि windows वर अवलंबून राहिले.

प्रत्यक्ष लेखनाने या windows च्या बाहेरचे cases दाखवले. ठरावीक 24-point straightening window ने छोट्या strokes मधले जाणूनबुजून ठेवलेले hooks पुसले. Adaptive window ने ते वाचवले, पण lift-off arcs अजूनही दिसणारे corners बनू शकत. Direction-aware trimming ने अधिक transient motion काढले; त्याची किंमत एका recorded stroke च्या खरोखर वक्र सुरुवातीतील सुमारे 2.5 points गमावणे होती.

प्रत्येक patch ने आपले target case दुरुस्त केले. तरी खालील model ला noisy, curved input मध्येही valid राहणारी hard outline आवश्यक होती.

तिसरी पिढी: clipped square caps असलेले coverage union

तिसऱ्या engine ने एकच contour तयार करणे बंद केले. Grayscale mask मध्ये प्रत्येक segment साठी trapezoid आणि प्रत्येक interior sample वर disc काढला, coverage union घेतला आणि मग highlighter color एकदा लावला. Renderer polygon winding वर अवलंबून राहिला नाही, म्हणून backtrack crescents, self-intersection darkening आणि zero-winding holes नाहीसे झाले.

Union ने टोके गोल केली. आम्ही दोन endpoint half-planes विरुद्ध coverage clip करून square caps परत आणले. या clip ने constant-pressure straight lines ठीक केल्या, पण मोठी अडचण आणली: curved stroke चा body endpoint half-plane ओलांडू शकतो, आणि cap clip मग stroke च्या मध्याचा काही भाग कापते. Live input मध्ये endpoint direction बदलत राहिल्याने clipped area हलत राहिला आणि stroke flicker झाला.

कॅप-क्लिप दुरुस्तीपूर्वीचा वक्र निळा स्ट्रोक; त्याचा वरचा भाग तिरक्या रेषेने कापलेला आहे आणि हिरवे डॉट्स पूर्ण sampled path दाखवतात.

Global cap constraint वक्र stroke च्या body मधून कापते.

Local arc-length gate जोडल्यानंतरचा तोच वक्र निळा stroke; हिरव्या sampled path भोवती दोन्ही branches परत आल्या आहेत.

Endpoint पासून एका half-width च्या आत असलेल्या primitives पुरते cap clipping मर्यादित केल्याने हा stroke परत मिळतो.

Arc-length gate मुळे cap clipping local झाले आणि प्रत्येक recorded case ठीक झाला. तरी extreme synthetic hook मध्ये low-pressure tip clipped cap ला भेटतो तिथे छोटी white notch दिसत राहिली. तिसऱ्या engine ने पुन्हा एका geometry failure ची जागा दुसऱ्या failure ने घेतली.

MaLiang ने hard boundary काढून टाकली

MaLiang एका बंद outline ऐवजी repeated texture stamps वापरून stroke बनवते. त्याचा path generator लागोपाठच्या samples च्या midpoints मधून जाणारे quadratic Bézier segments वापरतो, आणि 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 चार ऑपरेशन्स करतो:

  1. ज्यांचा pressure 0.005 पेक्षा कमी आहे असे फक्त endpoint samples काढतो.
  2. Midpoint quadratic Bézier segments ने positions smooth करतो.
  3. Arc length मधील प्रत्येक point साठी एक circular stamp ठेवतो.
  4. Normalized pressure वरून stamp diameter ठरवून source-over alpha ने stamps composite करतो.

Builder 686 lines वरून 190 वर आला. Azimuth, altitude, outline construction, union masks, endpoint direction windows, cap extension आणि cap clipping हे सर्व highlighter path मधून काढले गेले. Input model ते समर्थित करत असल्याने Pencil orientation आम्ही अजून record करतो, पण highlighter स्वतः ही fields वाचत नाही.

MaLiang च्या pressure formula साठी device calibration लागले

MaLiang खालीलशी समतुल्य formula वापरून stamp size ठरवते:

यात force हा 0 ते 1 या range मधील उपयोगी भाग वापरतो असे गृहीत आहे. आमच्या 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 केला:

Writing pressure चा median आता nominal 12-point diameter ला map होतो. फार हलका स्पर्श nominal size च्या 35% वर दिसत राहतो, तर जास्त pressure 120% वर वाढणे थांबवतो.

ही calibration MaLiang ची नसून input device आणि brush behavior ची आहे. स्वतःचे pressure distribution न मोजता original formula कॉपी केले असते, तर architecture वाचले असते पण brush चुकीचा झाला असता.

माहीत असलेल्या overlap count मधून per-stamp alpha मिळाला

MaLiang overlapping translucent stamps साठी empirical expression alpha ÷ overlapping × 2.5 वापरते. आमच्या brush चा nominal diameter 12 points आणि stamp step 1 point आहे; म्हणून centerline वर साधारण 12 layers येतात. या प्रकरणात overlap count माहीत असल्यामुळे compensation अंदाजाने नव्हे तर गणनेने मिळते.

Target stroke opacity आणि समान source-over layers असताना, stamp opacity मुळे

म्हणून एका stamp साठी आवश्यक आहे

आणि असल्यास प्रत्येक stamp सुमारे opacity वापरतो. पुन्हा पुन्हा composition केल्यावर center इच्छित रंगापर्यंत पोहोचतो; edge ला कमी stamps मिळाल्याने one- किंवा two-point feathered transition राहते.

अंतिम soft-stamp model ने रेंडर केलेल्या विविध निळ्या हायलाइटर स्ट्रोक्सचे पान; त्यात loops, hooks, short marks आणि self-intersections आहेत.

नोंदवलेल्या device paths पुन्हा चालवणारा अंतिम renderer. Hooks, diagonal cuts आणि white pockets नाहीत.

आम्ही स्वीकारलेल्या दोन किंमती

Self-intersections अधिक गडद होतात. Source-over composition प्रत्येक stamp मोजते; त्याच क्षेत्रातून आधीच्या pass मध्ये गेलेल्या stamps चा समावेश होतो. Coverage-union engine ने हे टाळले होते. तरी darker overlap predictable असून stroke shape अबाधित ठेवते, म्हणून आम्ही तो स्वीकारला.

Pressure कमी झाल्याने endpoints अरुंद होतात. प्रत्यक्ष lift-off मुळे pressure कमी होते, stamp diameter घटतो आणि stroke full-width square cap ने संपण्याऐवजी taper होतो. तिन्ही geometry engines मध्ये आम्ही square cap ला requirement मानले होते. ते सोडल्याने वरील अनेक failures मागची clipping आणि endpoint machinery दूर झाली, म्हणून tapered end स्वीकारला.

आम्ही ship करत असलेले model आधीच्या models पेक्षा कमी काम करते. ते measured writing event जपते, device calibration नंतर pressure ला प्रतिसाद देते आणि globally valid outline न मागता curved तसेच self-crossing paths रेंडर करते. Chisel-shaped nib, अगदी square cap आणि raw samples मधील exact polyline ही त्यासाठी आम्ही सोडलेली features आहेत.

Pressure floor इतर projects मध्ये सर्वात थेट नेणे शक्य आहे. 0.005 ही value प्रत्यक्ष input मधील दोन clusters च्या मधल्या gap मधून आली: 0.01 आणि त्याहून जास्त writing, 0.002 आणि त्याहून कमी airborne tracking. त्याच recorded strokes नी प्रत्येक geometry repair कुठे फेल झाली ते दाखवले. त्या failures मुळे stamp model आणि त्याच्या दोन किंमतींकडे आम्ही वळलो.