Skia Canvas: implementación sin navegador de la API de dibujo HTML Canvas para Node.js
(skia-canvas.org)- Skia Canvas permite usar en Node.js la API de dibujo de HTML Canvas para renderizado en pantalla y fuera de pantalla, y como está basado en Google Skia, produce una salida muy similar a la de
<canvas>de Chrome - Los resultados pueden exportarse como vectores, como PDF y SVG, y como mapas de bits, como JPEG, PNG y WEBP, además de conectarse con archivos, dataURL, Buffer y objetos Sharp
- También cubre áreas fuera del Canvas del navegador, con renderizado de Window GUI, framework de eventos, Canvas multipágina, operaciones de rutas, transformaciones de perspectiva 3D, filtros CSS y control tipográfico avanzado
- El renderizado asíncrono y la E/S de archivos se procesan en hilos nativos de un pool de workers configurable por el usuario, por lo que encaja bien con generación de imágenes del lado del servidor y entornos serverless
- Los benchmarks se miden por separado en modos serial y async; en async se inician varias tareas repetidas al mismo tiempo para comparar el efecto del multithreading
Implementación de Canvas para usar en Node.js
- Skia Canvas es una implementación de la API de dibujo HTML Canvas para Node.js, con soporte tanto para renderizado en pantalla como fuera de pantalla
- Usa el motor gráfico de Google Skia para producir resultados muy similares al elemento
<canvas>de Chrome - Incluye incluso funciones que el Canvas del navegador todavía no ofrece
Formatos de salida y formas de exportación
- La salida de imágenes admite tanto formatos vectoriales como formatos de mapa de bits
- Vectoriales: PDF, SVG
- Mapas de bits: JPEG, PNG, WEBP
- El resultado del Canvas puede enviarse a varios destinos
- Se puede dibujar directamente en una Window GUI interactiva y ofrece un framework de eventos similar al del navegador
Renderizado asíncrono y multipágina
- El renderizado y la E/S de archivos se ejecutan de forma asíncrona en un pool de workers que usa hilos nativos
- El pool de workers puede configurarse por el usuario
- Se pueden crear varias páginas dentro de un mismo Canvas
- Es posible exportarlas como un solo PDF multipágina
- También pueden guardarse como una secuencia de imágenes dividida en varios archivos
Funciones gráficas más allá del Canvas del navegador
- Las rutas Bézier admiten operaciones booleanas e interpolación punto por punto
- Las transformaciones ofrecen, además de escala, rotación y desplazamiento del Canvas básico, transformaciones de perspectiva 3D
- Para rellenar formas se puede usar no solo Pattern basado en mapas de bits, sino también Texture basada en vectores
- Al trazar líneas se pueden adjuntar marker personalizados
- El procesamiento de imágenes admite el conjunto completo de operadores de filtros CSS
Control tipográfico
- El renderizado de texto maneja múltiples líneas y ajuste automático de línea
- Proporciona métricas de texto por línea
- Funciones OpenType como small-caps y ligature pueden usarse con la sintaxis estándar de font-variant
- Es posible controlar proporcionalmente el espaciado entre letras, el espaciado entre palabras y el leading
- Es compatible con variable fonts y mapea de forma transparente los valores de weight
- También se pueden usar fuentes no incluidas en el sistema cargadas desde archivos locales mediante load
Ejemplos de uso
- El ejemplo de creación de archivo de imagen crea un
Canvas, obtiene un contexto 2D congetContext("2d")y luego dibuja un rectángulo usando un gradiente cónico como estilo de trazo- Guarda una imagen de alta densidad con
saveAs("rainbox.png", {density:2}) canvas.pngse usa como forma abreviada decanvas.toBuffer("png")- El resultado de
toDataURL("png")puede insertarse dentro de una cadena HTML - También es posible guardar de forma síncrona en el hilo principal con
saveAsSync("rainbox.pdf")
- Guarda una imagen de alta densidad con
- El ejemplo de secuencia multipágina crea páginas de distintos colores con
canvas.newPage()y luego las guarda- Guarda un PDF multipágina como
all-pages.pdf - Genera varios archivos como
page-01.png,page-02.pngcon el patrónpage-{2}.png
- Guarda un PDF multipágina como
- El ejemplo de renderizado con Window crea
Window(300, 300)y en el eventodrawdibuja un círculo cuyo grosor de línea cambia según el valor del frame - El ejemplo de integración con Sharp.js muestra el flujo para convertir el resultado del Canvas en un objeto Sharp para postprocesarlo, o dibujar en Canvas una imagen creada con Sharp usando
loadImage
Método y resultados de benchmark
- El benchmark prueba Skia Canvas en dos modos
- serial: espera a que termine cada tarea de renderizado antes de pasar a la siguiente repetición
- async: inicia de una vez todas las repeticiones de la prueba y las ejecuta en paralelo con soporte de multithreading
- Los resultados completos pueden revisarse en la página de resultados de canvas-benchmarks
- En la prueba de latencia de arranque, con 100 repeticiones, skia-canvas registró menos de 1 ms por ejecución y 33 ms en total
- canvaskit-wasm: 25 ms por ejecución, 2.47 s en total
- canvas: 88 ms por ejecución, 8.77 s en total
- @napi-rs/canvas: 69 ms por ejecución, 6.87 s en total
- En la prueba de curvas Bézier, con 20 repeticiones, se hizo visible la diferencia del procesamiento en paralelo en modo async
- skia-canvas serial: 137 ms por ejecución, 2.74 s en total
- skia-canvas async: 28 ms por ejecución, 558 ms en total
- Los comparados fueron canvaskit-wasm con 790 ms, canvas con 486 ms y @napi-rs/canvas con 230 ms
- En la prueba de SVG a PNG, con 100 repeticiones, canvaskit-wasm no era compatible, y skia-canvas registró 58 ms por ejecución en serial y 11 ms por ejecución en async
- skia-canvas serial: 5.83 s en total
- skia-canvas async: 1.08 s en total
- Resultados de 50 repeticiones en la prueba de escalado y rotación de imagen
- skia-canvas serial: 100 ms por ejecución, 5.00 s en total
- skia-canvas async: 19 ms por ejecución, 935 ms en total
- Resultados de 200 repeticiones en la prueba básica de texto
- skia-canvas serial: 21 ms por ejecución, 4.26 s en total
- skia-canvas async: 4 ms por ejecución, 819 ms en total
1 comentarios
Opiniones en Hacker News
https://windowjs.org también es un concepto muy parecido
Envuelve Skia y lo expone como Canvas API, pero en vez de Node incorpora v8 y funciona como un runtime muy pequeño
Fue mi primer proyecto open source publicado hace unos 3 años, y también tenía planes de exponer WebGL, audio, etc. para convertirlo en una plataforma de juegos JavaScript de escritorio, pero el desarrollo quedó detenido por otros trabajos y proyectos
Me alegra ver que aparezcan proyectos así que permiten usar Canvas fuera del navegador
A veces uno simplemente quiere hacer una petición de red
Me da curiosidad cuánto se reduce el tamaño del binario al incorporar solo V8 en lugar de Node.js
Node.js también usa V8, y también me pregunto si la mayor parte del tamaño del binario de Node no viene de V8 sino del runtime
Revisé el sitio web y el repositorio de GitHub, pero no encontré cuántos kB o MB suele pesar el binario de Window.js
Por curiosidad, ¿para qué se usan este tipo de librerías?
Si es para escritorio, pensaría que hay librerías nativas mejores para dibujar formas
Como también necesitamos generar PDFs vectoriales del lado del servidor, usamos una librería de Node que permite usar la HTML Canvas API y crear PDFs
Lo bueno de esto es que el resultado queda exactamente igual que la partitura renderizada en el navegador web
En resumen, estas librerías permiten reutilizar código incluso en entornos que no son el navegador
La gran ventaja de Skia está en aprovechar eficazmente los gráficos nativos, y por eso se usa en motores de navegador desde el principio
Además, que el objetivo sea una sola API también es una ventaja
Por un lado esto es de más bajo nivel, pero por otro también he usado Puppeteer como si fuera una librería de gráficos 2D
Obtienes Canvas 2D, WebGL, WebGPU y todo HTML/CSS, y puedes usar efectos de texto, imágenes de fondo y transformaciones CSS
También puedes cargar imágenes y videos, y con la función de screenshots convertir el resultado compuesto en una imagen
Es exagerado, pero para mi caso no importaba; funciona, es multiplataforma y resuelve el problema
Skia es un poco más rápida que Direct2D y mucho más rápida que Cairo
Está muy optimizada y también tiene backends con aceleración por GPU
Canvas también es una API de dibujo 2D muy común
iOS tiene una versión nativa y Android también tiene una versión nativa, y las operaciones de dibujo son en general parecidas
Aprovechar eso aporta mucho
Skia incluye CanvasKit, una build WASM compatible con Node: https://www.npmjs.com/package/canvaskit-wasm, y este módulo parece ser un binding de Rust
Me dan curiosidad las ventajas y desventajas de ambos enfoques
Internamente simplemente se genera a partir de una base de código mixta Rust/C++
Así que la diferencia clave es que WASM necesita un runtime aparte, mientras que el binario de Node no
¿Esto es como un wrapper alrededor de un crate de Rust?
https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canvas.html
No veo mucho que sea especial de Node
Llevaba mucho tiempo esperando algo así
Los intentos anteriores de hacer algo parecido requerían instalar Node-Gyp, y eso era una verdadera pesadilla
Es más que una simple API de renderizado: dice que “puede renderizar en una ventana mediante el pipeline gráfico nativo del OS y ofrece un framework de eventos de UI similar al de un navegador”
También podrían agregar wgpu para soporte de WebGPU y ANGLE para soporte de WebGL
Soporta muchos destinos de renderizado, incluidos WebGPU y ANGLE
QPainter maneja imágenes sin procesar y ventanas, y también hay widgets más sofisticados con soporte de eventos
Además, una función que estuve buscando durante un tiempo: Qt tiene graphicsview, que puede renderizar de forma independiente de la resolución millones de objetos en un plano gráfico más grande que la ventana y también manejar eventos
Si te interesan las implementaciones de Canvas compatibles con Node, hay varias
canvaskit-wasm del proyecto Skia parece no tener aceleración por GPU: https://github.com/google/skia/tree/main/modules/canvaskit/npm_build
@napi-rs/canvas es el binding más rápido: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#performance
node-canvas usa Cairo en lugar de Skia: https://github.com/Automattic/node-canvas
Actualmente lo uso con Deno cuando necesito una ventana gráfica simple para Advent of Code
También hay un ejemplo con ventana: https://skia-canvas.org/#rendering-to-a-window
Basta con agregarlo con
deno add npm:skia-canvas, poner"nodeModulesDir": "auto"endeno.jsony luego ejecutardeno install --allow-scriptsVer esto me entusiasma mucho
Parece que permitirá escribir gráficos independientes y compatibles con la web de una forma mucho más cómoda
Ya no hará falta compilar desde el código fuente ni generar código con un segundo lenguaje de scripting para compilar una librería destinada al primer lenguaje de scripting
También será interesante ver qué tan bien encaja para renderizar en tiempo real código de Canvas en lugares como VS Code, o para ejecutar código que tenga una capa de traducción pensada para correr sobre Web Canvas