• Stars
    star
    1,635
  • Rank 28,484 (Top 0.6 %)
  • Language
    Rust
  • License
    MIT License
  • Created about 4 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

High performance skia binding to Node.js. Zero system dependencies and pure npm packages without any postinstall scripts nor node-gyp.

skr canvas

CI Skia Version install size Downloads

๐Ÿš€ Help me to become a full-time open-source developer by sponsoring me on Github

Google Skia binding to Node.js via Node-API, 0 System dependencies!

โš ๏ธ This project is in pre-release stage. And there may some bugs existed.
For details on planned features and future direction please refer to the Roadmap.

ไธญๆ–‡ๆ–‡ๆกฃ

Install

yarn add @napi-rs/canvas
npm install @napi-rs/canvas

Support matrix

node10 node12 node14 node16
Windows x64 โœ“ โœ“ โœ“ โœ“
macOS x64 โœ“ โœ“ โœ“ โœ“
macOS arm64 (m chips) โœ“ โœ“ โœ“ โœ“
Linux x64 gnu โœ“ โœ“ โœ“ โœ“
Linux x64 musl โœ“ โœ“ โœ“ โœ“
Linux arm64 gnu โœ“ โœ“ โœ“ โœ“
Linux arm64 musl โœ“ โœ“ โœ“ โœ“
Linux arm gnueabihf โœ“ โœ“ โœ“ โœ“
Linux arm64 android โœ“ โœ“ โœ“ โœ“

System requirement

arm64

cortex-a57 or newer CPU architecture on Linux.

All Apple M chips on macOS.

armv7

cortex-a7 or newer CPU architecture.

glibc

Since Skia relies on the glibc 2.18 API, you need to have at least glibc version >= 2.18 on your system.

AWS Lambda usage

To use this library on Lambda you will need to use a Lambda layer.

You can simply attach a lambda layer by getting an ARN from Canvas-Lambda-Layer

Make sure to exclude @napi-rs/canvas while bundling your Lambda.

Usage

const { promises } = require('fs')
const { join } = require('path')
const { createCanvas } = require('@napi-rs/canvas')

const canvas = createCanvas(300, 320)
const ctx = canvas.getContext('2d')

ctx.lineWidth = 10
ctx.strokeStyle = '#03a9f4'
ctx.fillStyle = '#03a9f4'

// Wall
ctx.strokeRect(75, 140, 150, 110)

// Door
ctx.fillRect(130, 190, 40, 60)

// Roof
ctx.beginPath()
ctx.moveTo(50, 140)
ctx.lineTo(150, 60)
ctx.lineTo(250, 140)
ctx.closePath()
ctx.stroke()

async function main() {
  const pngData = await canvas.encode('png') // JPEG, AVIF and WebP are also supported
  // encoding in libuv thread pool, non-blocking
  await promises.writeFile(join(__dirname, 'simple.png'), pngData)
}

main()

Emoji text

const { writeFileSync } = require('fs')
const { join } = require('path')

const { createCanvas, GlobalFonts } = require('@napi-rs/canvas')

GlobalFonts.registerFromPath(join(__dirname, '..', 'fonts', '[email protected]'), 'Apple Emoji')
GlobalFonts.registerFromPath(join(__dirname, '..', '__test__', 'fonts', 'COLRv1.ttf'), 'COLRv1')

console.info(GlobalFonts.families)

const canvas = createCanvas(760, 360)
const ctx = canvas.getContext('2d')

ctx.font = '50px Apple Emoji'
ctx.strokeText('๐Ÿ˜€๐Ÿ˜ƒ๐Ÿ˜„๐Ÿ˜๐Ÿ˜†๐Ÿ˜…๐Ÿ˜‚๐Ÿคฃโ˜บ๏ธ๐Ÿ˜Š๐Ÿ˜Š๐Ÿ˜‡', 50, 150)

ctx.font = '100px COLRv1'
ctx.fillText('abc', 50, 300)

const b = canvas.toBuffer('image/png')

writeFileSync(join(__dirname, 'draw-emoji.png'), b)

Performance

See benchmark for benchmark code.

Hardware info:

