4 điểm bởi GN⁺ 2024-12-04 | 1 bình luận | Chia sẻ qua WhatsApp
  • Skia Canvas cho phép dùng API vẽ HTML Canvas trong Node.js cho cả render on-screen và off-screen, đồng thời dựa trên Google Skia nên cho đầu ra rất giống với <canvas> của Chrome
  • Kết quả có thể xuất ra định dạng vector như PDF·SVG và bitmap như JPEG·PNG·WEBP, đồng thời có thể kết nối dưới dạng file, dataURL, Buffer hoặc đối tượng Sharp
  • Nó cũng xử lý cả những phần vượt ra ngoài phạm vi Canvas trên trình duyệt, cung cấp render GUI Window, framework sự kiện, Canvas nhiều trang, phép toán đường dẫn, biến đổi phối cảnh 3D, CSS filter và khả năng kiểm soát typography nâng cao
  • Render bất đồng bộ và file I/O được xử lý trên các native thread của worker pool do người dùng cấu hình, phù hợp cho việc tạo ảnh phía máy chủ và môi trường serverless
  • Benchmark đo riêng chế độ serial và async; ở async, nhiều tác vụ lặp được khởi chạy đồng thời để so sánh hiệu quả của đa luồng

Hiện thực Canvas dùng trong Node.js

  • Skia Canvas là một hiện thực HTML Canvas drawing API cho Node.js, hỗ trợ cả render on-screen lẫn off-screen
  • Dùng engine đồ họa Google Skia nên tạo ra kết quả rất tương tự phần tử <canvas> của Chrome
  • Bao gồm cả những tính năng mà Canvas trên trình duyệt hiện vẫn chưa cung cấp

Định dạng đầu ra và cách xuất

  • Xuất ảnh hỗ trợ cả định dạng vectorđịnh dạng bitmap
    • Vector: PDF, SVG
    • Bitmap: JPEG, PNG, WEBP
  • Có thể gửi kết quả Canvas tới nhiều đích khác nhau
  • Có thể vẽ trực tiếp vào GUI Window tương tác, đồng thời cung cấp framework event tương tự trình duyệt

Render bất đồng bộ và nhiều trang

  • Render và file I/O được chạy bất đồng bộ trong worker pool sử dụng native thread
  • Có thể tạo nhiều trang trong một Canvas
    • Có thể xuất thành một file PDF nhiều trang duy nhất
    • Cũng có thể lưu dưới dạng chuỗi ảnh tách thành nhiều file

Tính năng đồ họa vượt ngoài Canvas trên trình duyệt

Kiểm soát typography

  • Render văn bản xử lý được nhiều dòng và tự động xuống dòng
  • Cung cấp text metrics theo từng dòng
  • Các tính năng OpenType như small-caps và ligature có thể dùng bằng cú pháp chuẩn font-variant
  • Có thể điều khiển theo tỷ lệ khoảng cách chữ, khoảng cách từ và leading
  • Hỗ trợ variable fonts và ánh xạ giá trị weight một cách minh bạch
  • Cũng có thể dùng font không thuộc hệ thống được nạp từ file cục bộ

Ví dụ sử dụng

  • Ví dụ tạo file ảnh tạo Canvas, lấy context 2D bằng getContext("2d"), rồi dùng gradient hình nón làm kiểu stroke để vẽ hình chữ nhật
    • Lưu ảnh mật độ cao bằng saveAs("rainbox.png", {density:2})
    • canvas.png được dùng như dạng viết tắt của canvas.toBuffer("png")
    • Có thể chèn kết quả toDataURL("png") vào chuỗi HTML
    • Cũng có thể lưu đồng bộ trên main thread bằng saveAsSync("rainbox.pdf")
  • Ví dụ chuỗi nhiều trang dùng canvas.newPage() để tạo các trang có màu khác nhau rồi lưu
    • Lưu PDF nhiều trang thành all-pages.pdf
    • Tạo nhiều file như page-01.png, page-02.png bằng mẫu page-{2}.png
  • Ví dụ render Window tạo Window(300, 300) rồi vẽ một vòng tròn có độ dày nét thay đổi theo giá trị frame trong event draw
  • Ví dụ tích hợp Sharp.js cho thấy luồng chuyển kết quả Canvas thành đối tượng Sharp để hậu xử lý, hoặc vẽ ảnh tạo bằng Sharp lên Canvas qua loadImage

