ยางลบสองแบบ และเหตุใดการลบจึงเป็นของเส้น
การทำให้เส้นยางลบเป็นองค์ประกอบแยกต่างหากดูเป็นการออกแบบที่ชัดเจน แต่การย้ายวัตถุทำให้มันพัง ดังนั้นใน Lulucat Notes การลบจึงเป็นของเส้นที่ถูกลบหมึกออก
เวลาคนลบอะไรในสมุดโน้ต พวกเขาอาจหมายถึงสองเรื่องที่ต่างกัน บางครั้งเครื่องหมายหนึ่งผิดและควรลบทิ้งทั้งเครื่องหมาย: เส้นไกด์ คำที่ขีดฆ่า หรือเส้นที่เกิดจากฝ่ามือซึ่งวางพักอยู่ บางครั้งมีเพียงบางส่วนของเครื่องหมายที่ผิด: อักขระครึ่งหนึ่ง แขนงหนึ่งของแผนภาพ หรือหางของรอยไฮไลต์ที่ลากเลยประโยคไป Lulucat Notes รองรับทั้งสองแบบแล้ว โดยแยกเป็นเครื่องมือคนละตัว
คำถามด้านการออกแบบที่ยากกว่าคือ การลบนั้นอยู่ที่ไหน
ยางลบแบบลบทั้งเส้นมาก่อน
ยางลบตัวแรกจะลบเส้นทั้งหมด การสัมผัสแต่ละครั้งจะทดสอบกับทุกเส้น: bounding box จะคัดเส้นออกไปเกือบทั้งหมด จากนั้นเส้นที่เหลือจะผ่านการทดสอบ point-to-segment squared distance โดยเทียบกับผลรวมของรัศมียางลบและรัศมีเส้นที่อินเทอร์โพเลตไปตามเซกเมนต์นั้น ความกว้างเปลี่ยนไปตามแรงกด ดังนั้นการใช้ความกว้าง nominal ค่าเดียวจะลบเส้นบางที่ผู้ใช้ไม่ได้แตะ และพลาดเส้นหนาที่ผู้ใช้แตะโดน
หนึ่ง gesture จะถูกรวมเป็น undo entry หนึ่งรายการ และรายการนั้นเก็บเส้นแต่ละเส้นที่ถูกลบพร้อมดัชนีเดิมของมัน Undo จะแทรกเส้นเหล่านั้นกลับตามลำดับย้อนกลับ ลำดับการซ้อนจึงกลับมาเหมือนเดิมได้อย่างแม่นยำ
ยางลบแบบนี้เร็ว คาดเดาได้ และไม่สามารถลบคำออกเพียงครึ่งคำ
MaLiang ทำให้ยางลบกลายเป็นแปรง
MaLiang คือเฟรมเวิร์กวาดภาพภายใต้ใบอนุญาต MIT ซึ่งเราเคยยืมโมเดลแสตมป์ของไฮไลต์มาแล้ว และมันตอบโจทย์การลบบางส่วนได้ใน 24 บรรทัด Eraser ของมันเป็นคลาสย่อยของ Brush ที่โอเวอร์ไรด์เพียงอย่างเดียว คือ blend options เพื่อให้ลบแทนที่จะเติม เส้นยางลบเป็นองค์ประกอบในลิสต์เดียวกับเส้นอื่นทุกเส้น Undo จึงลบองค์ประกอบนั้น ไม่มีเส้นเดิมเส้นใดถูกแก้ไข เพราะการลบจะเกิดขึ้นเฉพาะระหว่างการเล่นซ้ำตามลำดับในลิสต์
เราเปิดตัวโมเดลนี้ แล้วเซสชันจากอุปกรณ์สองเซสชันก็ทำให้มันพัง
การย้ายวัตถุเผยให้เห็นเจ้าของที่หายไป
รายงานแรกคือ: เลือกเส้นด้วย lasso ลากเส้นเหล่านั้น แล้วช่องว่างกลับค้างอยู่ที่เดิม

