- 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 và đị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
- Người dùng có thể cấu hình worker pool
- 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
- Đường Bézier hỗ trợ phép toán boolean và nội suy theo điểm
- Ngoài scale·rotate·translate của Canvas cơ bản, tính năng biến đổi còn cung cấp biến đổi phối cảnh 3D
- Với phần tô hình, ngoài Pattern dựa trên bitmap còn có thể dùng Texture dựa trên vector
- Có thể gắn marker tùy chỉnh khi vẽ đường
- Xử lý ảnh hỗ trợ đầy đủ tập toán tử CSS filter
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ằnggetContext("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ủacanvas.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")
- Lưu ảnh mật độ cao bằng
- 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.pngbằng mẫupage-{2}.png
- Lưu PDF nhiều trang thành
- 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 eventdraw - 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
Ý 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
Đôi khi bạn chỉ muốn gửi một request mạng thôi
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
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
Ư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ế
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 đề
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
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
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 không thấy có gì đặc biệt riêng với Node
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
Nó hỗ trợ nhiều đích render, bao gồm WebGPU và ANGLE
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àodeno.json, rồi chạydeno install --allow-scriptsNhì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