دو ایریزر، اور مٹانے کا اختیار اسٹروک کے پاس کیوں ہے
ایریزر اسٹروک کو الگ element بنانا ایک واضح ڈیزائن ہے۔ آبجیکٹ کو منتقل کرنا اسے توڑ دیتا ہے؛ Lulucat Notes میں مٹانا اسی اسٹروک کے ساتھ رہتا ہے جس سے یہ سیاہی مٹاتا ہے۔
نوٹ بک میں کچھ مٹاتے وقت لوگ دو مختلف باتوں کی طرف اشارہ کرتے ہیں۔ کبھی کوئی نشان غلط ہوتا ہے اور پورا نشان ختم ہونا چاہیے: رہنما لکیر، کاٹا ہوا لفظ، یا کاغذ پر ٹکی ہتھیلی سے بن جانے والا بے محل اسٹروک۔ کبھی نشان کا صرف ایک حصہ غلط ہوتا ہے: کسی حرف کا آدھا حصہ، خاکے کی ایک شاخ، یا ہائی لائٹر کی اس sweep کا آخری سرا جو جملے سے آگے نکل گیا ہو۔ Lulucat Notes اب دونوں کام دو الگ tools کے طور پر فراہم کرتا ہے۔
زیادہ مشکل design سوال یہ ہے کہ مٹانا کہاں رہے گا۔
اسٹروک ایریزر پہلے آیا
پہلا eraser پورے strokes حذف کرتا تھا۔ ہر touch ہر stroke کو جانچتا ہے: bounding boxes تقریباً سب کو خارج کر دیتے ہیں، اور باقی strokes پر eraser radius اور اس segment کے ساتھ interpolate کی گئی stroke radius کے مجموعے کے مقابل point-to-segment squared distance test چلایا جاتا ہے۔ width pressure کے ساتھ بدلتی ہے، اس لیے ایک nominal width ان باریک strokes کو حذف کر دے گی جنہیں صارف نے چھوا ہی نہیں، اور ان موٹے strokes کو نظر انداز کر دے گی جنہیں چھوا گیا تھا۔
ایک gesture ایک undo entry میں ضم ہو جاتا ہے، اور اس entry میں ہر ہٹائے گئے stroke کو اس کے اصل index کے ساتھ محفوظ کیا جاتا ہے۔ Undo انہیں reverse order میں واپس داخل کرتا ہے، اس لیے stacking order بالکل پہلے جیسا ہو جاتا ہے۔
یہ ایریزر تیز، قابلِ پیش گوئی، اور آدھا لفظ مٹانے سے قاصر ہے۔
MaLiang ایریزر کو برش بنا دیتا ہے
MaLiang وہ MIT-licensed drawing framework ہے جس سے ہم پہلے ہی highlighter stamp model لے چکے تھے؛ یہ partial erasing کا جواب 24 lines میں دیتا ہے۔ اس کا Eraser ایک Brush subclass ہے جو صرف blend options کو override کرتا ہے، تاکہ add کے بجائے subtract کرے۔ ایک eraser stroke بھی باقی تمام strokes کی طرح اسی list میں ایک element ہوتا ہے۔ Undo اس element کو ہٹا دیتا ہے۔ کوئی موجودہ stroke تبدیل نہیں ہوتا، کیونکہ erasure صرف replay کے دوران، list order میں ہوتی ہے۔
ہم نے یہ model ship کیا، اور دو device sessions نے اسے توڑ دیا۔
آبجیکٹ کی منتقلی غائب مالک کو ظاہر کرتی ہے
پہلی رپورٹ: lasso سے strokes منتخب کریں، انہیں drag کر کے منتقل کریں، اور سوراخ پیچھے رہ جاتا ہے۔

