4 คะแนน โดย GN⁺ 2024-12-04 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Skia Canvas ช่วยให้ใช้ HTML Canvas Drawing API ใน Node.js สำหรับการเรนเดอร์ทั้งแบบบนหน้าจอและนอกหน้าจอ โดยอิงกับ Google Skia จึงให้ผลลัพธ์ที่ใกล้เคียงกับ <canvas> ของ Chrome มาก
  • สามารถส่งออกผลลัพธ์เป็นเวกเตอร์อย่าง PDF·SVG และบิตแมปอย่าง JPEG·PNG·WEBP ได้ และเชื่อมต่อได้ทั้งเป็นไฟล์, dataURL, Buffer หรือออบเจ็กต์ Sharp
  • รองรับขอบเขตที่อยู่นอกเหนือ Canvas ของเบราว์เซอร์ด้วย เช่น การเรนเดอร์ GUI Window, เฟรมเวิร์กอีเวนต์, Canvas หลายหน้า, การคำนวณเส้นทาง, การแปลงมุมมอง 3D, CSS filter และการควบคุม typography ขั้นสูง
  • การเรนเดอร์แบบอะซิงโครนัสและไฟล์ I/O ถูกประมวลผลบน native thread ใน worker pool ที่ผู้ใช้กำหนดได้ จึงเหมาะกับการสร้างภาพฝั่งเซิร์ฟเวอร์และสภาพแวดล้อมแบบ serverless
  • benchmark วัดโดยแยกโหมด serial และ async โดยใน async จะเริ่มงานซ้ำหลายรายการพร้อมกันเพื่อเปรียบเทียบผลของ multithreading

การใช้งาน Canvas บน Node.js

  • Skia Canvas คือการใช้งาน HTML Canvas Drawing API สำหรับ Node.js ที่รองรับทั้งการเรนเดอร์บนหน้าจอและนอกหน้าจอ
  • ใช้เอนจินกราฟิก Google Skia จึงให้ผลลัพธ์ที่คล้ายกับองค์ประกอบ <canvas> ของ Chrome มาก
  • รวมถึงความสามารถที่ Canvas ของเบราว์เซอร์ยังไม่มีให้ด้วย

รูปแบบเอาต์พุตและวิธีส่งออก

  • เอาต์พุตภาพรองรับทั้ง รูปแบบเวกเตอร์ และ รูปแบบบิตแมป
    • เวกเตอร์: PDF, SVG
    • บิตแมป: JPEG, PNG, WEBP
  • สามารถส่งผลลัพธ์ของ Canvas ไปยังปลายทางได้หลายแบบ
    • บันทึกเป็นไฟล์
    • เข้ารหัสเป็นสตริง dataURL
    • คืนค่าเป็น Buffer
    • คืนค่าเป็นออบเจ็กต์ Sharp
  • สามารถวาดโดยตรงลงใน GUI Window แบบอินเทอร์แอคทีฟ และมีเฟรมเวิร์กอีเวนต์ที่คล้ายกับเบราว์เซอร์

การเรนเดอร์แบบอะซิงโครนัสและหลายหน้า

  • การเรนเดอร์และไฟล์ I/O ทำงานแบบอะซิงโครนัสใน worker pool ที่ใช้ native thread
  • สามารถสร้างหลายหน้าภายใน Canvas เดียวได้
    • สามารถส่งออกเป็น PDF หลายหน้าไฟล์เดียวได้
    • หรือบันทึกเป็นลำดับภาพที่แบ่งออกเป็นหลายไฟล์ได้

ความสามารถด้านกราฟิกที่เหนือกว่า Canvas ของเบราว์เซอร์

การควบคุม Typography

  • การเรนเดอร์ข้อความรองรับหลายบรรทัดและ การตัดบรรทัดอัตโนมัติ
  • มีเมตริกข้อความระดับรายบรรทัด
  • สามารถใช้ฟีเจอร์ OpenType เช่น small-caps และ ligature ด้วยไวยากรณ์มาตรฐาน font-variant
  • ควบคุมระยะห่างระหว่างตัวอักษร ระยะห่างระหว่างคำ และ leading ได้แบบสัดส่วน
  • รองรับ variable fonts และ map ค่าน้ำหนักได้อย่างโปร่งใส
  • สามารถใช้ฟอนต์ที่ไม่ใช่ฟอนต์ระบบซึ่งโหลดจากไฟล์โลคัลได้

