https://windowjs.org is a very similar concept -- it wraps Skia and exposes it as the Canvas API, but also embeds v8 for a very small runtime instead of using Node.
It was my first open-source project, released about 3 years ago.
I had plans to also expose WebGL, audio, etc and make it a viable platform for Javascript-based games on desktop.
Life and other projects happened instead, and development was discontinued. Happy to see this project also making Canvas accessible outside the browser!
> but also embeds v8 for a very small runtime instead of using Node.
By how much does embedding just V8 instead of using Node.js decrease the binary size? Node.js uses V8, does most of Node's binary size come from its runtime and not V8?
I tried browsing the website and GitHub repo to find how many kBs or MBs typically is a Window.js binary, but didn't find an answer.
Wow, 8 MiB, that's impressively small IMO. I expected a program that embeds V8 (let alone also having windowing and Skia) to have a binary size much bigger. something closer to Node's or Deno's binary size 40MiB+.
Using the Window.js approach of embedding V8, instead of using a windowing library + a Skia Canvas library in Node.js ─really makes sense if you would like to keep the binary size minimally small and only pay for the libraries/modules you actually pull in and use, instead of having them statically linked in the binary by default. For example, an offline app/game that doesn't use the network doesn't need to have a "net" or "http" module in the binary. These may be bad examples though, I don't know how much do those Node runtime modules constitute out of the final Node binary, it may be an insignificant fraction.
At Soundslice, we have a custom sheet-music-rendering graphics library in frontend JavaScript/Canvas.
We also need to generate vector PDFs serverside — so we use a node library that speaks the HTML Canvas API and can generate PDFs. This way the result is exactly the same as the rendered sheet music in the web browser. Nice!
The upshot is: this kind of library allows for code reuse in non-browser contexts.
On a related note, I once used puppeteer and headless chrome in a docker image to generate PDF manuals from our web page documentation using the print to PDF feature of headless chrome.
I am not sure if it would be viable to use for thousands of PDF generation per minute but it worked great. I wasted a lot of time trying to find a good lib for HTML->PDF and they all kinda sucked in different ways. The only downside is that the chrome PDF api doesn't have a way to generate a table of contents with page numbers.
We do indeed generate vector PDFs, not embedded bitmaps.
Our graphics engine works with Canvas API instructions — like "draw a line from point (A,B) to (C,D)." This API is small enough and low-level enough that it can also generate pristine vector output.
That's in fact one of the features of Skia Canvas (vector output in PDF and SVG).
There isn’t really anything commonly “native” as powerful as skia for 2D drawing and a big part of skia is using the native graphics effectively, this is why it exists in the first place for browser engines. Besides having one target API is a benefit.
In addition to the portability issue, CoreGraphics does not compete directly with Skia or Direct2D for that matter (it’s mostly CPU based). If you combine it with CoreAnimation you get something of a quasi superset. Direct2D is +/-, but it also doesn’t have some higher level features like PDF and SVG backends, and it doesn’t have the software rendering capabilities of skia by design.
Of course there are 2D drawing libraries on the major platforms (and CoreGraphics and Direct2D are very different, there’s also GDI+), but it was a weird question to start: what’s the use of a portable library. But furthermore, skia is not just a portability wrapper.
I love Cairo, but it’s just not in the same feature or performance ballpark as skia. There’s a reason even Firefox adopted skia.
Skia is like the V8 of 2D drawing libraries, just a ton of continual investment into optimization. I used to work on cairo and pixman, it’s great, very straightforward but not finely tuned for performance on modern hardware.
It's portable? What other GPU accelerated 2D rendering library is?
One the one hand this is lower-level. On the other hand, I've used puppeteer as my 2D graphics library. I get Canvas 2D, and WebGL, and WebGPU, and all of HTML/CSS (so Text with effects, background images, CSS transforms, etc). I get image and video loading. I can then use the screenshot functionality to make an image of whatever I put together. It's overkill, but for my use case I don't care. It works, it's cross platform, it solves my problem.
Being able to run the same code on multiple platforms is a big bonus. It’s also a very common 2D drawing API. iOS has a native version, Android has a native version, all with more or less the same drawing operations. Leveraging them can pay dividends.
Server-side rendering of a track onto map tiles, enriched with other information like PoIs, distance marks, colorizing the track according to the speed, and so on, in order to then send it as a summary email to the customer.
Skia is the most modern library if you want to render shapes onto an image/surface.
According to the example `import {Window} from 'skia-canvas'` you can also use it to draw onto a spawned window on a desktop.
I used it (different lib, same purpose) at a previous startup, shared web whiteboard, to save the whiteboard to pdf/image.
We had a rendering engine backed by canvas, so it was easy to just redirect it to this to get the file output. (not everything was visible on the user's screen all the time so couldn't just save from the client)
Yes I would like to know this too. There are various people saying they use this (or things like it) but nobody is saying what they are actually doing with it.
I'd be surprised if you could point out a platform that has better APIs for drawing 2D graphics than Canvas offers. Skia outperforms Direct2D by a little bit and outperforms Cairo by a lot. It is optimized to a high degree and has GPU-accelerated backends.
Skia ships with a WASM build that supports node, called CanvasKit [1], whereas this module is Rust bindings? I’d be keen to know what the pros and cons of each approach are.
Note that it's not so much "rust bindings" as a pre-compiled node binary (that happens to be generated off of a mixed Rust/C++ codebase). So that's really the main difference: WASM needs a separate runtime, whereas a node binary doesn't.
Huh, it's more than just a rendering API: "can render to windows using an OS-native graphics pipeline and provides a browser-like UI event framework". They could add wgpu to get WebGPU support and ANGLE to get WebGL support.
Skia is a widely used library (off the top of my head, Chrome, Firefox, and LibreOffice use it) that supports lots of rendering targets, including WebGPU and ANGLE.
I don't mean WebGPU and WebGL as Skia backends. I mean WebGPU and WebGL as APIs that this library could expose to users in addition to the HTML canvas API.
That's very similar to what Qt already provides (QPainter, for raw image/windows, more sophisticated widgets with event support. Further- and this is something I've been looking for a while- they have a graphicsview that allows resolution-independent rendering of millions of objects, with events, in an larger-than-window graphics plane.
Kind of strange to call it browserless as though Skia requires one to begin with. Been using the .NET Skia library in CLI and desktop applications for a while.
They're not saying Skia itself is browserless; they're saying that "Skia Canvas" is a non-browser-based implementation of what people would normally call the browser Canvas API.
Canvas is one of the easiest browser APIs to implement because most of it maps 1:1 to PostScript-style 2D APIs.
It was created by Apple as a wrapper for CoreGraphics back around Mac OS X Tiger 10.4. (IIRC, Canvas even debuted in Apple's Dashboard widgets rather than Safari? I'm not 100% sure.)
I remember writing my own Canvas implementation around 2008-2009 using JavaScriptCore and CoreGraphics. It took maybe two days. I used it for a video application where JS plugins needed to render 2D graphics but I couldn't provide a browser engine because that would have been too slow and unpredictable for render functions that get called in the video render loop.
I'm not claiming I implemented everything from scratch. Since it was a Mac OS X Cocoa app, it would just call the APIs available on that platform, like Apple's original Canvas implementation in WebKit.
Also since this was a video application running custom scripts written specifically for that host app, there wasn't a big worry about exact compatibility with any other Canvas-using code.
Anyway, here's the old code that doesn't look like there's anything interesting going on:
My previous startup Vidmaker created a browser-based collaborative video editor (think Google Docs for iMovie) in 2014 which did live preview in the browser and final render server-side. To use the same typescript-based "engine" for both we re-implemented most of the media browser APIs in NodeJS: 2d and 3d canvas, WebAudio, image APIs, something akin to HTML Video, etc. It worked amazingly well.
We got acqui-hired in 2015 and the product was shut down, but in hindsight I wish we'd open sourced at least those libraries.
Automattic acquired LearnBoost who employed TJ Holowaychuk, the original creator of node-canvas (and also Express and tons else). Automattic has not been involved in any way since at least 2017 when I joined node-canvas.
That's not necessarily better or worse, it's just potentially helpful if you've got lots of Canvas-based code lying around you want to run outside a browser environment.
Super excited to see this, it'll make writing web-compatible graphics that can also run standalone so much nicer (and without needing a compile things from source using a second scripting language to generate the code needed to compile the library for the first scripting language).
It'll also be interesting to see how well this slots into things like VS Code for live-rendering canvas code (or code that has a translation layer for running on a web canvas)
In this context I would bet money that it's an implementation of the Canvas API that uses Skia underneath to do actual rasterization. It turns out (if you do a little research) that Firefox and Chrome have at various points in time used Skia as a backend for their own implementations of the Canvas API, too.
Reading the documentation, it seems to implement most (all?) of the Web Canvas API[1], and in certain areas extend the API in some exciting (well, exciting to me[2]) ways. The key point is that this can be used in the backend, far beyond the browser environment?
[2] The things that jump out to me as "exciting extensions" beyond the Api include: multiline text and text decoration (underlines!)[3]; perspective effects[4]; and Path2D boolean and filter operations[5].
https://windowjs.org is a very similar concept -- it wraps Skia and exposes it as the Canvas API, but also embeds v8 for a very small runtime instead of using Node.
It was my first open-source project, released about 3 years ago.
I had plans to also expose WebGL, audio, etc and make it a viable platform for Javascript-based games on desktop.
Life and other projects happened instead, and development was discontinued. Happy to see this project also making Canvas accessible outside the browser!
Amazing project!
> but also embeds v8 for a very small runtime instead of using Node.
By how much does embedding just V8 instead of using Node.js decrease the binary size? Node.js uses V8, does most of Node's binary size come from its runtime and not V8?
I tried browsing the website and GitHub repo to find how many kBs or MBs typically is a Window.js binary, but didn't find an answer.
See the binary sizes in the (obsolete) releases page:
https://github.com/windowjs/windowjs/releases
About 8 MiB in the end. Note that these builds have a binary trimmed by UPX.
Wow, 8 MiB, that's impressively small IMO. I expected a program that embeds V8 (let alone also having windowing and Skia) to have a binary size much bigger. something closer to Node's or Deno's binary size 40MiB+.
Using the Window.js approach of embedding V8, instead of using a windowing library + a Skia Canvas library in Node.js ─really makes sense if you would like to keep the binary size minimally small and only pay for the libraries/modules you actually pull in and use, instead of having them statically linked in the binary by default. For example, an offline app/game that doesn't use the network doesn't need to have a "net" or "http" module in the binary. These may be bad examples though, I don't know how much do those Node runtime modules constitute out of the final Node binary, it may be an insignificant fraction.
That’s pretty cool! But v8 alone kind of sucks unless you specifically want isolation. Sometimes you just want to make a network request, you know?
That makes total sense in many applications, including games.
The idea was to have TCP sockets and Websockets to enable that.
Basically, have the same APIs you're familiar with in a Browser, but in a much smaller package that you can ship independently of the Browser.
(this is very similar to Electron)
> Sometimes you just want to make a network request
In a drawing tool / library?
That's just asking for trouble. By spec, SVG allows for XXE shenanigans. I'd rather not worry that any image file I process might exfiltrate my data.
Out of curiosity, what's the use for such library? If you are on a desktop surely there is a better native library to draw shapes, no?
At Soundslice, we have a custom sheet-music-rendering graphics library in frontend JavaScript/Canvas.
We also need to generate vector PDFs serverside — so we use a node library that speaks the HTML Canvas API and can generate PDFs. This way the result is exactly the same as the rendered sheet music in the web browser. Nice!
The upshot is: this kind of library allows for code reuse in non-browser contexts.
I have a soundslice account that I use infrequently. It's pretty nice. Thanks for building it and making it available.
On a related note, I once used puppeteer and headless chrome in a docker image to generate PDF manuals from our web page documentation using the print to PDF feature of headless chrome.
I am not sure if it would be viable to use for thousands of PDF generation per minute but it worked great. I wasted a lot of time trying to find a good lib for HTML->PDF and they all kinda sucked in different ways. The only downside is that the chrome PDF api doesn't have a way to generate a table of contents with page numbers.
Is Canvas really suitable for PDFs ? Afaik it's immediate mode bitmap graphics - so PDFs would just be embedded bitmaps ?
We do indeed generate vector PDFs, not embedded bitmaps.
Our graphics engine works with Canvas API instructions — like "draw a line from point (A,B) to (C,D)." This API is small enough and low-level enough that it can also generate pristine vector output.
That's in fact one of the features of Skia Canvas (vector output in PDF and SVG).
Ah nice didn't know it had a PDF backend - that sounds perfect for this use case.
Curious: What other libs are you using for the PDF generation?
There isn’t really anything commonly “native” as powerful as skia for 2D drawing and a big part of skia is using the native graphics effectively, this is why it exists in the first place for browser engines. Besides having one target API is a benefit.
Direct2D, CoreGraphics,...
In addition to the portability issue, CoreGraphics does not compete directly with Skia or Direct2D for that matter (it’s mostly CPU based). If you combine it with CoreAnimation you get something of a quasi superset. Direct2D is +/-, but it also doesn’t have some higher level features like PDF and SVG backends, and it doesn’t have the software rendering capabilities of skia by design.
Of course there are 2D drawing libraries on the major platforms (and CoreGraphics and Direct2D are very different, there’s also GDI+), but it was a weird question to start: what’s the use of a portable library. But furthermore, skia is not just a portability wrapper.
> There isn’t really anything commonly “native” as powerful as skia for 2D drawing
Cairo?
I love Cairo, but it’s just not in the same feature or performance ballpark as skia. There’s a reason even Firefox adopted skia. Skia is like the V8 of 2D drawing libraries, just a ton of continual investment into optimization. I used to work on cairo and pixman, it’s great, very straightforward but not finely tuned for performance on modern hardware.
We use it for excalidraw to generate opengraph previews of scenes when you send a link to somebody.
That's so interesting! So it's a great way to display og:images for canvas items
It's portable? What other GPU accelerated 2D rendering library is?
One the one hand this is lower-level. On the other hand, I've used puppeteer as my 2D graphics library. I get Canvas 2D, and WebGL, and WebGPU, and all of HTML/CSS (so Text with effects, background images, CSS transforms, etc). I get image and video loading. I can then use the screenshot functionality to make an image of whatever I put together. It's overkill, but for my use case I don't care. It works, it's cross platform, it solves my problem.
Being able to run the same code on multiple platforms is a big bonus. It’s also a very common 2D drawing API. iOS has a native version, Android has a native version, all with more or less the same drawing operations. Leveraging them can pay dividends.
Server-side rendering of a track onto map tiles, enriched with other information like PoIs, distance marks, colorizing the track according to the speed, and so on, in order to then send it as a summary email to the customer.
Skia is the most modern library if you want to render shapes onto an image/surface.
According to the example `import {Window} from 'skia-canvas'` you can also use it to draw onto a spawned window on a desktop.
I used it (different lib, same purpose) at a previous startup, shared web whiteboard, to save the whiteboard to pdf/image.
We had a rendering engine backed by canvas, so it was easy to just redirect it to this to get the file output. (not everything was visible on the user's screen all the time so couldn't just save from the client)
Yes I would like to know this too. There are various people saying they use this (or things like it) but nobody is saying what they are actually doing with it.
I'd be surprised if you could point out a platform that has better APIs for drawing 2D graphics than Canvas offers. Skia outperforms Direct2D by a little bit and outperforms Cairo by a lot. It is optimized to a high degree and has GPU-accelerated backends.
I would imagine that you could use it to write automated tests for Canvas code (maybe charts?) running in Node, e.g., jest with snapshots.
Yes, I'm doing exactly this with https://www.npmjs.com/package/jest-image-snapshot
Seems you could compile desktop apps to Web Assembly and distribute them without using some monstrosity like Electron.
Skia ships with a WASM build that supports node, called CanvasKit [1], whereas this module is Rust bindings? I’d be keen to know what the pros and cons of each approach are.
1. https://www.npmjs.com/package/canvaskit-wasm
Note that it's not so much "rust bindings" as a pre-compiled node binary (that happens to be generated off of a mixed Rust/C++ codebase). So that's really the main difference: WASM needs a separate runtime, whereas a node binary doesn't.
> WASM needs a separate runtime, whereas a node binary doesn't.
FWIW, node ships with a WASM runtime. See https://nodejs.org/en/learn/getting-started/nodejs-with-weba...
The more accurate way to say that is: V8 is also a WASM runtime.
I'm using this currently with Deno for Advent of Code, if I need a simple window with graphics. (There's a window-example[1])
It can be added by doing "deno add npm:skia-canvas", adding "nodeModulesDir: auto" to the deno.json, and doing a "deno install --allow-scripts".
[1] https://skia-canvas.org/#rendering-to-a-window
Is this like a wrapper for the Rust crate?
https://rust-skia.github.io/doc/skia_safe/canvas/struct.Canv...
I understand that a Skia Canvas should work with almost any programming language with just bindings [1]. I don't see anything in particular with Node.
[1] https://www.swig.org/
Looks like it
yes, generated by napi-rs
If you're interested in node-compatible canvas implementations:
- canvaskit-wasm from the skia project - I don't think it's gpu-accelerated: https://github.com/google/skia/tree/main/modules/canvaskit/n...
- @napi-rs/canvas - This is the fastest binding: https://github.com/Brooooooklyn/canvas?tab=readme-ov-file#pe...
- node-canvas - Uses Cairo instead of Skia: https://github.com/Automattic/node-canvas
I've been waiting for this for a long time. Previous attempts to do similar things required installing Node-Gyp, which was a nightmare.
Huh, it's more than just a rendering API: "can render to windows using an OS-native graphics pipeline and provides a browser-like UI event framework". They could add wgpu to get WebGPU support and ANGLE to get WebGL support.
Skia is a widely used library (off the top of my head, Chrome, Firefox, and LibreOffice use it) that supports lots of rendering targets, including WebGPU and ANGLE.
I don't mean WebGPU and WebGL as Skia backends. I mean WebGPU and WebGL as APIs that this library could expose to users in addition to the HTML canvas API.
That's very similar to what Qt already provides (QPainter, for raw image/windows, more sophisticated widgets with event support. Further- and this is something I've been looking for a while- they have a graphicsview that allows resolution-independent rendering of millions of objects, with events, in an larger-than-window graphics plane.
I made something similar for node/web, not so complete, but good. It can also create windows in the OS using SDL
Check the docs and examples
https://github.com/pedroth/tela.js
I use it in https://malmal.io for rendering the drawn tiles on my server, it works really well.
I wonder if this would help to render MapLibre server-side. To provide map thumbnails.
Are there plans for a C API?
The missing C API for Skia is most likely why Cairo is still alive.
Impeller - Flutter’s rendering library that replaces Skia on iOS and Android - is working on a C API: https://github.com/flutter/engine/blob/main/impeller/toolkit...
I did a little work on a C API for Skia a long time ago. We didn't have a good use case at the time, so the project was dropped.
Kind of strange to call it browserless as though Skia requires one to begin with. Been using the .NET Skia library in CLI and desktop applications for a while.
They're not saying Skia itself is browserless; they're saying that "Skia Canvas" is a non-browser-based implementation of what people would normally call the browser Canvas API.
Canvas is one of the easiest browser APIs to implement because most of it maps 1:1 to PostScript-style 2D APIs.
It was created by Apple as a wrapper for CoreGraphics back around Mac OS X Tiger 10.4. (IIRC, Canvas even debuted in Apple's Dashboard widgets rather than Safari? I'm not 100% sure.)
I remember writing my own Canvas implementation around 2008-2009 using JavaScriptCore and CoreGraphics. It took maybe two days. I used it for a video application where JS plugins needed to render 2D graphics but I couldn't provide a browser engine because that would have been too slow and unpredictable for render functions that get called in the video render loop.
I'd love to learn more about how your 2 day implementation did fillText (incl full unicode rendering, proper text shaping etc)
I'm not claiming I implemented everything from scratch. Since it was a Mac OS X Cocoa app, it would just call the APIs available on that platform, like Apple's original Canvas implementation in WebKit.
Also since this was a video application running custom scripts written specifically for that host app, there wasn't a big worry about exact compatibility with any other Canvas-using code.
Anyway, here's the old code that doesn't look like there's anything interesting going on:
https://github.com/pojala/lacqit/blob/main/Lacqit/LQJSBridge...
Looks like I misremembered — it's using Cairo rather than CoreGraphics. I don't remember why that was.
Automattic created node-canvas a long time ago which was based around Cairo.
My previous startup Vidmaker created a browser-based collaborative video editor (think Google Docs for iMovie) in 2014 which did live preview in the browser and final render server-side. To use the same typescript-based "engine" for both we re-implemented most of the media browser APIs in NodeJS: 2d and 3d canvas, WebAudio, image APIs, something akin to HTML Video, etc. It worked amazingly well.
We got acqui-hired in 2015 and the product was shut down, but in hindsight I wish we'd open sourced at least those libraries.
Automattic acquired LearnBoost who employed TJ Holowaychuk, the original creator of node-canvas (and also Express and tons else). Automattic has not been involved in any way since at least 2017 when I joined node-canvas.
Yeah that's been around for a while - I used node-canvas to generate frames to render to mp4 for my HTML canvas animation site:
https://www.superanimo.com
Looks like it would have been a lot easier if this existed back then. (haven't worked on the site for 5+ years)
It's the "html canvas drawing api" that "browserless" refers to
Node is a browserless JS engine. I don’t see why saying browserless is weird.
I don’t think anyone ever thought Node was a browser thing though
Thinking Node is related to the browser is not uncommon. They're both javascript and javascript means "browser" to a lot of (if not most) people.
Is not only that, Node was born from the V8 implementation in Google Chrome, not the other way around.
So that means everyone pretends node is a browser thing? Why stop there? Why not call every JS library that happens to run in Node “browserless”?
That answers the original question "Thinking Node is related to the browser is not uncommon." where Node and the browser ARE related.
Anyone know what the difference between this and `@napi-rs/canvas` is?
They seem similar?
Having skia power in nodejs is a cool idea. Thanks!
How is it different from jimp?
https://jimp-dev.github.io/jimp/
Maybe there are some killer features?
The only similarity between the two is that they deal with images.
That library only resizes and does other basic pixel level operations.
Skia is for drawing vector graphics.
> Skia is for drawing vector graphics.
What do you mean by "vector graphics"?
Both Canvas and ImageData work only with pixels.
Skia’s api is focused around using postscript-like commands such as “move to” “line to”, etc to produce vectors that it rasterizes.
It implements the HTML Canvas API.
That's not necessarily better or worse, it's just potentially helpful if you've got lots of Canvas-based code lying around you want to run outside a browser environment.
Could this be consumed by Konva js for SSR ?
Yes, it may require some monkey-patching of Konva right now. But I was able to do that. Drop GitHub issue if you are interested to discuss it.
Thanks for the response. Would you suggest this lib for Konva ?
Would this work together with something like react-three-fiber to render 3D scenes on the server?
Super excited to see this, it'll make writing web-compatible graphics that can also run standalone so much nicer (and without needing a compile things from source using a second scripting language to generate the code needed to compile the library for the first scripting language).
It'll also be interesting to see how well this slots into things like VS Code for live-rendering canvas code (or code that has a translation layer for running on a web canvas)
What does "based on Skia" actually mean?
Is it a fork of Skia?
Does it depend on Skia?
Did somebody look at a design diagram of Skia and redo the whole thing from scratch?
Did they read about Skia in a blogpost and get the gist of it?
In this context I would bet money that it's an implementation of the Canvas API that uses Skia underneath to do actual rasterization. It turns out (if you do a little research) that Firefox and Chrome have at various points in time used Skia as a backend for their own implementations of the Canvas API, too.
> Firefox and Chrome have at various points in time used Skia as a backend for their own implementations of the Canvas API, too.
Skia is the graphics engine used by Chrome and Android to render everything: https://skia.org/
Reading the documentation, it seems to implement most (all?) of the Web Canvas API[1], and in certain areas extend the API in some exciting (well, exciting to me[2]) ways. The key point is that this can be used in the backend, far beyond the browser environment?
[1] https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
[2] The things that jump out to me as "exciting extensions" beyond the Api include: multiline text and text decoration (underlines!)[3]; perspective effects[4]; and Path2D boolean and filter operations[5].
[3] https://skia-canvas.org/api/context#textdecoration
[4] - https://skia-canvas.org/api/context#createprojection
[5] - https://skia-canvas.org/api/path2d
> Does it depend on Skia?
This one