OS: Windows 10 x86_64
Host: Micro-Star International Co., Ltd. MS-7C35
Kernel: 10.0.19043
Terminal: Windows Terminal
CPU: AMD Ryzen 9 5950X (32) @ 3.400GHz
Memory: 32688MiB
โฏ yarn bench

> @napi-rs/[email protected] bench D:\workspace\skia-rs
> node -r @swc-node/register benchmark/bench.ts

Running "Draw house" suite...
Progress: 100%

  skia-canvas:
    26 ops/s, ยฑ0.70%   | slowest, 29.73% slower

  node-canvas:
    30 ops/s, ยฑ6.95%   | 18.92% slower

  @napi-rs/canvas:
    37 ops/s, ยฑ6.30%   | fastest

Finished 3 cases!
  Fastest: @napi-rs/canvas
  Slowest: skia-canvas
Running "Draw gradient" suite...
Progress: 100%

  skia-canvas:
    36 ops/s, ยฑ6.12%   | 14.29% slower

  node-canvas:
    34 ops/s, ยฑ5.60%   | slowest, 19.05% slower

  @napi-rs/canvas:
    42 ops/s, ยฑ0.53%   | fastest

Finished 3 cases!
  Fastest: @napi-rs/canvas
  Slowest: node-canvas

Features

Path2D

new Path2D()
new Path2D(path: Path2D)
// new Path2D('M108.956,403.826c0,0,0.178,3.344-1.276,3.311  c-1.455-0.033-30.507-84.917-66.752-80.957C40.928,326.18,72.326,313.197,108.956,403.826z')
new Path2D(path: string)
export interface DOMMatrix2DInit {
  a: number
  b: number
  c: number
  d: number
  e: number
  f: number
}

export class Path2D {
  constructor(path?: Path2D | string)

  addPath(path: Path2D, transform?: DOMMatrix2DInit): void
  arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void
  arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void
  bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void
  closePath(): void
  ellipse(
    x: number,
    y: number,
    radiusX: number,
    radiusY: number,
    rotation: number,
    startAngle: number,
    endAngle: number,
    anticlockwise?: boolean,
  ): void
  lineTo(x: number, y: number): void
  moveTo(x: number, y: number): void
  quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void
  rect(x: number, y: number, w: number, h: number): void

  // PathKit methods
  op(path: Path2D, operation: PathOp): Path2D
  toSVGString(): string
  getFillType(): FillType
  getFillTypeString(): string
  setFillType(type: FillType): void
  simplify(): Path2D
  asWinding(): Path2D
  stroke(stroke?: StrokeOptions): Path2D
  transform(transform: DOMMatrix2DInit): Path2D
  getBounds(): [left: number, top: number, right: number, bottom: number]
  computeTightBounds(): [left: number, top: number, right: number, bottom: number]
  trim(start: number, end: number, isComplement?: boolean): Path2D
  equals(path: Path2D): boolean
}

PathKit

PathKit is a toolset for manipulating Path in Skia, supporting quadratic beziers, cubic beziers and conics. The main features are.

Path Operation

.op(path, PathOp)

const pathOne = new Path2D(
  'M8 50H92C96.4183 50 100 53.5817 100 58V142C100 146.418 96.4183 150 92 150H8C3.58172 150 0 146.418 0 142V58C0 53.5817 3.58172 50 8 50Z',
)
const pathTwo = new Path2D(
  '"M58 0H142C146.418 0 150 3.58172 150 8V92C150 96.4183 146.418 100 142 100H58C53.5817 100 50 96.4183 50 92V8C50 3.58172 53.5817 0 58 0Z',
)

pathOne.op(pathTwo, PathOp.Intersect).toSVGString()
// => "M100 100L58 100C53.5817 100 50 96.4183 50 92L50 50L92 50C96.4183 50 100 53.5817 100 58L100 100Z"
  • Union, subtract the op path from the first path
  • Difference, intersect the two paths
  • ReverseDifference, union (inclusive-or) the two paths
  • Intersect, exclusive-or the two paths
  • XOR, subtract the first path from the op path

boolean-operations