ตัวอย่างการใช้งาน

  • ตัวอย่างการสร้างไฟล์ภาพจะสร้าง Canvas แล้วใช้ getContext("2d") เพื่อรับ 2D context จากนั้นใช้ conic gradient เป็น stroke style เพื่อวาดสี่เหลี่ยม
    • บันทึกภาพความหนาแน่นสูงด้วย saveAs("rainbox.png", {density:2})
    • canvas.png ใช้เป็นรูปย่อของ canvas.toBuffer("png")
    • สามารถใส่ผลลัพธ์ของ toDataURL("png") ลงในสตริง HTML ได้
    • สามารถบันทึกแบบซิงโครนัสบน main thread ด้วย saveAsSync("rainbox.pdf") ได้เช่นกัน
  • ตัวอย่างลำดับหลายหน้าใช้ canvas.newPage() เพื่อสร้างหน้าที่มีสีต่างกันแล้วบันทึก
    • บันทึก PDF หลายหน้าเป็น all-pages.pdf
    • ใช้แพตเทิร์น page-{2}.png เพื่อสร้างไฟล์หลายไฟล์ เช่น page-01.png, page-02.png
  • ตัวอย่างการเรนเดอร์ Window สร้าง Window(300, 300) แล้ววาดวงกลมที่ความหนาของเส้นเปลี่ยนตามค่าเฟรมในอีเวนต์ draw
  • ตัวอย่างการผสานกับ Sharp.js แสดงโฟลว์การแปลงผลลัพธ์ Canvas เป็น ออบเจ็กต์ Sharp เพื่อประมวลผลภายหลัง หรือวาดภาพที่สร้างด้วย Sharp ลงบน Canvas ด้วย loadImage

วิธี benchmark และผลลัพธ์

  • benchmark ทดสอบ Skia Canvas โดยแบ่งเป็นสองโหมด
    • serial: รอให้งานเรนเดอร์แต่ละงานเสร็จก่อนจึงไปยังรอบถัดไป
    • async: เริ่มการทดสอบทุกรอบพร้อมกันและรันแบบขนานด้วยการรองรับ multithreading
  • ดูผลลัพธ์ทั้งหมดได้ที่หน้าผลลัพธ์ canvas-benchmarks
  • การทดสอบ startup latency ที่ 100 รอบ skia-canvas ทำเวลาได้ต่ำกว่า 1ms ต่อรอบ รวม 33ms
    • canvaskit-wasm: 25ms ต่อรอบ รวม 2.47 วินาที
    • canvas: 88ms ต่อรอบ รวม 8.77 วินาที
    • @napi-rs/canvas: 69ms ต่อรอบ รวม 6.87 วินาที
  • การทดสอบ Bézier curves ที่ 20 รอบแสดงความแตกต่างจากการประมวลผลขนานในโหมด async
    • skia-canvas serial: 137ms ต่อรอบ รวม 2.74 วินาที
    • skia-canvas async: 28ms ต่อรอบ รวม 558ms
    • ตัวเปรียบเทียบคือ canvaskit-wasm 790ms, canvas 486ms และ @napi-rs/canvas 230ms
  • การทดสอบ SVG to PNG ที่ 100 รอบ canvaskit-wasm ไม่รองรับ ส่วน skia-canvas ทำได้ 58ms ต่อรอบใน serial และ 11ms ต่อรอบใน async
    • skia-canvas serial: รวม 5.83 วินาที
    • skia-canvas async: รวม 1.08 วินาที
  • ผลการทดสอบ scale·rotate ภาพ 50 รอบ
    • skia-canvas serial: 100ms ต่อรอบ รวม 5.00 วินาที
    • skia-canvas async: 19ms ต่อรอบ รวม 935ms
  • ผลการทดสอบข้อความพื้นฐาน 200 รอบ
    • skia-canvas serial: 21ms ต่อรอบ รวม 4.26 วินาที
    • skia-canvas async: 4ms ต่อรอบ รวม 819ms

