- Skia Canvas ermöglicht die Nutzung der HTML-Canvas-Zeichen-API in Node.js für Onscreen- und Offscreen-Rendering und liefert auf Basis von Google Skia Ergebnisse, die dem Chrome-
<canvas>sehr ähnlich sind - Ergebnisse lassen sich als Vektorformate wie PDF und SVG sowie als Bitmaps wie JPEG, PNG und WEBP exportieren und können mit Dateien, dataURLs, Buffern oder Sharp-Objekten verbunden werden
- Es deckt auch Bereiche außerhalb des Browser-Canvas ab und bietet GUI-Window-Rendering, ein Event-Framework, mehrseitige Canvas, Pfadoperationen, 3D-Perspektivtransformationen, CSS filter und erweiterte Typografie-Steuerung
- Asynchrones Rendering und Datei-I/O werden in nativen Threads eines vom Nutzer konfigurierbaren Worker-Pools verarbeitet und eignen sich damit für serverseitige Bilderzeugung und serverless Umgebungen
- Benchmarks messen getrennt nach serial- und async-Modus; im async-Modus werden mehrere wiederholte Aufgaben gleichzeitig gestartet, um den Effekt von Multithreading zu vergleichen
Canvas-Implementierung für Node.js
- Skia Canvas ist eine Implementierung der HTML-Canvas-Zeichen-API für Node.js und unterstützt sowohl Onscreen- als auch Offscreen-Rendering
- Es nutzt die Google-Skia-Grafik-Engine und erzeugt Ergebnisse, die dem Chrome-
<canvas>-Element sehr ähnlich sind - Enthalten sind auch Funktionen, die der Browser-Canvas bisher noch nicht bietet
Ausgabeformate und Exportmethoden
- Die Bildausgabe unterstützt sowohl Vektorformate als auch Bitmap-Formate
- Vektor: PDF, SVG
- Bitmap: JPEG, PNG, WEBP
- Canvas-Ergebnisse können an mehrere Ziele ausgegeben werden
- Es kann direkt in ein interaktives GUI-Window gezeichnet werden, außerdem wird ein browserähnliches Event-Framework bereitgestellt
Asynchrones Rendering und mehrere Seiten
- Rendering und Datei-I/O werden asynchron in einem Worker-Pool ausgeführt, der native Threads verwendet
- Der Worker-Pool ist benutzerkonfigurierbar
- In einem einzelnen Canvas können mehrere Seiten erstellt werden
- Ausgabe als einzelne mehrseitige PDF-Datei ist möglich
- Auch das Speichern als auf mehrere Dateien aufgeteilte Bildsequenz ist möglich
Grafikfunktionen über den Browser-Canvas hinaus
- Bézier-Pfade unterstützen boolesche Operationen und punktweise Interpolation
- Die Transformationsfunktionen bieten zusätzlich zu Skalierung, Rotation und Verschiebung des Standard-Canvas auch 3D-Perspektiv-Transformationen
- Für das Füllen von Formen lassen sich neben bitmapbasierten Pattern auch vektorbasierte Texture verwenden
- Beim Zeichnen von Linien können benutzerdefinierte marker hinzugefügt werden
- Die Bildverarbeitung unterstützt den vollständigen Satz von CSS filter-Operatoren
Typografie-Steuerung
- Textrendering verarbeitet mehrere Zeilen und automatischen Zeilenumbruch
- Es stellt zeilenbezogene Textmetriken bereit
- OpenType-Funktionen wie small-caps und ligature können über die standardisierte font-variant-Syntax verwendet werden
- Zeichenabstand, Wortabstand und leading lassen sich proportional steuern
- variable fonts werden unterstützt, weight-Werte werden transparent abgebildet
- Auch aus lokalen Dateien geladene Nicht-Systemschriftarten können verwendet werden
Nutzungsbeispiele
- Das Beispiel zur Erzeugung einer Bilddatei erstellt ein
Canvas, holt mitgetContext("2d")einen 2D-Kontext und zeichnet ein Rechteck mit einem konischen Gradienten als stroke-Stil- Mit
saveAs("rainbox.png", {density:2})wird ein hochauflösendes Bild gespeichert canvas.pngwird als Kurzform fürcanvas.toBuffer("png")verwendet- Das Ergebnis von
toDataURL("png")kann in einen HTML-String eingefügt werden - Mit
saveAsSync("rainbox.pdf")ist auch synchrones Speichern im Main Thread möglich
- Mit
- Das Beispiel für eine mehrseitige Sequenz erstellt mit
canvas.newPage()Seiten mit unterschiedlichen Farben und speichert sie- Eine mehrseitige PDF wird als
all-pages.pdfgespeichert - Mit dem Muster
page-{2}.pngwerden mehrere Dateien wiepage-01.pngundpage-02.pngerzeugt
- Eine mehrseitige PDF wird als
- Das Window-Rendering-Beispiel erstellt
Window(300, 300)und zeichnet imdraw-Event einen Kreis, dessen Linienstärke sich je nach Frame-Wert ändert - Das Integrationsbeispiel für Sharp.js zeigt den Ablauf, Canvas-Ergebnisse in Sharp-Objekte umzuwandeln und nachzubearbeiten oder mit Sharp erzeugte Bilder per
loadImagein Canvas zu zeichnen
Benchmark-Methode und Ergebnisse
- Die Benchmarks testen Skia Canvas in zwei Modi
- serial: Nach jedem Rendering-Job wird gewartet, bevor die nächste Iteration beginnt
- async: Alle Testiterationen werden auf einmal gestartet und dank Multithreading-Unterstützung parallel ausgeführt
- Die vollständigen Ergebnisse sind auf der Ergebnisseite der canvas-benchmarks zu finden
- Beim Test der Startlatenz über 100 Iterationen erreichte skia-canvas weniger als 1 ms pro Durchlauf und insgesamt 33 ms
- canvaskit-wasm: 25 ms pro Durchlauf, insgesamt 2,47 s
- canvas: 88 ms pro Durchlauf, insgesamt 8,77 s
- @napi-rs/canvas: 69 ms pro Durchlauf, insgesamt 6,87 s
- Im Test mit Bézier curves über 20 Iterationen wird der Unterschied durch parallele Verarbeitung im async-Modus sichtbar
- skia-canvas serial: 137 ms pro Durchlauf, insgesamt 2,74 s
- skia-canvas async: 28 ms pro Durchlauf, insgesamt 558 ms
- Die Vergleichswerte lagen bei canvaskit-wasm 790 ms, canvas 486 ms und @napi-rs/canvas 230 ms
- Im Test SVG to PNG über 100 Iterationen wurde canvaskit-wasm nicht unterstützt; skia-canvas erreichte serial 58 ms pro Durchlauf und async 11 ms pro Durchlauf
- skia-canvas serial: insgesamt 5,83 s
- skia-canvas async: insgesamt 1,08 s
- Ergebnisse des Tests zu Bildskalierung und -rotation über 50 Iterationen
- skia-canvas serial: 100 ms pro Durchlauf, insgesamt 5,00 s
- skia-canvas async: 19 ms pro Durchlauf, insgesamt 935 ms
- Ergebnisse des grundlegenden Texttests über 200 Iterationen
- skia-canvas serial: 21 ms pro Durchlauf, insgesamt 4,26 s
- skia-canvas async: 4 ms pro Durchlauf, insgesamt 819 ms
1 Kommentare
Meinungen auf Hacker News
https://windowjs.org ist ein sehr ähnliches Konzept
Es kapselt Skia und stellt es als Canvas API bereit, läuft aber statt mit Node mit eingebettetem v8 als sehr kleine Runtime
Es war mein erstes Open-Source-Projekt, das ich vor etwa drei Jahren veröffentlicht habe, und es gab auch Pläne, daraus durch Bereitstellung von WebGL, Audio usw. eine JavaScript-Spieleplattform für den Desktop zu machen, aber wegen anderer Arbeit und Projekte ist die Entwicklung eingeschlafen
Schön, solche Projekte zu sehen, die Canvas auch außerhalb des Browsers nutzbar machen
Manchmal möchte man einfach nur eine Netzwerkanfrage machen
Mich würde interessieren, wie stark sich die Binärgröße verringert, wenn man statt Node.js nur V8 einbettet
Node.js verwendet ja ebenfalls V8, und ich frage mich auch, ob der Großteil der Node-Binärgröße nicht von V8, sondern von der Runtime kommt
Ich habe mir die Website und das GitHub-Repository angesehen, konnte aber nicht finden, wie viele kB oder MB ein Window.js-Binary normalerweise hat
Aus Neugier: Wofür verwendet man solche Bibliotheken?
Auf dem Desktop gibt es zum Zeichnen von Formen doch vermutlich bessere native Bibliotheken
Auf der Serverseite müssen wir ebenfalls Vektor-PDFs erzeugen, daher nutzen wir eine Node-Bibliothek, die die HTML Canvas API verwendet und PDFs erstellen kann
Das ist praktisch, weil das Ergebnis exakt den im Webbrowser gerenderten Noten entspricht
Unterm Strich ermöglichen solche Bibliotheken die Wiederverwendung von Code auch in Umgebungen außerhalb des Browsers
Ein großer Vorteil von Skia liegt darin, native Grafik effektiv zu nutzen, und genau deshalb wird es überhaupt in Browser-Engines eingesetzt
Außerdem ist es ein Vorteil, dass es nur eine Ziel-API gibt
Einerseits ist das hier niedriger angesetzt, andererseits habe ich Puppeteer auch schon einmal wie eine 2D-Grafikbibliothek verwendet
Man bekommt Canvas 2D, WebGL, WebGPU und den kompletten HTML/CSS-Stack, kann Texteffekte, Hintergrundbilder und CSS-Transformationen nutzen
Auch das Laden von Bildern und Videos ist möglich, und mit der Screenshot-Funktion lässt sich das zusammengesetzte Ergebnis als Bild ausgeben
Es ist zwar überdimensioniert, aber für meinen Anwendungsfall war das egal; es funktioniert, ist plattformübergreifend und löst das Problem
Skia ist etwas schneller als Direct2D und deutlich schneller als Cairo
Es ist hochgradig optimiert und verfügt auch über GPU-beschleunigte Backends
Canvas ist außerdem eine sehr verbreitete 2D-Zeichen-API
Auch iOS hat eine native Version, Android ebenfalls, und die Zeichenoperationen sind weitgehend ähnlich
Daraus lässt sich viel Nutzen ziehen
Skia enthält mit CanvasKit einen WASM-Build, der Node unterstützt: https://www.npmjs.com/package/canvaskit-wasm; dieses Modul scheint eine Rust-Bindung zu sein
Mich würden die Vor- und Nachteile der beiden Ansätze interessieren
Intern wurde es lediglich aus einer gemischten Rust/C++-Codebasis erzeugt
Der wesentliche Unterschied ist daher, dass WASM eine separate Runtime benötigt, das Node-Binary hingegen nicht
Ist das so etwas wie ein Wrapper um ein Rust-Crate?
https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canvas.html
An Node sehe ich nichts besonders Spezielles
Darauf habe ich lange gewartet
Frühere Versuche, Ähnliches zu machen, erforderten eine Node-Gyp-Installation, und das war ein echter Albtraum
Das ist mehr als eine einfache Rendering-API: Es heißt, sie könne „über die nativen Grafik-Pipelines des Betriebssystems in Fenster rendern und ein browserähnliches UI-Event-Framework bereitstellen“
Für WebGPU-Unterstützung ließe sich vielleicht wgpu hinzufügen, für WebGL-Unterstützung ANGLE
Es unterstützt viele Rendering-Ziele, einschließlich WebGPU und ANGLE
QPainter arbeitet mit Rohbildern und Fenstern, und es gibt auch ausgefeiltere Widgets mit Event-Unterstützung
Außerdem gibt es in Qt eine Funktion, nach der ich eine Zeit lang gesucht hatte: graphicsview, mit dem man auf einer größeren Grafikebene als dem Fenster Millionen von Objekten auflösungsunabhängig rendern und sogar Events verarbeiten kann
Wenn man an Node-kompatiblen Canvas-Implementierungen interessiert ist, gibt es einige
canvaskit-wasm aus dem Skia-Projekt scheint nicht GPU-beschleunigt zu sein: https://github.com/google/skia/tree/main/modules/canvaskit/npm_build
@napi-rs/canvas ist das schnellste Binding: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#performance
node-canvas verwendet Cairo statt Skia: https://github.com/Automattic/node-canvas
Ich nutze es derzeit zusammen mit Deno, wenn ich bei Advent of Code ein einfaches Grafikfenster brauche
Es gibt auch ein window-example: https://skia-canvas.org/#rendering-to-a-window
Man fügt es mit
deno add npm:skia-canvashinzu, setzt indeno.json"nodeModulesDir": "auto"und führt anschließenddeno install --allow-scriptsausDas macht mich wirklich gespannt
Es dürfte es deutlich einfacher machen, eigenständig ausführbare und zugleich webkompatible Grafik zu schreiben
Man muss dann auch nicht mehr aus dem Source mit einer zweiten Skriptsprache bauen und Code generieren, nur um eine Bibliothek für die erste Skriptsprache zu kompilieren
Interessant ist auch, wie gut es sich dafür eignet, Canvas-Code etwa in VS Code in Echtzeit zu rendern oder Code auszuführen, der eine Übersetzungsschicht für die Ausführung im Web-Canvas enthält