4 ポイント 投稿者 GN⁺ 2024-12-04 | 1件のコメント | WhatsAppで共有
  • Skia Canvas は Node.js で HTML Canvas 描画 API をオンスクリーン・オフスクリーンレンダリングに使えるようにし、Google Skia ベースのため Chrome の <canvas> と非常によく似た出力を実現する
  • 出力結果は PDF・SVG のようなベクター形式と、JPEG・PNG・WEBP のようなビットマップ形式で書き出せ、ファイル・dataURL・Buffer・Sharp オブジェクトに接続できる
  • ブラウザー Canvas の外側の領域も扱い、GUI Window レンダリング、イベントフレームワーク、複数ページ Canvas、パス演算、3D 遠近変換、CSS filter、高度なタイポグラフィ制御を提供する
  • 非同期レンダリングとファイル I/O は、ユーザーが設定する ワーカープール のネイティブスレッドで処理されるため、サーバーサイド画像生成や serverless 環境に適している
  • ベンチマークは serial と async モードを分けて計測し、async では複数の反復処理を同時に開始して マルチスレッド 効果を比較している

Node.jsで使うCanvas実装

  • Skia Canvas は Node.js 向けの HTML Canvas 描画 API 実装で、オンスクリーンとオフスクリーンレンダリングの両方をサポートする
  • Google Skia グラフィックスエンジンを使用し、Chrome の <canvas> 要素と非常に近い結果を出力する
  • ブラウザー Canvas がまだ提供していない機能まで含んでいる

出力形式とエクスポート方法

  • 画像出力は ベクター形式ビットマップ形式 の両方をサポートする
    • ベクター: PDF, SVG
    • ビットマップ: JPEG, PNG, WEBP
  • Canvas の結果を複数の出力先へ送れる
  • インタラクティブな GUI Window に直接描画でき、ブラウザーに近い イベント フレームワークを提供する

非同期レンダリングと複数ページ

  • レンダリングとファイル I/O は、ネイティブスレッドを使う ワーカープール で非同期に実行される
  • 1つの Canvas に 複数ページ を作成できる
    • 単一の複数ページ PDF として 出力 できる
    • 複数ファイルに分かれた画像シーケンスとして保存することもできる

ブラウザーCanvasを超えるグラフィックス機能

  • Bézier パスはブール演算と点単位の補間をサポートする
  • 変換機能は基本的な Canvas の拡大縮小・回転・移動に加え、3D 遠近 変換を提供する
  • 図形の塗りつぶしにはビットマップベースの Pattern だけでなく、ベクターベースの Texture も使える
  • 線描画にはカスタム marker を付けられる
  • 画像処理では CSS filter 演算子一式をサポートする

タイポグラフィ制御

  • テキストレンダリングは複数行と 自動改行 に対応する
  • 行単位の テキストメトリクス を提供する
  • small-caps や ligature などの OpenType 機能を標準の font-variant 構文で使える
  • 文字間隔、単語間隔、leading を比例的に制御できる
  • variable fonts をサポートし、weight 値を透過的にマッピングする
  • ローカルファイルから 読み込んだ 非システムフォントも使える

使用例

  • 画像ファイル生成の例では、Canvas を作成し getContext("2d") で 2D コンテキストを取得した後、円錐形グラデーションを stroke スタイルとして使って四角形を描く
    • saveAs("rainbox.png", {density:2}) で高密度画像を保存する
    • canvas.pngcanvas.toBuffer("png") の短縮形として使われる
    • toDataURL("png") の結果を HTML 文字列の中に埋め込める
    • saveAsSync("rainbox.pdf") によりメインスレッドで同期保存も可能
  • 複数ページシーケンスの例では、canvas.newPage() で色の異なるページを作成して保存する
    • all-pages.pdf として複数ページ PDF を保存する
    • page-{2}.png パターンで page-01.png, page-02.png のような複数ファイルを生成する
  • Window レンダリングの例では、Window(300, 300) を作成し、draw イベントでフレーム値に応じて線の太さが変わる円を描く
  • Sharp.js 統合の例では、Canvas の結果を Sharp オブジェクト に変換して後処理したり、Sharp で作成した画像を loadImage で Canvas に描画する流れを示している

