Skip to main content

Skia Canvas Skia Canvas

Skia Canvas is a Node.js implementation of the HTML Canvas drawing API for both on- and off-screen rendering. Since it uses Google’s Skia graphics engine, its output is very similar to Chrome’s <canvas> element — though it's also capable of things the browser’s Canvas still can't achieve.

In particular, Skia Canvas:

Example Usage

Generating image files

import {Canvas} from 'skia-canvas'

let canvas = new Canvas(400, 400),
ctx = canvas.getContext("2d"),
{width, height} = canvas;

let sweep = ctx.createConicGradient(Math.PI * 1.2, width/2, height/2)
sweep.addColorStop(0, "red")
sweep.addColorStop(0.25, "orange")
sweep.addColorStop(0.5, "yellow")
sweep.addColorStop(0.75, "green")
sweep.addColorStop(1, "red")
ctx.strokeStyle = sweep
ctx.lineWidth = 100
ctx.strokeRect(100,100, 200,200)

// render to multiple destinations using a background thread
async function render(){
// save a ‘retina’ image...
await canvas.saveAs("rainbox.png", {density:2})
// ...or use a shorthand for canvas.toBuffer("png")
let pngData = await canvas.png
// ...or embed it in a string
let pngEmbed = `<img src="${await canvas.toDataURL("png")}">`
}
render()

// ...or save the file synchronously from the main thread
canvas.saveAsSync("rainbox.pdf")

Multi-page sequences

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()

Rendering to a window

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()
})

Integrating with Sharp.js

import sharp from 'sharp'
import {Canvas, loadImage} from 'skia-canvas'

let canvas = new Canvas(400, 400),
ctx = canvas.getContext("2d"),
{width, height} = canvas,
[x, y] = [width/2, height/2]

ctx.fillStyle = 'red'
ctx.fillRect(0, 0, x, y)
ctx.fillStyle = 'orange'
ctx.fillRect(x, y, x, y)

// Render the canvas to a Sharp object on a background thread then desaturate
await canvas.toSharp().modulate({saturation:.25}).jpeg().toFile("faded.jpg")

// Convert an ImageData to a Sharp object and save a grayscale version
let imgData = ctx.getImageData(0, 0, width, height, {matte:'white', density:2})
await imgData.toSharp().grayscale().png().toFile("black-and-white.png")

// Create an image using Sharp then draw it to the canvas as an Image object
let sharpImage = sharp({create:{ width:x, height:y, channels:4, background:"skyblue" }})
let canvasImage = await loadImage(sharpImage)
ctx.drawImage(canvasImage, x, 0)
await canvas.saveAs('mosaic.png')

Benchmarks

In these benchmarks, Skia Canvas is tested running in two modes: serial and async. When running serially, each rendering operation is awaited before continuing to the next test iteration. When running asynchronously, all the test iterations are begun at once and are executed in parallel using the library’s multi-threading support.

See full results here…

Startup latency

LibraryPer RunTotal Time (100 iterations)
canvaskit-wasm     25 ms 2.46 s
canvas     88 ms 8.76 s
@napi-rs/canvas     73 ms 7.30 s
skia-canvas     <1 ms  33 ms

Bezier curves

LibraryPer RunTotal Time (20 iterations)
canvaskit-wasm 👁️ 789 ms15.77 s
canvas 👁️ 488 ms 9.76 s
@napi-rs/canvas 👁️ 233 ms 4.65 s
skia-canvas (serial) 👁️ 137 ms 2.74 s
skia-canvas (async) 👁️  28 ms 558 ms

SVG to PNG

LibraryPer RunTotal Time (100 iterations)
canvaskit-wasm —————  —————    not supported
canvas 👁️ 122 ms12.20 s
@napi-rs/canvas 👁️  98 ms 9.76 s
skia-canvas (serial) 👁️  59 ms 5.91 s
skia-canvas (async) 👁️  11 ms 1.06 s

Scale/rotate images

LibraryPer RunTotal Time (50 iterations)
canvaskit-wasm 👁️ 279 ms13.95 s
canvas 👁️ 284 ms14.21 s
@napi-rs/canvas 👁️ 116 ms 5.78 s
skia-canvas (serial) 👁️ 100 ms 5.01 s
skia-canvas (async) 👁️  19 ms 937 ms

Basic text

LibraryPer RunTotal Time (200 iterations)
canvaskit-wasm 👁️  24 ms 4.74 s
canvas 👁️  24 ms 4.86 s
@napi-rs/canvas 👁️  19 ms 3.82 s
skia-canvas (serial) 👁️  21 ms 4.24 s
skia-canvas (async) 👁️   4 ms 781 ms