Cách benchmark và kết quả

  • Benchmark kiểm thử Skia Canvas ở hai chế độ
    • serial: chờ từng tác vụ render xong rồi mới chuyển sang vòng lặp tiếp theo
    • async: khởi chạy toàn bộ vòng lặp kiểm thử cùng lúc và chạy song song nhờ hỗ trợ đa luồng
  • Có thể xem toàn bộ kết quả tại trang kết quả canvas-benchmarks
  • Ở bài test độ trễ khởi động với 100 lần lặp, skia-canvas ghi nhận dưới 1ms mỗi lần, tổng cộng 33ms
    • canvaskit-wasm: 25ms mỗi lần, tổng 2.47 giây
    • canvas: 88ms mỗi lần, tổng 8.77 giây
    • @napi-rs/canvas: 69ms mỗi lần, tổng 6.87 giây
  • Bài test Bézier curves với 20 lần lặp cho thấy khác biệt của xử lý song song ở chế độ async
    • skia-canvas serial: 137ms mỗi lần, tổng 2.74 giây
    • skia-canvas async: 28ms mỗi lần, tổng 558ms
    • Các đối tượng so sánh là canvaskit-wasm 790ms, canvas 486ms, @napi-rs/canvas 230ms
  • Ở bài test SVG to PNG với 100 lần lặp, canvaskit-wasm không hỗ trợ, còn skia-canvas ghi nhận 58ms mỗi lần ở serial và 11ms mỗi lần ở async
    • skia-canvas serial: tổng 5.83 giây
    • skia-canvas async: tổng 1.08 giây
  • Kết quả bài test scale·rotate ảnh với 50 lần lặp
    • skia-canvas serial: 100ms mỗi lần, tổng 5.00 giây
    • skia-canvas async: 19ms mỗi lần, tổng 935ms
  • Kết quả bài test văn bản cơ bản với 200 lần lặp
    • skia-canvas serial: 21ms mỗi lần, tổng 4.26 giây
    • skia-canvas async: 4ms mỗi lần, tổng 819ms