Covert FillType in Path

.asWinding()

You can convert fill-rule="evenodd" to fill-rule="nonzero" in SVG. This is useful for OpenType font-related tools, as fill-rule="nonzero" is only supported in OpenType fonts.

SVG fill-rule

const pathCircle = new Path2D(
  'M24.2979 13.6364H129.394V40.9091H24.2979L14.6278 27.2727L24.2979 13.6364ZM21.9592 0C19.0246 0 16.2716 1.42436 14.571 3.82251L1.67756 22.0043C-0.559186 25.1585 -0.559186 29.387 1.67756 32.5411L14.571 50.7227C16.2716 53.1209 19.0246 54.5455 21.9592 54.5455H70.4673V68.1818H16.073C11.0661 68.1818 7.00728 72.2518 7.00728 77.2727V113.636C7.00728 118.657 11.0661 122.727 16.073 122.727H70.4673V150H84.0658V122.727H128.041C130.975 122.727 133.729 121.303 135.429 118.905L148.323 100.723C150.559 97.5686 150.559 93.3405 148.323 90.1864L135.429 72.0045C133.729 69.6064 130.975 68.1818 128.041 68.1818H84.0658V54.5455H133.927C138.934 54.5455 142.993 50.4755 142.993 45.4545V9.09091C142.993 4.07014 138.934 0 133.927 0H21.9592ZM125.702 109.091H20.6058V81.8182H125.702L135.372 95.4545L125.702 109.091Z',
)
pathCircle.setFillType(FillType.EvenOdd)
pathCircle.asWinding().toSVGString()
// => "M24.2979 13.6364L129.394 13.6364L129.394 40.9091L24.2979 40.9091L14.6278 27.2727L24.2979 13.6364ZM21.9592 0C19.0246 0 16.2716 1.42436 14.571 3.82251L1.67756 22.0043C-0.559186 25.1585 -0.559186 29.387 1.67756 32.5411L14.571 50.7227C16.2716 53.1209 19.0246 54.5455 21.9592 54.5455L70.4673 54.5455L70.4673 68.1818L16.073 68.1818C11.0661 68.1818 7.00728 72.2518 7.00728 77.2727L7.00728 113.636C7.00728 118.657 11.0661 122.727 16.073 122.727L70.4673 122.727L70.4673 150L84.0658 150L84.0658 122.727L128.041 122.727C130.975 122.727 133.729 121.303 135.429 118.905L148.323 100.723C150.559 97.5686 150.559 93.3405 148.323 90.1864L135.429 72.0045C133.729 69.6064 130.975 68.1818 128.041 68.1818L84.0658 68.1818L84.0658 54.5455L133.927 54.5455C138.934 54.5455 142.993 50.4755 142.993 45.4545L142.993 9.09091C142.993 4.07014 138.934 0 133.927 0L21.9592 0ZM125.702 109.091L20.6058 109.091L20.6058 81.8182L125.702 81.8182L135.372 95.4545L125.702 109.091Z"

Simplify Path

.simplify()

Set the path to the same non-overlapping contour as the original path area, which means that it can also remove overlapping paths.

SVG with overlapping paths (Left)

const path =
  'M2.933,89.89 L89.005,3.818 Q90.412,2.411 92.249,1.65 Q94.087,0.889 96.076,0.889 Q98.065,0.889 99.903,1.65 Q101.741,2.411 103.147,3.818 L189.22,89.89 Q190.626,91.296 191.387,93.134 Q192.148,94.972 192.148,96.961 Q192.148,98.95 191.387,100.788 Q190.626,102.625 189.219,104.032 Q187.813,105.439 185.975,106.2 Q184.138,106.961 182.148,106.961 Q180.159,106.961 178.322,106.2 Q176.484,105.439 175.077,104.032 L89.005,17.96 L96.076,10.889 L103.147,17.96 L17.075,104.032 Q15.668,105.439 13.831,106.2 Q11.993,106.961 10.004,106.961 Q8.015,106.961 6.177,106.2 Q4.339,105.439 2.933,104.032 Q1.526,102.625 0.765,100.788 Q0.004,98.95 0.004,96.961 Q0.004,94.972 0.765,93.134 Q1.526,91.296 2.933,89.89 Z'