ベンチマーク方法と結果

  • ベンチマークは Skia Canvas を 2 つのモードに分けてテストしている
    • serial: 各レンダリング処理の完了を待ってから次の反復に進む
    • async: すべてのテスト反復を一度に開始し、マルチスレッド対応により並列実行する
  • 全体結果は canvas-benchmarks 結果ページ で確認できる
  • 起動遅延時間テストでは、100 回反復基準で skia-canvas が 1 回あたり 1ms 未満、合計 33ms を記録した
    • canvaskit-wasm: 1 回あたり 25ms、合計 2.47 秒
    • canvas: 1 回あたり 88ms、合計 8.77 秒
    • @napi-rs/canvas: 1 回あたり 69ms、合計 6.87 秒
  • Bézier curves テストでは、20 回反復基準で async モードの並列処理差が表れた
    • skia-canvas serial: 1 回あたり 137ms、合計 2.74 秒
    • skia-canvas async: 1 回あたり 28ms、合計 558ms
    • 比較対象は canvaskit-wasm 790ms、canvas 486ms、@napi-rs/canvas 230ms だった
  • SVG to PNG テストでは、100 回反復基準で canvaskit-wasm は未対応で、skia-canvas は serial で 1 回あたり 58ms・async で 1 回あたり 11ms を記録した
    • skia-canvas serial: 合計 5.83 秒
    • skia-canvas async: 合計 1.08 秒
  • 画像の拡大縮小・回転テスト 50 回反復の結果
    • skia-canvas serial: 1 回あたり 100ms、合計 5.00 秒
    • skia-canvas async: 1 回あたり 19ms、合計 935ms
  • 基本テキストテスト 200 回反復の結果
    • skia-canvas serial: 1 回あたり 21ms、合計 4.26 秒
    • skia-canvas async: 1 回あたり 4ms、合計 819ms

