skia-canvas

repository·main·Indexed 25 days ago

https://github.com/samizdatco/skia-canvas

A high-performance Node.js implementation of the HTML Canvas API powered by the Skia graphics engine. It supports server-side image generation (PDF, SVG, JPEG, PNG, WEBP) and interactive GUI window rendering. Key features include 3D perspective transformations, advanced typography with variable fonts, multi-page PDF output, and a configurable multi-threaded worker pool for asynchronous rendering.

Tokens
29.2K
Snippets
58
Records
173
Agent score
82%

What's inside skia-canvas

  1. Overview of Skia Canvas API classes

    main

    Skia Canvas emulates standard browser objects and provides several utility classes and functions for canvas manipulation, font management, and windowed display.

    Emulated Browser Objects

    The library provides implementations of standard web APIs, some of which include Skia-specific extensions (marked with 🧪):

    • Canvas 🧪
    • CanvasGradient
    • CanvasPattern
    • CanvasRenderingContext2D 🧪
    • DOMMatrix
    • Image 🧪
    • ImageData 🧪
    • Path2D 🧪

    Additional Utilities

    • FontLibrary: A global object used to inspect system fonts and load additional fonts.
    • Window: A class for displaying a canvas interactively in an on-screen window.
    • App: A helper class for coordinating multiple Window instances within a single script.
    • loadImage(): A utility function to load Image objects asynchronously.
    • loadImageData(): A utility function to load ImageData objects asynchronously.
  2. Overview of Skia Canvas capabilities

    main

    Skia Canvas is a Node.js implementation of the HTML Canvas API using Google's Skia graphics engine. It supports both on-screen and off-screen rendering and offers features beyond standard browser canvas implementations:

    • Formats: Generates vector (PDF, SVG) and bitmap (JPEG, PNG, WEBP) images.
    • Rendering: Supports 3D perspective transformations, CSS filters, and vector-based textures.
    • Typography: Advanced control including variable fonts, word-wrapping, and OpenType features (ligatures, small-caps).
    • Path Operations: Efficient Bézier path manipulation (simplify, blunt, combine, excerpt, atomize).
    • Concurrency: Uses native threads in a configurable worker pool for asynchronous I/O and rendering.
    • Multi-page: Supports creating multiple 'pages' on a single canvas for multi-page PDF output.
  3. Understand the App global manager

    main

    The App class is a static global manager used to control the lifecycle of GUI windows and the application process. It does not need to be instantiated with new.

    Key responsibilities include:

    • Managing the global event loop mode via .eventLoop.
    • Controlling the application's execution via .launch() and .quit().
    • Monitoring the application state via .running.
    • Accessing all active windows via .windows.
    • Setting the global frame rate via .fps.
  4. Draw ImageData to a Canvas

    main

    There are two ways to put ImageData onto a canvas, and they behave differently:

    1. putImageData(): This is the standard method. It performs a pixel-for-pixel copy and ignores the current context state (transformations, filters, or global opacity).
    2. drawImage(): Skia Canvas allows you to pass ImageData to drawImage(). Unlike putImageData(), this method honors the current context settings (transformations, filters, etc.).

    You can also pass ImageData objects to createPattern() to use them as patterns.

    Note: putImageData() is a copy operation, not a drawing operation.

  5. Understand Skia Canvas performance modes: Serial vs Async

    main

    Skia Canvas supports two execution modes for rendering operations:

    1. Serial Mode: Each rendering operation is awaited sequentially. The next operation only begins after the current one completes.
    2. Async Mode: Multiple test iterations or rendering operations are started simultaneously and executed in parallel. This mode leverages the library's built-in multi-threading support to improve throughput.

    Use Async Mode when you have many independent rendering tasks to perform and want to maximize hardware utilization via multi-threading.

  6. Create multi-page PDF or image sequences

    main

    Skia Canvas supports a multi-page model. By calling canvas.newPage(), you create a new drawing context on the same canvas instance. This allows you to treat a single Canvas object as a container for multiple pages, which can then be exported as a single multi-page PDF or a sequence of individual image files using brace expansion in the filename.

    import {Canvas} from 'skia-canvas'
    
    let canvas = new Canvas(400, 400),
        ctx = canvas.getContext("2d"),
        {width, height} = canvas
    
    for (const color of ['orange', 'yellow', 'green', 'skyblue', 'purple']){
      ctx = canvas.newPage()
      ctx.fillStyle = color
      ctx.fillRect(0,0, width, height)
      ctx.fillStyle = 'white'
      ctx.arc(width/2, height/2, 40, 0, 2 * Math.PI)
      ctx.fill()
    }
    
    async function render(){
      // save to a multi-page PDF file
      await canvas.saveAs("all-pages.pdf")
    
      // save to files named `page-01.png`, `page-02.png`, etc.
      await canvas.saveAs("page-{2}.png")
    }
    render()
  7. Understand Skia Canvas execution modes: serial vs async

    main

    Skia Canvas supports two primary execution modes for rendering operations:

    1. Serial Mode: Each rendering operation is awaited before the next one begins. This is the standard sequential execution pattern.
    2. Async Mode: Multiple test iterations or rendering operations are started simultaneously. This mode leverages the library's built-in multi-threading support to execute operations in parallel, which can significantly improve performance for batch processing tasks.
  8. Work with multi-page canvases via .pages

    main

    The .pages attribute is an array of CanvasRenderingContext2D objects.

    • The first page is created automatically upon initialization.
    • Additional pages can be added using the newPage() method.
    • All pages remain drawable persistently; you can modify any page at any time.
    • When accessing the .pdf property, the resulting buffer will contain all pages as a multi-page PDF. For other formats, only the most recent page is included by default.
  9. Use the FontLibrary to manage fonts

    main

    The FontLibrary is a static global class used to inspect system fonts or dynamically load new ones. Because it is a static class, you do not need to instantiate it with new. Changes made via FontLibrary (such as loading new fonts) are shared across all canvases created in your application.

    Supported font formats include:

    • OpenType (.otf)
    • TrueType (.ttf)
    • Web-fonts (.woff & .woff2)
  10. Implement animations using frame and draw events

    main

    For smooth animations, use the specialized animation events provided by the Window object:

    • setup: Emitted just before the window is displayed. Use this to initialize animation data. Immediately after setup, the frame and draw events fire.
    • frame: Similar to requestAnimationFrame. Use this to schedule redrawing to maintain a constant frame rate. The event object provides a window-specific frame counter.
    • draw: Fires immediately after frame.

    Note on automatic clearing: If you use .on("draw", ...) as an event handler, the window will automatically erase the canvas before calling your handler. This behavior persists until you remove the handler using .off() or .removeAllListeners().

  11. Control GPU and CPU rendering

    main

    By default, Skia uses the system's GPU (Metal on macOS, Vulkan on Linux/Windows) for faster rendering of complex scenes.

    When to disable GPU

    Disable the GPU (set .gpu to false) if you are repeatedly accessing the canvas's bitmap data (e.g., via getImageData) from JavaScript. The overhead of copying pixels between GPU and CPU memory can outweigh the rendering speedup.

    How to toggle

    You can pass gpu: false to the constructor or reassign the .gpu property on an existing canvas instance.

    new Canvas(512, 512, {gpu:false}) // use CPU-based rendering
  12. Render to an interactive GUI window

    main

    The Window class allows you to render the canvas to an interactive GUI window. It provides a browser-like event framework, such as the draw event, which is triggered when the window needs to repaint. The event object provides access to the canvas via e.target.canvas.

    import {Window} from 'skia-canvas'
    
    let win = new Window(300, 300)
    win.title = "Canvas Window"
    win.on("draw", e => {
      let ctx = e.target.canvas.getContext("2d")
      ctx.lineWidth = 25 + 25 * Math.cos(e.frame / 10)
      ctx.beginPath()
      ctx.arc(150, 150, 50, 0, 2 * Math.PI)
      ctx.stroke()
    
      ctx.beginPath()
      ctx.arc(150, 150, 10, 0, 2 * Math.PI)
      ctx.stroke()
      ctx.fill()
    })