1 bình luận

 
GN⁺ 2024-12-04
Ý kiến trên Hacker News
  • https://windowjs.org cũng là một ý tưởng rất tương tự
    Nó bọc Skia và phơi ra dưới dạng Canvas API, nhưng thay vì Node, nó nhúng v8 để chạy như một runtime rất nhỏ
    Đây là dự án mã nguồn mở đầu tiên tôi công bố khoảng 3 năm trước; tôi cũng từng có kế hoạch phơi ra WebGL, audio, v.v. để biến nó thành một nền tảng game JavaScript cho desktop, nhưng việc phát triển đã dừng lại vì các công việc và dự án khác
    Thật vui khi thấy những dự án như thế này, giúp dùng Canvas được cả bên ngoài trình duyệt

    • Khá hay, nhưng nếu không thật sự cần cô lập thì một môi trường chỉ có v8 hơi đáng tiếc
      Đôi khi bạn chỉ muốn gửi một request mạng thôi
    • Dự án rất tuyệt
      Tôi tò mò nếu chỉ nhúng V8 thay vì Node.js thì kích thước binary giảm được bao nhiêu
      Node.js cũng dùng V8, nên tôi cũng tò mò liệu phần lớn kích thước binary của Node không đến từ V8 mà từ runtime hay không
      Tôi đã xem qua website và kho GitHub nhưng không tìm thấy binary của Window.js thường cỡ bao nhiêu kB hay MB
  • Tôi tò mò không biết những thư viện kiểu này được dùng vào đâu
    Nếu là desktop thì chẳng phải có những thư viện native tốt hơn để vẽ hình sao

    • Ở Soundslice, chúng tôi dùng một thư viện đồ họa render bản nhạc tùy biến được viết bằng JavaScript/Canvas cho frontend
      Phía server cũng cần tạo PDF vector, nên chúng tôi dùng một thư viện Node có thể dùng HTML Canvas API và tạo PDF
      Cách này hay ở chỗ kết quả khớp chính xác với bản nhạc được render trong trình duyệt web
      Tóm lại, các thư viện như thế này cho phép tái sử dụng code trong môi trường không phải trình duyệt
    • Trong mảng vẽ 2D, thực ra không có nhiều thứ mạnh như Skia mà thường được gọi là “native”
      Ưu điểm lớn của Skia là tận dụng hiệu quả đồ họa native, và đó cũng là lý do ban đầu nó được dùng trong các engine trình duyệt
      Hơn nữa, việc chỉ nhắm tới một API cũng là một lợi thế
    • Chẳng phải tính di động là điểm mạnh sao? Tôi tò mò còn có thư viện render 2D tăng tốc GPU nào khác
      Một mặt thì cái này cấp thấp hơn, nhưng mặt khác tôi từng dùng Puppeteer như một thư viện đồ họa 2D
      Bạn có Canvas 2D, WebGL, WebGPU, toàn bộ HTML/CSS, và có thể dùng hiệu ứng văn bản, ảnh nền, biến đổi CSS
      Cũng có thể tải ảnh và video, rồi dùng tính năng chụp màn hình để xuất kết quả đã ghép thành ảnh
      Hơi quá mức, nhưng với nhu cầu của tôi thì không sao; nó chạy được, đa nền tảng và giải quyết được vấn đề
    • Tôi sẽ khá ngạc nhiên nếu có ai nêu được một nền tảng có API vẽ đồ họa 2D tốt hơn Canvas
      Skia nhanh hơn Direct2D một chút và nhanh hơn Cairo rất nhiều
      Nó được tối ưu hóa cao và cũng có backend tăng tốc GPU
    • Việc có thể chạy cùng một code trên nhiều nền tảng là một điểm cộng lớn
      Canvas cũng là một API vẽ 2D rất phổ biến
      iOS có phiên bản native, Android cũng có phiên bản native, và các thao tác vẽ nhìn chung tương tự nhau
      Tận dụng được điều đó mang lại lợi ích lớn
  • Skia có CanvasKit, một bản build WASM hỗ trợ Node: https://www.npmjs.com/package/canvaskit-wasm, còn module này có vẻ là binding Rust
    Tôi tò mò ưu và nhược điểm của hai cách tiếp cận này

    • Nói đúng hơn thì nó gần với binary Node được biên dịch sẵn hơn là “binding Rust”
      Chỉ là nó được tạo ra từ một codebase nội bộ pha trộn Rust/C++
      Vì vậy khác biệt cốt lõi là WASM cần runtime riêng, còn binary Node thì không
  • Đây có phải là wrapper bọc một Rust crate không?
    https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canvas.html

    • Tôi hiểu Skia Canvas có thể hoạt động với gần như mọi ngôn ngữ lập trình miễn là có binding https://www.swig.org/
      Tôi không thấy có gì đặc biệt riêng với Node
    • Đúng vậy, nó được tạo bằng napi-rs
  • Tôi đã chờ thứ như thế này từ lâu
    Những nỗ lực trước đây làm việc tương tự cần cài Node-Gyp, đúng là ác mộng

  • Cái này không chỉ là một API render đơn giản: có đoạn nói rằng “nó có thể render vào cửa sổ thông qua pipeline đồ họa native của OS và cung cấp framework sự kiện UI giống trình duyệt”
    Có lẽ cũng có thể thêm wgpu để hỗ trợ WebGPU và ANGLE để hỗ trợ WebGL

    • Skia là một thư viện được dùng rộng rãi; chỉ những cái tôi nhớ ra đã có Chrome, Firefox, LibreOffice sử dụng
      Nó hỗ trợ nhiều đích render, bao gồm WebGPU và ANGLE
    • Khá giống những gì Qt đã cung cấp
      QPainter xử lý ảnh thô và cửa sổ, đồng thời cũng có các widget tinh vi hơn có hỗ trợ sự kiện
      Ngoài ra, một tính năng tôi đã tìm kiếm một thời gian là graphicsview của Qt, có thể render độc lập với độ phân giải hàng triệu đối tượng trên một mặt phẳng đồ họa lớn hơn cửa sổ và xử lý cả sự kiện
  • Nếu bạn quan tâm đến các triển khai Canvas tương thích Node thì có vài lựa chọn
    canvaskit-wasm của dự án Skia có vẻ không tăng tốc GPU: https://github.com/google/skia/tree/main/modules/canvaskit/npm_build
    @napi-rs/canvas là binding nhanh nhất: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#performance
    node-canvas dùng Cairo thay vì Skia: https://github.com/Automattic/node-canvas

  • Hiện tôi đang dùng nó cùng Deno khi cần một cửa sổ đồ họa đơn giản cho Advent of Code
    Cũng có ví dụ về window: https://skia-canvas.org/#rendering-to-a-window
    Chỉ cần thêm bằng deno add npm:skia-canvas, đưa "nodeModulesDir": "auto" vào deno.json, rồi chạy deno install --allow-scripts

  • Nhìn cái này khiến tôi rất hào hứng
    Có vẻ nó sẽ giúp viết đồ họa độc lập nhưng vẫn tương thích web dễ dàng hơn rất nhiều
    Cũng không còn phải build từ source và sinh code bằng một ngôn ngữ script thứ hai chỉ để biên dịch thư viện cho ngôn ngữ script thứ nhất
    Tôi cũng tò mò nó sẽ phù hợp đến đâu cho việc render trực tiếp code Canvas trong những nơi như VS Code, hoặc chạy code có lớp chuyển đổi để chạy trên web Canvas