1件のコメント

 
GN⁺ 2024-12-04
Hacker Newsのコメント
  • https://windowjs.org もかなり似たコンセプト Skiaをラップして Canvas API として公開しているが、Nodeの代わりにv8を内蔵し、非常に小さなランタイムで動作する 約3年前に公開した最初のオープンソースプロジェクトで、WebGL・オーディオなども公開してデスクトップ向けJavaScriptゲームプラットフォームにする計画もあったが、別の仕事やプロジェクトのため開発は中断した ブラウザ外でもCanvasを使えるようにするこうしたプロジェクトが出てきてうれしい

    • かなり良いが、隔離が絶対に必要というわけでないなら、v8しかない環境 なのは惜しい たまには単にネットワークリクエストを送りたいこともある
    • 素晴らしいプロジェクト Node.jsの代わりにV8だけを内蔵すると バイナリサイズ がどれくらい減るのか気になる Node.jsもV8を使っているが、Nodeバイナリの大半はV8ではなくランタイム由来なのかも気になる WebサイトとGitHubリポジトリを見て回ったが、Window.jsのバイナリが普通どれくらいのkBやMBなのか見つけられなかった
  • 興味本位だが、こういうライブラリがどこで使われるのか気になる デスクトップなら図形を描くのにもっと良い ネイティブライブラリ があるのではと思う

    • Soundsliceでは、フロントエンドのJavaScript/Canvasで作った独自の 楽譜レンダリング用グラフィックライブラリ を使っている サーバー側でもベクターPDFを生成する必要があるので、HTML Canvas APIを使ってPDFを作れるNodeライブラリを使っている こうするとWebブラウザでレンダリングされた楽譜と結果が正確に一致するので都合が良い 結局のところ、こうしたライブラリはブラウザではない環境でもコードの再利用を可能にしてくれる
    • 2D描画でSkiaほど強力で、一般に「ネイティブ」と呼べるものは実際ほとんどない Skiaの大きな利点は ネイティブグラフィックスを効果的に活用 できることにあり、そもそもブラウザエンジンで使われている理由もそこにある しかも対象APIが1つであることも利点
    • 移植性が利点ではないか? GPUアクセラレーション対応の2Dレンダリングライブラリで他に何があるのか気になる 一方ではこちらの方が低レベルだが、別の意味ではPuppeteerを2Dグラフィックライブラリのように使ったこともある Canvas 2D、WebGL、WebGPU、HTML/CSS一式が手に入り、テキスト効果・背景画像・CSS変形も使える 画像や動画の読み込みも可能で、スクリーンショット機能で合成結果を画像にできる やりすぎではあるが、自分の用途では問題なく、動作し、クロスプラットフォームで、課題を解決してくれる
    • Canvasより優れた 2Dグラフィック描画API を持つプラットフォームを挙げられるなら驚くと思う SkiaはDirect2Dより少し速く、Cairoよりずっと速い 高度に最適化されており、GPUアクセラレーションのバックエンドも備えている
    • 同じコードを複数のプラットフォームで実行できるのは大きなボーナス Canvasは非常に一般的な 2D描画API でもある iOSにもネイティブ版がありAndroidにもネイティブ版があり、描画操作もだいたい似ている これを活用できるなら得るものは大きい
  • SkiaにはNodeをサポートするWASMビルドのCanvasKitが含まれている https://www.npmjs.com/package/canvaskit-wasm が、このモジュールは Rustバインディング のように見える 2つのアプローチの長所と短所が気になる

    • 「Rustバインディング」というより、あらかじめコンパイルされた Nodeバイナリ に近い 内部的にはRust/C++混在のコードベースから生成されているだけ なので中核的な違いは、WASMには別個のランタイムが必要だが、Nodeバイナリには不要という点
  • これはRustクレートを包んだラッパーのようなもの? https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canvas.html

    • Skia Canvasはバインディングさえあればほぼあらゆるプログラミング言語で動かせると理解している https://www.swig.org/ Nodeにだけ特別な点はあまり見当たらない
    • その通り、napi-rs で生成されている
  • こういうものをずっと待っていた 以前に似たことをやろうとした試みでは Node-Gypのインストール が必要で、本当に悪夢のようだった

  • 単なるレンダリングAPI以上のものだ: 「OSネイティブのグラフィックパイプラインでウィンドウにレンダリングでき、ブラウザのようなUIイベントフレームワークを提供する」とある WebGPU対応のために wgpu を、WebGL対応のためにANGLEを追加することもできそう

    • Skiaは広く使われているライブラリで、思いつくだけでもChrome、Firefox、LibreOfficeが使っている WebGPUやANGLEを含め、多くの レンダリング先 をサポートしている
    • Qtがすでに提供しているものとかなり似ている QPainterは生の画像とウィンドウを扱え、イベント対応のより洗練されたウィジェットもある それに、しばらく探していた機能でもあるが、Qtにはウィンドウより大きなグラフィック平面上で何百万ものオブジェクトを 解像度非依存でレンダリング し、さらにイベント処理までできるgraphicsviewがある
  • Node互換のCanvas実装に興味があるなら、いくつかある Skiaプロジェクトのcanvaskit-wasmはGPUアクセラレーションではないようだ: https://github.com/google/skia/tree/main/modules/canvaskit/npm_build @napi-rs/canvasは最速のバインディング: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#performance node-canvasはSkiaではなくCairoを使っている: https://github.com/Automattic/node-canvas

  • Advent of Codeで簡単なグラフィックウィンドウが必要なとき、今はDenoと一緒に使っている window-exampleもある: https://skia-canvas.org/#rendering-to-a-window deno add npm:skia-canvas で追加し、deno.json"nodeModulesDir": "auto" を入れてから deno install --allow-scripts を実行すればよい

  • これを見ると本当に期待が高まる スタンドアロン実行も可能で、Web互換のグラフィックスをずっと楽に書けるようになりそう 1つ目のスクリプト言語向けライブラリをコンパイルするために、2つ目のスクリプト言語でソースからビルドしてコード生成する必要もなくなる VS Codeのような場所でCanvasコードを リアルタイムレンダリング したり、Web Canvasで動かすための変換レイヤーを持つコードを実行したりするのに、どれほど向いているのかも興味深い