1 ความคิดเห็น

 
GN⁺ 2024-12-04
ความคิดเห็นจาก Hacker News
  • https://windowjs.org ก็เป็นแนวคิดที่คล้ายกันมาก
    มันห่อ Skia แล้วเปิดเผยเป็น Canvas API แต่แทนที่จะใช้ Node ก็ฝัง v8 ไว้ ทำให้ทำงานเป็นรันไทม์ขนาดเล็กมาก
    เป็นโปรเจกต์โอเพนซอร์สแรกที่เปิดตัวเมื่อประมาณ 3 ปีก่อน และเคยมีแผนจะเปิดเผย WebGL, audio ฯลฯ เพื่อทำเป็นแพลตฟอร์มเกม JavaScript สำหรับเดสก์ท็อป แต่การพัฒนาหยุดไปเพราะงานและโปรเจกต์อื่น
    ดีใจที่มีโปรเจกต์แบบนี้ออกมา ซึ่งทำให้ใช้ Canvas นอกเบราว์เซอร์ได้

    • ค่อนข้างเจ๋ง แต่ถ้าไม่ได้จำเป็นต้องมีการแยกสภาพแวดล้อมจริง ๆ สภาพแวดล้อมที่มีแค่ v8 ก็น่าเสียดาย
      บางครั้งก็แค่อยากส่งคำขอเครือข่ายธรรมดา ๆ
    • เป็นโปรเจกต์ที่เจ๋ง
      สงสัยว่าถ้าฝังแค่ V8 แทน Node.js จะลด ขนาดไบนารี ได้มากแค่ไหน
      Node.js ก็ใช้ V8 อยู่แล้ว และก็สงสัยเหมือนกันว่าส่วนใหญ่ของขนาดไบนารี Node มาจากรันไทม์ ไม่ใช่ V8 หรือเปล่า
      ลองดูทั้งเว็บไซต์และ GitHub repository แล้ว แต่หาไม่เจอว่าไบนารีของ Window.js ปกติใหญ่กี่ kB หรือ MB
  • แค่สงสัยว่าไลบรารีแบบนี้เอาไปใช้ทำอะไร
    ถ้าเป็นเดสก์ท็อป ก็น่าจะมี ไลบรารีเนทีฟ ที่ดีกว่าสำหรับวาดรูปทรงอยู่ไม่ใช่หรือ

    • ที่ Soundslice เราใช้ ไลบรารีกราฟิกสำหรับเรนเดอร์โน้ตดนตรี แบบกำหนดเองที่สร้างด้วย JavaScript/Canvas ฝั่งฟรอนต์เอนด์
      ฝั่งเซิร์ฟเวอร์ก็ต้องสร้าง vector PDF ด้วย เลยใช้ไลบรารี Node ที่ใช้ HTML Canvas API แล้วสร้าง PDF ได้
      แบบนี้ดีตรงที่ผลลัพธ์จะตรงกับโน้ตดนตรีที่เรนเดอร์ในเว็บเบราว์เซอร์พอดี
      สรุปคือไลบรารีแบบนี้ทำให้ใช้โค้ดซ้ำได้แม้ในสภาพแวดล้อมที่ไม่ใช่เบราว์เซอร์
    • ในงานวาด 2D จริง ๆ แล้วแทบไม่มีอะไรที่ทรงพลังเท่า Skia และยังพอเรียกได้ว่าเป็น “เนทีฟ”
      จุดแข็งใหญ่ของ Skia คือการ ใช้ประโยชน์จากกราฟิกเนทีฟได้อย่างมีประสิทธิภาพ และนี่ก็เป็นเหตุผลที่มันถูกใช้ในเอนจินเบราว์เซอร์ตั้งแต่แรก
      แถมการมี API เป้าหมายเพียงตัวเดียวก็เป็นข้อดีด้วย
    • ข้อดีคือความพกพาข้ามแพลตฟอร์มไม่ใช่เหรอ? สงสัยเหมือนกันว่ายังมีไลบรารีเรนเดอร์ 2D แบบเร่งด้วย GPU อะไรอีกบ้าง
      อีกด้านหนึ่ง ตัวนี้ระดับต่ำกว่า แต่ในอีกด้านหนึ่ง ผมเคยใช้ Puppeteer เหมือนเป็นไลบรารีกราฟิก 2D ด้วย
      ได้ทั้ง Canvas 2D, WebGL, WebGPU และ HTML/CSS ทั้งชุด รวมถึงใช้เอฟเฟกต์ข้อความ ภาพพื้นหลัง และ CSS transform ได้
      โหลดรูปภาพและวิดีโอได้ด้วย และใช้ฟังก์ชัน screenshot เพื่อทำผลลัพธ์ที่ประกอบกันแล้วออกมาเป็นรูปภาพได้
      มันเกินจำเป็นก็จริง แต่สำหรับงานของผมไม่เป็นปัญหา มันใช้งานได้ ข้ามแพลตฟอร์ม และแก้ปัญหาได้
    • ถ้ามีใครยกแพลตฟอร์มที่มี API วาดกราฟิก 2D ดีกว่า Canvas ได้ ผมน่าจะประหลาดใจ
      Skia เร็วกว่า Direct2D เล็กน้อย และเร็วกว่า Cairo มาก
      มันถูกปรับแต่งมาอย่างมาก และมีแบ็กเอนด์เร่งด้วย GPU ด้วย
    • การรันโค้ดเดียวกันบนหลายแพลตฟอร์มได้เป็นโบนัสใหญ่
      Canvas ยังเป็น API วาด 2D ที่พบได้ทั่วไปมากด้วย
      iOS ก็มีเวอร์ชันเนทีฟ Android ก็มีเวอร์ชันเนทีฟ และการทำงานด้านการวาดก็คล้ายกันโดยรวม
      ใช้ประโยชน์จากสิ่งนี้แล้วได้ประโยชน์มาก
  • ใน Skia มี CanvasKit ซึ่งเป็น WASM build ที่รองรับ Node รวมอยู่ด้วย https://www.npmjs.com/package/canvaskit-wasm โมดูลนี้ดูเหมือนเป็น Rust binding
    อยากรู้ข้อดีข้อเสียของทั้งสองแนวทาง

    • แทนที่จะเป็น “Rust binding” น่าจะใกล้เคียงกับ ไบนารี Node ที่คอมไพล์ไว้ล่วงหน้ามากกว่า
      แค่ถูกสร้างจากโค้ดเบสผสม Rust/C++ ภายในเท่านั้น
      ดังนั้นความต่างหลักคือ WASM ต้องมีรันไทม์แยกต่างหาก แต่ไบนารี Node ไม่ต้องมี
  • นี่เป็น wrapper ที่หุ้ม Rust crate อะไรทำนองนั้นหรือเปล่า?
    https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canvas.html

    • เข้าใจว่า Skia Canvas สามารถทำงานได้ในภาษาโปรแกรมแทบทุกภาษา ตราบใดที่มี binding https://www.swig.org/
      ไม่ค่อยเห็นว่ามีอะไรพิเศษเฉพาะกับ Node
    • ใช่ สร้างด้วย napi-rs
  • รอของแบบนี้มานานแล้ว
    ความพยายามก่อนหน้านี้ที่ทำอะไรคล้ายกันต้องติดตั้ง Node-Gyp ซึ่งเป็นฝันร้ายจริง ๆ

  • นี่เป็นมากกว่า API เรนเดอร์ธรรมดา: มีข้อความว่า “สามารถเรนเดอร์ไปยังหน้าต่างผ่าน graphics pipeline เนทีฟของ OS และมีเฟรมเวิร์ก UI event แบบเดียวกับเบราว์เซอร์”
    อาจเพิ่ม wgpu เพื่อรองรับ WebGPU และเพิ่ม ANGLE เพื่อรองรับ WebGL ได้ด้วย

    • Skia เป็นไลบรารีที่ใช้กันแพร่หลาย เท่าที่นึกออกก็มี Chrome, Firefox, LibreOffice ใช้อยู่
      รองรับ rendering target จำนวนมาก รวมถึง WebGPU และ ANGLE
    • ค่อนข้างคล้ายกับสิ่งที่ Qt มีให้อยู่แล้ว
      QPainter จัดการทั้งภาพดิบและหน้าต่าง และยังมีวิดเจ็ตที่ซับซ้อนกว่าพร้อมการรองรับ event ด้วย
      อีกอย่างเป็นฟีเจอร์ที่ผมหามาสักพักแล้ว คือใน Qt มี graphicsview ที่สามารถ เรนเดอร์แบบไม่ขึ้นกับความละเอียด บนระนาบกราฟิกที่ใหญ่กว่าหน้าต่างและมีออบเจ็กต์นับล้าน พร้อมจัดการ event ได้ด้วย
  • ถ้าสนใจการทำ Canvas implementation ที่เข้ากันได้กับ Node ก็มีอยู่หลายตัว
    canvaskit-wasm ของโปรเจกต์ Skia ดูเหมือนจะไม่ได้เร่งด้วย GPU: https://github.com/google/skia/tree/main/modules/canvaskit/npm_build
    @napi-rs/canvas เป็น binding ที่เร็วที่สุด: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#performance
    node-canvas ใช้ Cairo แทน Skia: https://github.com/Automattic/node-canvas

  • ตอนต้องการหน้าต่างกราฟิกง่าย ๆ ใน Advent of Code ตอนนี้ใช้อยู่กับ Deno
    มี window-example ด้วย: https://skia-canvas.org/#rendering-to-a-window
    เพิ่มด้วย deno add npm:skia-canvas แล้วใส่ "nodeModulesDir": "auto" ใน deno.json จากนั้นรัน deno install --allow-scripts ก็พอ

  • เห็นสิ่งนี้แล้วคาดหวังมากจริง ๆ
    น่าจะทำให้เขียนกราฟิกที่ทำงานแบบ standalone ได้และเข้ากันได้กับเว็บสะดวกขึ้นมาก
    ไม่ต้อง build จากซอร์สและ generate code ด้วยภาษาสคริปต์ตัวที่สองเพื่อคอมไพล์ไลบรารีสำหรับภาษาสคริปต์ตัวแรกอีกแล้ว
    น่าสนใจด้วยว่าจะเหมาะแค่ไหนสำหรับการ เรนเดอร์ Canvas code แบบเรียลไทม์ ในที่อย่าง VS Code หรือสำหรับการรันโค้ดที่มีชั้นแปลงเพื่อให้ไปทำงานบนเว็บ Canvas