เส้นชุดเดิมที่ถูกลบและย้ายไปทางขวา ด้านบน: ยางลบเป็นองค์ประกอบแยกต่างหาก ด้านล่าง: การลบเป็นของเส้น
การเลือกทำให้เรื่องแย่ลง เส้นที่เลือกจะถูกยกออกจากบิตแมปไปไว้ในโอเวอร์เลย์ ซึ่งวาดเส้นเหล่านั้นโดยตรงโดยไม่มียางลบที่เป็นองค์ประกอบติดไปด้วย ดังนั้นทันทีที่เลือกเส้นที่ถูกลบ มันก็จะกลับมาเต็มเส้นอีกครั้ง
รายงานที่สองคือ: ยางลบลอยอยู่เหนือไฮไลต์ ตัวเรนเดอร์ของเราเล่นซ้ำสองรอบ โดยวาดไฮไลต์ก่อนแล้วจึงวาดปากกา เพราะหมึกไฮไลต์ต้องอยู่ใต้หมึกชนิดอื่น องค์ประกอบยางลบต้องเข้าร่วมทั้งสองรอบ มิฉะนั้นจะลบหมึกได้เพียงชนิดเดียว การเข้าร่วมรอบปากกาหมายความว่าต้องทำงานหลังจากวาดไฮไลต์ทุกเส้นแล้ว ดังนั้นยางลบจึงลบเส้นไฮไลต์ที่ถูกวาดทีหลังตามเวลาได้ด้วย
ความล้มเหลวทั้งสองมีต้นเหตุเดียวกัน MaLiang ไม่มี lasso ไม่มีการย้าย และไม่มีลำดับเลเยอร์ระหว่างแปรง ดังนั้นองค์ประกอบที่ไม่มีเจ้าของจึงเพียงพอในเฟรมเวิร์กนั้น แต่แคนวาสของเรามีทั้งสามอย่าง และการลบที่ไม่มีเจ้าของก็ไม่มีคำตอบที่กำหนดไว้สำหรับคำถามว่า การลบจะเกิดอะไรขึ้นเมื่อหมึกเคลื่อนที่ หรือการลบนี้กำลังทำงานอยู่บนเลเยอร์ใด
การลบที่เป็นของเส้น
ตอนนี้ Stroke ทุกตัวจะเก็บเส้นทางการลบของตัวเอง ยางลบจะทดสอบว่าแตะเส้นใดบ้าง แล้วเติมเส้นทางลงในเส้นแต่ละเส้นที่ถูกแตะ โดยใช้พิกัดของแคนวาส เมื่อเรนเดอร์เส้นหนึ่งเส้น จะวาดหมึกของเส้นนั้นและการลบของตัวเองไว้ภายในเลเยอร์โปร่งใส:
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 จะเก็บปลายทางไว้ และลบมันออกตามสัดส่วนของอัลฟาจากต้นทาง:
ภายในเลเยอร์โปร่งใส ปลายทางก็คือเส้นนั้นเพียงเส้นเดียว ดังนั้นการทำงานจึงไม่อาจไปถึงกระดาษ เส้นที่อยู่ข้างใต้ หรือเส้นที่วาดทีหลังได้ เส้นที่ไม่มีการลบไม่ต้องเสียค่าใช้จ่ายจากเลเยอร์ ซึ่งคิดเป็นเส้นเกือบทั้งหมด: เส้นเหล่านั้นยังใช้พาธเดิม
การมีเจ้าของตอบคำถามทั้งสองได้โดยตรง การแปลงตำแหน่งจะย้ายเส้นทางการลบไปพร้อมกับจุดตัวอย่าง ดังนั้นเส้นที่ถูกย้ายจึงพกช่องว่างของมันไปด้วย โอเวอร์เลย์สำหรับการเลือกวาดออบเจ็กต์เส้นเดียวกัน จึงแสดงช่องว่างเดิมของเส้นที่เลือก ลำดับเลเยอร์ไม่สำคัญอีกต่อไป เพราะการลบไม่เคยส่งผลกับสิ่งอื่นนอกจากเส้นของตัวเอง
การเลิกทำทำงานในระดับ gesture: รายการหนึ่งจะระบุคู่ (stroke, path) ทุกคู่ที่ gesture นั้นสร้างขึ้น การเลิกทำจะลบเส้นทางเหล่านั้น และการทำซ้ำจะเติมกลับเข้าไปอีกครั้ง หมึกที่อยู่ใต้การลบไม่เคยถูกแก้ไข จึงไม่ต้องสร้างอะไรขึ้นใหม่
การตอบสนองแบบสดส่งผ่านบิตแมปแบบไทล์
โดยปกติเส้นที่กำลังวาดจะเรนเดอร์เป็นโอเวอร์เลย์เหนือหน้าที่คอมโพสิตแล้ว การลบทำงานตรงนั้นไม่ได้ เพราะโอเวอร์เลย์อยู่เหนือทั้งกระดาษและหมึก และ destination-out จะเจาะทะลุกระดาษไปด้วย
ดังนั้นยางลบจึงเขียนลงในเส้นทันที และตัวอย่างใหม่แต่ละชุดจะเรียกการเล่นซ้ำเฉพาะที่ของสี่เหลี่ยมบริเวณที่ได้รับผลกระทบ ไปป์ไลน์แบบไทล์มีขั้นตอนล้างสี่เหลี่ยมและเล่นซ้ำเส้นที่ตัดกับสี่เหลี่ยมนั้นอยู่แล้ว ยางลบจึงใช้เส้นทางเดียวกันและไม่ต้องเพิ่มความหมายของการวาดชุดที่สอง
ยางลบทั้งสองแบบเคียงข้างกัน
แถบเครื่องมือมีปุ่มยางลบสองปุ่ม แทนที่จะเป็นปุ่มเดียวที่ซ่อนโหมดไว้ ยางลบบางส่วนจะลบเพียงบางส่วนของเส้น ส่วนยางลบเส้นจะลบเส้นทั้งเส้นเมื่อสัมผัส ทั้งสองแบบใช้ค่าคงที่ของรัศมีแยกกัน และปัจจุบันทั้งคู่มีค่า 4 จุด
การลบเส้นทั้งเส้นจะลบเส้นทางการลบของเส้นนั้นไปด้วย เพราะเส้นทางเหล่านั้นเป็นส่วนหนึ่งของเส้น และการเลิกทำจะนำทั้งสองอย่างกลับมาพร้อมกัน เส้นทางการลบยังเป็นส่วนหนึ่งของไฟล์ที่บันทึกไว้ และถูกควอนไทซ์เหมือนข้อมูลอื่นทั้งหมด: รัศมีและพิกัดเก็บเป็นหน่วยละ 0.1 จุด
ยังมีข้อจำกัดอยู่อย่างหนึ่ง เส้นที่ถูกลบจนไม่เหลืออะไรยังคงมีอยู่ในฐานะข้อมูล มันเรนเดอร์ออกมาเป็นความว่างเปล่า แต่ lasso ยังเลือกมันได้ ยางลบเส้นยังลบมันได้ และมันยังกินพื้นที่ในเอกสารอยู่ การเก็บกวาดเส้นที่ถูกลบจนหมดต้องอาศัยการทดสอบพื้นที่ครอบคลุมที่เรายังไม่ได้เขียน ดังนั้นตอนนี้จึงปล่อยให้มันอยู่ต่อ
เมื่อโมเดลจากอีกโครงการหนึ่งหยุดทำงานในโครงการของคุณ ความแตกต่างมักไม่ได้อยู่ที่โค้ดที่คุณคัดลอกมา แต่อยู่ที่ฟีเจอร์ที่โครงการนั้นไม่มี