path.simplify().toSVGString()
// => "M89.005 3.818L2.933 89.89Q1.526 91.296 0.765 93.134Q0.004 94.972 0.004 96.961Q0.004 98.95 0.765 100.788Q1.526 102.625 2.933 104.032Q4.339 105.439 6.177 106.2Q8.015 106.961 10.004 106.961Q11.993 106.961 13.831 106.2Q15.668 105.439 17.075 104.032L96.076 25.031L175.077 104.032Q176.484 105.439 178.322 106.2Q180.159 106.961 182.148 106.961Q184.138 106.961 185.975 106.2Q187.813 105.439 189.219 104.032Q190.626 102.625 191.387 100.788Q192.148 98.95 192.148 96.961Q192.148 94.972 191.387 93.134Q190.626 91.296 189.22 89.89L103.147 3.818Q101.741 2.411 99.903 1.65Q98.065 0.889 96.076 0.889Q94.087 0.889 92.249 1.65Q90.412 2.411 89.005 3.818Z"

Example

The tiger.json was serialized from gojs/samples/tiger

node example/anime-girl.js
SVG PNG

CC-BY-SA 3.0 by Niabot

CC-BY-SA 3.0 by Niabot

Building

Build skia from source

You can build this project from source, the system requirements are here: https://skia.org/docs/user/build

# Clone the code:
$ git clone --recurse-submodules https://github.com/Brooooooklyn/canvas.git
$ cd canvas

# Build Skia:
$ node scripts/build-skia.js

# Install NPM packages, build the Node.js addon:
$ npm install -g yarn
$ yarn install --mode=skip-build # Here are modules that are used for benchmarking and are hard to install, you can skip it by specifying `--mode=skip-build`
$ sudo dnf install clang # https://fedora.pkgs.org/34/fedora-x86_64/clang-12.0.0-0.3.rc1.fc34.x86_64.rpm.html
$ yarn build

# All done! Run test cases or examples now:
$ yarn test
$ node example/tiger.js

Pull pre-build skia binary from GitHub

You can pull skia pre-build binaries if you just care the Rust part:

# Clone the code:
$ git clone --recurse-submodules https://github.com/Brooooooklyn/canvas.git
$ cd canvas

# Download Skia binaries:
# It will pull the binaries match the git hash in `./skia` submodule
$ node scripts/release-skia-binary.js --download

# Install NPM packages, build the Node.js addon:
$ npm install -g yarn
$ yarn install --mode=skip-build
$ sudo dnf install clang # https://fedora.pkgs.org/34/fedora-x86_64/clang-12.0.0-0.3.rc1.fc34.x86_64.rpm.html
$ yarn build

# All done! Run test cases or examples now:
$ yarn test
$ node example/tiger.js

More Repositories

1

ts-import-plugin

babel-import-plugin TypeScript Implement
TypeScript
567
star
2

learning-rxjs

Learning RxJS step by step
TypeScript
449
star
3

Image

Image processing library.
Rust
251
star
4

simple-git

Simple and fast git helper functions.
Rust
136
star
5

snappy

Fastest Snappy compression library in Node.js
Rust
119
star
6

uuid

Fastest RFC4122 UUIDs generator for Node.js
JavaScript
91
star
7

Clipboard

Manipulate Clipboard in Node.js via native API.
Rust
73
star
8

jsc-rs

JavaScript core Rust safe binding
Rust
67
star
9

redux-epics-decorator

Dumb decorators for redux & redux-observable & react-redux & redux-actions
TypeScript
59
star
10

blake-hash

Rust Blake hash bindings for Node.js.
Rust
36
star
11

sourcemap-decoder

https://github.com/getsentry/rust-sourcemap node bindings
Rust
33
star
12

woff-build

TTF to WOFF2
Rust
30
star
13

rust-to-nodejs-overhead-benchmark

Benchmark over Node.js binding frameworks in Rust
Rust
25
star
14

vercel-canvas

Sample Canvas App running on Vercel
HTML
23
star
15

ssh