وہی erased strokes، دائیں طرف منتقل کیے ہوئے۔ اوپر: eraser اپنا الگ element ہے۔ نیچے: erasure strokes کے پاس ہے۔
Selection نے معاملہ مزید خراب کیا۔ منتخب strokes bitmap سے overlay میں اٹھائے جاتے ہیں، جو انہیں eraser element کے بغیر براہِ راست draw کرتا ہے، اس لیے جس لمحے آپ نے erased stroke کو منتخب کیا وہ پھر پورا دکھائی دینے لگا۔
دوسری رپورٹ: eraser highlighter کے اوپر تیرتا تھا۔ ہمارا renderer دو passes میں replay کرتا ہے، پہلے highlighters اور پھر pens، کیونکہ highlighter ink کو ink کے نیچے بیٹھنا چاہیے۔ ایک eraser element کو دونوں passes میں حصہ لینا ہوتا ہے، ورنہ وہ دونوں قسم کی ink نہیں مٹا سکتا۔ pen pass میں حصہ لینے کا مطلب ہے تمام highlighters کے draw ہونے کے بعد چلنا، اس لیے eraser بعد میں وقت میں draw کیے گئے highlighter strokes بھی کاٹ دیتا تھا۔
دونوں ناکامیوں کی جڑ ایک ہے۔ MaLiang میں نہ lasso ہے، نہ move، نہ brushes کے درمیان layer order، اس لیے وہاں بے مالک element کافی ہے۔ ہمارے canvas میں یہ تینوں موجود ہیں، اور بے مالک erasure کے پاس اس سوال کا کوئی متعین جواب نہیں کہ ink منتقل ہو تو کیا ہوتا ہے یا یہ کس layer پر کام کر رہی ہے۔
اسٹروک کے پاس موجود erasure
ہر Stroke اب اپنی erase paths رکھتا ہے۔ ایریزر جانچتا ہے کہ وہ کن strokes کو چھوتا ہے اور ہر ایک میں canvas coordinates میں ایک path شامل کرتا ہے۔ ایک stroke کو render کرتے وقت اس کی ink اور اس کی اپنی erasures کو ایک transparency layer کے اندر draw کیا جاتا ہے:
context.beginTransparencyLayer(auxiliaryInfo: nil)
drawInk(stroke, in: context, clip: clip, colorOverride: colorOverride)
context.setBlendMode(.destinationOut)
for path in stroke.erasePaths { fillErasePath(path, in: context) }
context.endTransparencyLayer()
Destination-out destination کو برقرار رکھتا ہے اور source alpha کے تناسب سے اسے ہٹاتا ہے:
شفافیت کی تہہ کے اندر destination صرف وہ ایک stroke ہے، اس لیے operation کاغذ، نیچے موجود strokes، یا بعد میں draw کیے جانے والے strokes تک نہیں پہنچ سکتا۔ جن strokes میں erasures نہیں ہیں ان کے لیے layer کی کوئی لاگت نہیں، اور ایسے strokes تقریباً سبھی ہیں: وہ original path استعمال کرتے ہیں۔
Ownership دونوں ناکامیوں کا براہِ راست جواب دیتی ہے۔ Translation erase paths کو sample points کے ساتھ ساتھ منتقل کرتی ہے، اس لیے منتقل کیا گیا stroke اپنے gaps ساتھ لے جاتا ہے۔ selection overlay وہی stroke object draw کرتا ہے، اس لیے منتخب stroke وہی gaps دکھاتا ہے۔ Layer order اہم نہیں رہتا، کیونکہ erasure اپنے stroke کے سوا کسی چیز پر لاگو نہیں ہوتی۔
Undo gesture کی سطح پر کام کرتا ہے: ایک entry میں اس gesture سے پیدا ہونے والا ہر (stroke, path) pair درج ہوتا ہے، undo ان paths کو ہٹا دیتا ہے، redo انہیں دوبارہ append کرتا ہے۔ erasure کے نیچے موجود ink کبھی edit نہیں ہوتی، اس لیے کسی چیز کو reconstruct نہیں کرنا پڑتا۔
لائیو feedback tile bitmap کے راستے آتا ہے
جو stroke draw ہو رہا ہو اسے معمول کے مطابق composited page کے اوپر overlay کے طور پر render کیا جاتا ہے۔ وہاں erasure کام نہیں کر سکتی، کیونکہ وہ overlay کاغذ کے ساتھ ساتھ ink کے اوپر بھی بیٹھی ہوتی ہے، اور destination-out کاغذ میں بھی سوراخ کر دے گا۔
لہٰذا ایریزر فوری طور پر strokes میں لکھتا ہے، اور نئے samples کے ہر batch پر صرف متاثرہ rectangle کا مقامی replay چلتا ہے۔ tile pipeline پہلے ہی rectangle صاف کر کے اس سے intersect کرنے والے strokes replay کرتی ہے؛ eraser اسی path کو دوبارہ استعمال کرتا ہے اور drawing semantic کا کوئی دوسرا طریقہ شامل نہیں کرتا۔
دونوں ایریزر، ساتھ ساتھ
toolbar میں ایک ایسے button کے بجائے جس میں mode چھپا ہو، eraser کے دو buttons ہیں۔ partial eraser stroke کا ایک حصہ نکال دیتا ہے۔ stroke eraser چھونے پر پورے strokes حذف کر دیتا ہے۔ ان کے radii الگ constants ہیں، اور فی الحال دونوں 4 points ہیں۔
پورا stroke حذف کرنے سے اس کے erase paths بھی ساتھ ختم ہو جاتے ہیں، کیونکہ وہ اسی stroke کا حصہ ہیں، اور undo دونوں کو ایک ساتھ واپس لاتا ہے۔ erase paths بھی باقی ہر چیز کی طرح saved file کا حصہ ہیں، اور quantize کیے جاتے ہیں: radius اور coordinates ایک point کے دسویں حصے کے طور پر۔
ایک limitation باقی ہے۔ مکمل طور پر مٹ جانے والا stroke data کے طور پر موجود رہتا ہے۔ render میں کچھ نہیں دکھتا، مگر lasso اسے اب بھی منتخب کر سکتا ہے اور stroke eraser اسے اب بھی حذف کر سکتا ہے، اور یہ document میں اب بھی جگہ گھیرتا ہے۔ مکمل مٹائے گئے strokes کو جمع کرنے کے لیے coverage test درکار ہے جو ہم نے ابھی نہیں لکھا، اس لیے فی الحال وہ رہتے ہیں۔
جب کسی دوسرے project کا model آپ کے project میں کام کرنا بند کرے، تو فرق عموماً اس code میں نہیں ہوتا جو آپ نے copy کیا ہے۔ فرق ان features میں ہوتا ہے جو اس project میں موجود نہیں۔