Skia Canvas: implementação sem navegador da API de desenho HTML Canvas para Node.js
(skia-canvas.org)- Skia Canvas permite usar a API de desenho HTML Canvas no Node.js para renderização onscreen e offscreen; por ser baseada no Google Skia, produz uma saída muito parecida com a do
<canvas>do Chrome - Os resultados podem ser exportados como vetores, como PDF·SVG, e bitmaps, como JPEG·PNG·WEBP, e podem ser conectados a arquivos, dataURL, Buffer e objetos Sharp
- Também lida com áreas fora do Canvas do navegador e oferece renderização em GUI Window, framework de eventos, Canvas multipágina, operações de caminho, transformação de perspectiva 3D, CSS filter e controle tipográfico avançado
- A renderização assíncrona e o I/O de arquivos são processados em threads nativas de um pool de workers configurado pelo usuário, sendo adequados para geração de imagens server-side e ambientes serverless
- Os benchmarks medem separadamente os modos serial e async; no async, várias iterações são iniciadas simultaneamente para comparar o efeito de multithreading
Implementação de Canvas para uso em Node.js
- Skia Canvas é uma implementação da API de desenho HTML Canvas para Node.js, com suporte tanto a renderização onscreen quanto offscreen
- Usa o motor gráfico Google Skia para produzir resultados muito semelhantes aos do elemento
<canvas>do Chrome - Inclui até recursos que o Canvas do navegador ainda não oferece
Formatos de saída e formas de exportação
- A saída de imagem oferece suporte tanto a formatos vetoriais quanto a formatos bitmap
- Vetores: PDF, SVG
- Bitmap: JPEG, PNG, WEBP
- O resultado do Canvas pode ser enviado para vários destinos
- É possível desenhar diretamente em uma Window GUI interativa, com um framework de eventos semelhante ao do navegador
Renderização assíncrona e multipágina
- A renderização e o I/O de arquivos são executados de forma assíncrona em um pool de workers que usa threads nativas
- O pool de workers pode ser configurado pelo usuário
- É possível criar várias páginas em um único Canvas
- Pode ser exportado como um único PDF multipágina
- Também pode ser salvo como uma sequência de imagens dividida em vários arquivos
Recursos gráficos além do Canvas do navegador
- Caminhos Bézier oferecem suporte a operações booleanas e interpolação ponto a ponto
- Além de escala, rotação e translação do Canvas básico, os recursos de transformação oferecem transformação de perspectiva 3D
- Para preenchimento de formas, além de Pattern baseado em bitmap, é possível usar Texture baseada em vetor
- É possível anexar markers personalizados ao desenho de linhas
- O processamento de imagens oferece suporte ao conjunto completo de operadores de CSS filter
Controle tipográfico
- A renderização de texto lida com múltiplas linhas e quebra automática de linha
- Fornece métricas de texto por linha
- Recursos OpenType como small-caps e ligature podem ser usados com a sintaxe padrão de font-variant
- É possível controlar proporcionalmente espaçamento entre letras, espaçamento entre palavras e leading
- Oferece suporte a variable fonts e mapeia valores de weight de forma transparente
- Também é possível usar fontes não sistêmicas carregadas de arquivos locais
Exemplos de uso
- O exemplo de geração de arquivo de imagem cria um
Canvas, obtém um contexto 2D comgetContext("2d")e desenha um retângulo usando um gradiente cônico como estilo de stroke- Salva uma imagem de alta densidade com
saveAs("rainbox.png", {density:2}) canvas.pngé usado como abreviação decanvas.toBuffer("png")- O resultado de
toDataURL("png")pode ser inserido em uma string HTML - Também é possível salvar de forma síncrona na thread principal com
saveAsSync("rainbox.pdf")
- Salva uma imagem de alta densidade com
- O exemplo de sequência multipágina cria páginas de cores diferentes com
canvas.newPage()e as salva- Salva um PDF multipágina como
all-pages.pdf - Cria vários arquivos, como
page-01.pngepage-02.png, com o padrãopage-{2}.png
- Salva um PDF multipágina como
- O exemplo de renderização em Window cria
Window(300, 300)e desenha, no eventodraw, um círculo cuja espessura da linha muda conforme o valor do frame - O exemplo de integração com Sharp.js mostra o fluxo de converter o resultado do Canvas em um objeto Sharp para pós-processamento, ou desenhar no Canvas com
loadImageuma imagem criada pelo Sharp
Método e resultados dos benchmarks
- Os benchmarks testam o Skia Canvas em dois modos
- serial: espera cada tarefa de renderização antes de avançar para a próxima iteração
- async: inicia todas as iterações do teste de uma vez e as executa em paralelo com suporte a multithreading
- Os resultados completos podem ser vistos na página de resultados do canvas-benchmarks
- No teste de latência de inicialização, com 100 iterações, o skia-canvas registrou menos de 1 ms por iteração e 33 ms no total
- canvaskit-wasm: 25 ms por iteração, 2,47 s no total
- canvas: 88 ms por iteração, 8,77 s no total
- @napi-rs/canvas: 69 ms por iteração, 6,87 s no total
- O teste de curvas Bézier, com 20 iterações, evidencia a diferença do processamento paralelo no modo async
- skia-canvas serial: 137 ms por iteração, 2,74 s no total
- skia-canvas async: 28 ms por iteração, 558 ms no total
- Os comparativos foram canvaskit-wasm com 790 ms, canvas com 486 ms e @napi-rs/canvas com 230 ms
- No teste SVG to PNG, com 100 iterações, o canvaskit-wasm não tinha suporte, e o skia-canvas registrou 58 ms por iteração no serial e 11 ms por iteração no async
- skia-canvas serial: 5,83 s no total
- skia-canvas async: 1,08 s no total
- Resultados do teste de escala e rotação de imagens, com 50 iterações
- skia-canvas serial: 100 ms por iteração, 5,00 s no total
- skia-canvas async: 19 ms por iteração, 935 ms no total
- Resultados do teste de texto básico, com 200 iterações
- skia-canvas serial: 21 ms por iteração, 4,26 s no total
- skia-canvas async: 4 ms por iteração, 819 ms no total
1 comentários
Opiniões no Hacker News
https://windowjs.org também é um conceito muito parecido
Ele envolve o Skia e o expõe como Canvas API, mas, em vez de Node, incorpora o v8 e funciona como um runtime bem pequeno
Foi o primeiro projeto open source que publiquei, cerca de 3 anos atrás, e havia planos de expor WebGL, áudio etc. para transformá-lo em uma plataforma de jogos em JavaScript para desktop, mas o desenvolvimento foi interrompido por causa de outros trabalhos e projetos
Fico feliz em ver surgir um projeto assim, que permite usar Canvas também fora do navegador
Às vezes você só quer fazer uma requisição de rede
Fico curioso para saber quanto o tamanho do binário diminui ao incorporar apenas o V8 em vez do Node.js
O Node.js também usa V8, então também fico curioso se a maior parte do tamanho do binário do Node vem do runtime, e não do V8
Dei uma olhada no site e no repositório do GitHub, mas não consegui encontrar quantos kB ou MB costuma ter o binário do Window.js
Por curiosidade, onde esse tipo de biblioteca é usado?
No desktop, imagino que existam bibliotecas nativas melhores para desenhar formas
Como também precisamos gerar PDFs vetoriais no lado do servidor, usamos uma biblioteca Node que permite usar a HTML Canvas API e criar PDFs
Isso é bom porque o resultado fica exatamente igual às partituras renderizadas no navegador
Em resumo, esse tipo de biblioteca permite reutilizar código em ambientes que não são navegadores
A grande vantagem do Skia está em aproveitar gráficos nativos de forma eficaz, e é justamente por isso que ele é usado em engines de navegador
Além disso, ter uma única API-alvo também é uma vantagem
Por um lado, isto é de mais baixo nível; por outro, já usei o Puppeteer como se fosse uma biblioteca gráfica 2D
Você obtém Canvas 2D, WebGL, WebGPU e HTML/CSS completos, além de poder usar efeitos de texto, imagens de fundo e transformações CSS
Também é possível carregar imagens e vídeos, e usar a função de screenshot para transformar o resultado combinado em uma imagem
É exagerado, mas para o meu caso de uso não importava; funcionava, era multiplataforma e resolvia o problema
Skia é um pouco mais rápido que Direct2D e muito mais rápido que Cairo
É altamente otimizado e também tem backends com aceleração por GPU
Canvas também é uma API de desenho 2D muito comum
O iOS tem uma versão nativa e o Android também tem uma versão nativa, e as operações de desenho são, em geral, parecidas
Aproveitar isso traz muitos benefícios
O Skia inclui o CanvasKit, uma build WASM com suporte a Node: https://www.npmjs.com/package/canvaskit-wasm, e este módulo parece ser um binding em Rust
Fico curioso sobre os prós e contras das duas abordagens
Ele apenas é gerado internamente a partir de uma base de código mista em Rust/C++
Então a diferença principal é que o WASM precisa de um runtime separado, enquanto o binário Node não precisa
Isto é algo como um wrapper em torno de um crate Rust?
https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canvas.html
Não vejo nada muito especial no Node
Eu estava esperando por algo assim há muito tempo
Tentativas anteriores de fazer algo parecido exigiam instalação via Node-Gyp, o que era um verdadeiro pesadelo
É mais do que uma simples API de renderização: há a afirmação de que “pode renderizar em uma janela usando o pipeline gráfico nativo do SO e oferece um framework de eventos de UI semelhante ao do navegador”
Também seria possível adicionar wgpu para suporte a WebGPU e ANGLE para suporte a WebGL
Ela oferece suporte a muitos alvos de renderização, incluindo WebGPU e ANGLE
QPainter lida com imagens brutas e janelas, e também há widgets mais sofisticados com suporte a eventos
Além disso, existe uma funcionalidade que eu vinha procurando havia um tempo: no Qt, há o graphicsview, que consegue renderizar independentemente da resolução milhões de objetos em um plano gráfico maior que a janela e ainda tratar eventos
Se você tem interesse em implementações de Canvas compatíveis com Node, há algumas opções
O canvaskit-wasm do projeto Skia não parece ter aceleração por GPU: https://github.com/google/skia/tree/main/modules/canvaskit/npm_build
@napi-rs/canvas é o binding mais rápido: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#performance
node-canvas usa Cairo em vez de Skia: https://github.com/Automattic/node-canvas
Estou usando isto atualmente com Deno quando preciso de uma janela gráfica simples no Advent of Code
Também há um window-example: https://skia-canvas.org/#rendering-to-a-window
Basta adicionar com
deno add npm:skia-canvas, colocar"nodeModulesDir": "auto"nodeno.jsone então executardeno install --allow-scriptsVer isto me deixa realmente animado
Parece que vai tornar muito mais fácil escrever gráficos compatíveis com a web e que também podem rodar de forma independente
Também elimina a necessidade de compilar a partir do código-fonte e gerar código em uma segunda linguagem de script para compilar uma biblioteca para a primeira linguagem de script
Também é interessante pensar em quão bem isso se encaixaria para renderização em tempo real de código Canvas em lugares como o VS Code, ou para executar código que tenha uma camada de conversão para rodar no Canvas da web