https://github.com/warp-tech/russh Node.js binding
Rust
20
star
16

hns

Node.js http server framework powered by Hyper native binding.
TypeScript
19
star
17

sysinfo

System information
Rust
19
star
18

svg-to-pdf

Convert SVG to PDF
Rust
17
star
19

notify

https://github.com/notify-rs/notify Node.js binding via https://napi.rs
Rust
16
star
20

keyring-node

https://github.com/hwchen/keyring-rs Node.js binding via https://napi.rs. Including 100% compatible node-keytar alternative.
Rust
15
star
21

rxjs-webpack-treeshaking-example

Example for treeshaking
JavaScript
13
star
22

typescript-monorepo

Template project for typescript monorepo
JavaScript
12
star
23

lzma

https://docs.rs/lzma-rs binding to Node.js via https://napi.rs
Rust
10
star
24

wechat-api

JavaScript
9
star
25

ada-url

https://github.com/ada-url/ada Rust and Node.js binding
C++
9
star
26

Learning-Rx

ๅœจๅฎž่ทตไธญๅญฆไน  Rx
9
star
27

rxjs-in-react-demo

RxJS ๅœจ React ไธญ็š„ๅบ”็”จ็คบไพ‹้กน็›ฎ
TypeScript
8
star
28

html-parser

Parse html string to AST.
JavaScript
8
star
29

crypto-wasm

rust-crypto wasm build
JavaScript
8
star
30

dog

Watch dog for Node.js development
Rust
8
star
31

vuex-rx

RxJS Plugins for vuex
TypeScript
7
star
32

wx-fetch

ๅพฎไฟกๅฐๅบ”็”จ็š„ fetch polyfill
JavaScript
7
star
33

datafusion-node

Apache DataFusion Node.js binding
Rust
6
star
34

WechatBot

TypeScript
5
star
35

summer-page

JavaScript
5
star
36

node-ed25519

JavaScript
5
star
37

pnpm-duplicate-packages

Reproduction for duplicate packages bundled in pnpm project
JavaScript
5
star
38

node-threadsafe-function-loom

Play the Loom
Rust
4
star
39

Build-Promise

Build your own Promise
TypeScript
4
star
40

puppeteer-screencast-to-video

Puppeteer screencast frames to video
Rust
4
star
41

resume

Resume
TypeScript
4
star
42

Rust-OSS

Aliyun OSS utils
3
star
43

xcx-fucker

3
star
44

dart-rs

Dart rust binding library
Rust
3
star
45

blog

blog
TypeScript
3
star
46

css-minify-comparation

JavaScript
3
star
47

napi-rs-cli-testing

Testing package for @napi-rs/cli
JavaScript
2
star
48

zig-linker-issue

JavaScript
2
star
49

wechat-dingding-cryptor

Wechat/ Dingding cryptor
JavaScript
1
star
50

TypeScript-Webworker-Plugin

Generate webworker codes
1
star
51

next.js-remix-benchmark

TypeScript
1
star
52

ts-import-plugin-example

ts-import-plugin example project with create-react-app
JavaScript
1
star
53

node-crypto

rust-crypto bindings to nodejs
Rust
1
star
54

Cassanova

Teambition & Gitlab & Jenkins Webhook Bridge
TypeScript
1
star
55

Appelsin-Scroll

A cross browers custom scroll bar
JavaScript
1
star
56

frontend-mesh

Connect, control, and observe frontend services. Inspired by Istio.
1
star
57

Learning-FSharp

1
star
58

yarn-bug

1
star
59

node-ref-object-leak

JavaScript
1
star
60

angular_with_typescript

JavaScript
1
star
61

nestjs-vercel

1
star
62

traceurs

Fetch your data, eagerly.
TypeScript
1
star
63

vue-worker-rendering

Rendering HTML in service worker
JavaScript
1
star
64

nodejs-worker-thread-constructor-attribute-issue

C++
1
star
65

typescript-example-projects

JavaScript
1
star
66

nft-pnpm

@vercel/nft with pnpm
JavaScript
1
star
67

intelligent-link

Generate universal link or urlschema or App download url
1
star