- Notifications
You must be signed in to change notification settings - Fork83
A multi-threaded, GPU-powered, 2D vector graphics environment for Node.js
License
samizdatco/skia-canvas
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation

Skia Canvas is a Node.js implementation of the HTML Canvas drawingAPI for both on- and off-screen rendering. Since it uses Google’sSkia graphics engine, its output is very similar to Chrome’s<canvas>
element — though it's also capable of things the brower’s Canvas still can't achieve.
In particular, Skia Canvas:
- generates images in both raster (JPEG, PNG, & WEBP) and vector (PDF & SVG) formats
- can draw to interactive GUIwindows and provides a browser-likeevent framework
- can save images tofiles, encode todataURL strings, and returnBuffers orSharp objects
- uses native threads in auser-configurable worker pool for asynchronous rendering and file I/O
- can createmultiple ‘pages’ on a given canvas and thenoutput them as a single, multi-page PDF or an image-sequence saved to multiple files
- cansimplify,blunt,combine,excerpt, andatomize bézier paths usingefficient boolean operations or point-by-pointinterpolation
- provides3D perspective transformations in addition toscaling,rotation, andtranslation
- can fill shapes with vector-basedTextures in addition to bitmap-basedPatterns and supports line-drawing with custommarkers
- supports the full set ofCSS filter image processing operators
- offers rich typographic control including:
- multi-line,word-wrapped text
- line-by-linetext metrics
- small-caps, ligatures, and other opentype features accessible using standardfont-variant syntax
- proportionalletter-spacing,word-spacing, andleading
- support forvariable fonts and transparent mapping of weight values
- use of non-system fontsloaded from local files
If you’re running on a supported platform, installation should be as simple as:
npm install skia-canvas
This will download a pre-compiled library from the project’s most recentrelease.
If you use thepnpm
package manager, it will not downloadskia-canvas
's platform-native binary unless you explicitly allow it. You can do this interactively via the ‘approve builds’ command (note that you need to press<space>
to toggle the selection and then<enter>
to proceed):
pnpm install skia-canvaspnpm approve-builds
In non-interactive scenarios (like building via CI), you can approve the build step when you addskia-canvas
to your project:
pnpm install skia-canvas --allow-build=skia-canvas
Alternatively, you can add apnpm.onlyBuiltDependencies
entry to yourpackage.json
file to mark the build-step as allowed:
{"pnpm": {"onlyBuiltDependencies": ["skia-canvas"] }}
The underlying Rust library usesN-API v8 which allows it to run on Node.js versions:
- v12.22+
- v14.17+
- v15.12+
- v16.0.0 and later
Pre-compiled binaries are available for:
- Linux — x64 & arm64
- macOS — x64 & Apple silicon
- Windows — x64 & arm64
- AWS Lambda — x64 & arm64
The library is compatible with Linux systems usingglibc 2.28 or later as well as Alpine Linux and themusl C library it favors. In both cases, theFontconfig library must be installed on the system forskia-canvas
to operate correctly.
If you are setting up aDockerfile that usesnode
as its basis, the simplest approach is to set yourFROM
image to one of the (Debian-derived) defaults likenode:lts
,node:18
,node:16
,node:14-buster
,node:12-buster
,node:bullseye
,node:buster
, or simply:
FROM node
You can also use the ‘slim’ image if you manually install fontconfig:
FROM node:slimRUN apt-get update && apt-get install -y -q --no-install-recommends libfontconfig1
If you wish to use Alpine as the underlying distribution, you can start with something along the lines of:
FROM node:alpineRUN apk update && apk add fontconfig
Skia Canvas depends on libraries that aren't present in the standard Lambdaruntime. You can add these to your function by uploading a ‘layer’ (a zip file containing the required libraries andnode_modules
directory) and configuring your function to use it.
- Look in theAssets section of Skia Canvas’scurrent release and download the
aws-lambda-x64.zip
oraws-lambda-arm64.zip
file (depending on your architecture) but don’t decompress it - Go to the AWS LambdaLayers console and click theCreate Layer button, then fill in the fields:
- Name:
skia-canvas
(or whatever you want) - Description: you might want to note the Skia Canvas version here
- Compatible architectures: selectx86_64 orarm64 depending on which zip you chose
- Compatible runtimes: selectNode.js 22.x (and/or 20.x & 18.x)
- Click theChoose file button and select the zip file you downloaded in Step 1, then clickCreate
You can now use this layer in any function you create in theFunctions console. After creating a new function, click theAdd a Layer button and you can select your newly created Skia Canvas layer from theCustom Layers layer source.
If you are using a framework like Next.js that bundles your serverside with Webpack, you'll need to markskia-canvas
as an ‘external’, otherwise its platform-native binary file will be excluded from the final build. Try adding these options to yournext.config.ts
file:
constnextConfig:NextConfig={serverExternalPackages:['skia-canvas'],webpack:(config,options)=>{if(options.isServer){config.externals=[ ...config.externals,{'skia-canvas':'commonjs skia-canvas'},]}returnconfig}};
If prebuilt binaries aren’t available for your system you’ll need to compile the portions of this library that directly interface with Skia.
Start by installing:
- TheRust compiler and cargo package manager using
rustup
- A C compiler toolchain (either LLVM/Clang or MSVC)
- Python 3 (used by Skia'sbuild process)
- TheNinja build system
- On Linux: Fontconfig and OpenSSL
Detailed instructions for setting up these dependencies on different operating systems can be found in the ‘Building’ section of the Rust Skia documentation. Once all the necessary compilers and libraries are present, runningnpm run build
will give you a usable library (after a fairly lengthy compilation process).
There are a handful of settings that can only be configured at launch and will apply to all the canvases you create in your script. The sections below describe the differentenvironment variables you can set to make global changes. You can either set them as part of your command line invocation, or place them in a
.env
file in your project directory and use Node 20's--env-file
argument to load them all at once.
When rendering canvases in the background (e.g., by using the asynchronoussaveAs ortoBuffer methods), tasks are spawned in a thread pool managed by therayon library. By default it will create up to as many threads as your CPU has cores. You can see this default value by inspecting anyCanvas object'sengine.threads
property. If you wish to override this default, you can set theSKIA_CANVAS_THREADS
environment variable to your preferred value.
For example, you can limit your asynchronous processing to two simultaneous tasks by running your script with:
SKIA_CANVAS_THREADS=2 node my-canvas-script.js
There are a number of situations where the browser API will react to invalid arguments by silently ignoring the method call rather than throwing an error. For example, these lines will simply have no effect:
ctx.fillRect(0,0,100,"october")ctx.lineTo(NaN,0)
Skia Canvas does its best to emulate these quirks, but allows you to opt into a stricter mode in which it will throw TypeErrors in these situations (which can be useful for debugging).
Set theSKIA_CANVAS_STRICT
environment variable to1
ortrue
to enable this mode.
import{Canvas}from'skia-canvas'letcanvas=newCanvas(400,400),ctx=canvas.getContext("2d"),{width, height}=canvas;letsweep=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=sweepctx.lineWidth=100ctx.strokeRect(100,100,200,200)// render to multiple destinations using a background threadasyncfunctionrender(){// save a ‘retina’ image...awaitcanvas.saveAs("rainbox.png",{density:2})// ...or use a shorthand for canvas.toBuffer("png")letpngData=awaitcanvas.png// ...or embed it in a stringletpngEmbed=`<img src="${awaitcanvas.toDataURL("png")}">`}render()// ...or save the file synchronously from the main threadcanvas.saveAsSync("rainbox.pdf")
import{Canvas}from'skia-canvas'letcanvas=newCanvas(400,400),ctx=canvas.getContext("2d"),{width, height}=canvasfor(constcolorof['orange','yellow','green','skyblue','purple']){ctx=canvas.newPage()ctx.fillStyle=colorctx.fillRect(0,0,width,height)ctx.fillStyle='white'ctx.arc(width/2,height/2,40,0,2*Math.PI)ctx.fill()}asyncfunctionrender(){// save to a multi-page PDF fileawaitcanvas.saveAs("all-pages.pdf")// save to files named `page-01.png`, `page-02.png`, etc.awaitcanvas.saveAs("page-{2}.png")}render()
import{Window}from'skia-canvas'letwin=newWindow(300,300)win.title="Canvas Window"win.on("draw",e=>{letctx=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 withSharp.js
importsharpfrom'sharp'import{Canvas,loadImage}from'skia-canvas'letcanvas=newCanvas(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 desaturateawaitcanvas.toSharp().modulate({saturation:.25}).jpeg().toFile("faded.jpg")// Convert an ImageData to a Sharp object and save a grayscale versionletimgData=ctx.getImageData(0,0,width,height,{matte:'white',density:2})awaitimgData.toSharp().grayscale().png().toFile("black-and-white.png")// Create an image using Sharp then draw it to the canvas as an Image objectletsharpImage=sharp({create:{width:x,height:y,channels:4,background:"skyblue"}})letcanvasImage=awaitloadImage(sharpImage)ctx.drawImage(canvasImage,x,0)awaitcanvas.saveAs('mosaic.png')
This project is deeply indebted to the work of theRust Skia project whose Skia bindings provide a safe and idiomatic interface to the mess of C++ that lies underneath. Many thanks to the developers ofnode-canvas for their terrific set of unit tests. In the absence of anAcid Test for canvas, these routines were invaluable.
- @mpaparno contributed support for SVG rendering, raw image-buffer handling, WEBP import/export and numerous bugfixes
- @Salmondx developed the initial Raw image loading & rendering routines
- @lucasmerlin helped get GPU rendering working on Vulkan
- @cprecioso &@saantonandre corrected and expanded upon the TypeScript type definitions
- @meihuanyu contributed filter & path rendering fixes
© 2020–2025Samizdat Drafting Co.
About
A multi-threaded, GPU-powered, 2D vector graphics environment for Node.js
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.