4 Punkte von GN⁺ 2024-12-04 | 1 Kommentare | Auf WhatsApp teilen
  • 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
  • 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 mit getContext("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.png wird als Kurzform für canvas.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
  • 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.pdf gespeichert
    • Mit dem Muster page-{2}.png werden mehrere Dateien wie page-01.png und page-02.png erzeugt
  • Das Window-Rendering-Beispiel erstellt Window(300, 300) und zeichnet im draw-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 loadImage in 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

 
GN⁺ 2024-12-04
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

    • Ziemlich cool, aber wenn Isolation nicht unbedingt nötig ist, ist eine Nur-v8-Umgebung etwas schade
      Manchmal möchte man einfach nur eine Netzwerkanfrage machen
    • Tolles Projekt
      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

    • Bei Soundslice verwenden wir eine maßgeschneiderte Grafikbibliothek zum Rendern von Noten, die mit Frontend-JavaScript/Canvas gebaut ist
      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
    • Im Bereich 2D-Zeichnen gibt es tatsächlich nicht viel, das so leistungsfähig wie Skia ist und gemeinhin als „nativ“ gelten würde
      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
    • Ist Portabilität nicht der Vorteil? Ich frage mich, welche anderen GPU-beschleunigten 2D-Rendering-Bibliotheken es 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
    • Es würde mich überraschen, wenn jemand eine Plattform nennen könnte, die eine bessere 2D-Grafik-Zeichen-API als Canvas hat
      Skia ist etwas schneller als Direct2D und deutlich schneller als Cairo
      Es ist hochgradig optimiert und verfügt auch über GPU-beschleunigte Backends
    • Dass derselbe Code auf mehreren Plattformen laufen kann, ist ein großer Bonus
      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

    • Es ist weniger eine „Rust-Bindung“ als vielmehr ein vorkompiliertes Node-Binary
      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

    • Soweit ich es verstehe, kann Skia Canvas mit passenden Bindings in praktisch jeder Programmiersprache funktionieren https://www.swig.org/
      An Node sehe ich nichts besonders Spezielles
    • Genau, es wird mit napi-rs erzeugt
  • 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

    • Skia ist eine weit verbreitete Bibliothek; spontan fallen mir Chrome, Firefox und LibreOffice als Nutzer ein
      Es unterstützt viele Rendering-Ziele, einschließlich WebGPU und ANGLE
    • Das ähnelt ziemlich dem, was Qt bereits bietet
      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-canvas hinzu, setzt in deno.json "nodeModulesDir": "auto" und führt anschließend deno install --allow-scripts aus

  • Das 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