Release Notes
📦 ⟩ v4.0.0 ⟩ Oct 4, 2026
New Features
Rendering
- Canvas and Image objects now support the
usingkeyword (or.dispose()and.release()methods) to synchronously release memory after use rather than waiting for GC getContext()andnewPage()now accept awillReadFrequentlyhint thatgetImageData()will be called repeatedly (so the bitmap can be retained for the lifetime of the context). The current value can be read back throughgetContextAttributes().
GUI
- Window objects now support PointerEvent event types and provide
requestAnimationFrame()andcancelAnimationFrame()methods for scheduling updates synced to display refresh - Rendering now always uses the current monitor’s pixel density, so dragging a window between screens with different scale factors should stay sharp
Wide-gamut Color
- The canvas’s color space can now be selected via
getContext("2d", {colorSpace})and set to either"srgb"(the default) or"display-p3". The setting will be inherited by any subsequent pages but can be overridden by passing acolorSpaceoption tonewPage(). - The
getImageData()function also takes acolorSpaceoption, selecting how the raw pixels it returns will be clamped (by default it uses the context’s color space setting). Likewise, the ImageData constructor andcreateImageData()now also take an optionalcolorSpacearg. - Bitmap exports (and
toSharp()) will use the context’s color space and embed a matching ICC profile, PDFs will do the same for images drawn onto the canvas but not path drawing, and SVGs have no wide-gamut support. - Color strings can now use full CSS Color 4 syntax everywhere a color is accepted (
fillStyle,strokeStyle,shadowColor, gradient stops,matte, anddrop-shadow()filters). This includes support foroklch(),oklab(),lab(),lch(), andcolor(display-p3 …), but relative color syntax likergb(from …)is not currently supported. - CanvasGradient objects now have
colorInterpolationMethod,hueInterpolationMethod, andpremultipliedAlphaproperties controlling how colors are blended between stops - Windows on macOS now use Display P3. Vulkan windows remain sRGB, though this may change in the future
Typography
- Text in PDF exports is now selectable and searchable (thanks to @Mythie’s PR #295 for the proof of concept)
- The new
fontSmoothingproperty can be set tofalseto render un-antialiased text that is aligned to the pixel grid. By default, fonts use greyscale AA and are sub-pixel positioned - The
fontSynthesisproperty allows you to control whether a fake bold and oblique should be generated when a font family lacks a real one. Note that this now defaults to true, matching browser behavior but breaking from previous Skia Canvas defaults - The
fontKerningproperty controls whether the OpenTypekernfeature is enabled:"auto"/"normal"(the default) leaves it on,"none"turns it off. - The
letterSpacingandwordSpacingproperties now acceptemandremunits.emis relative to the current font size andremuses a 16px root size. - Variable fonts that support optical sizing (the
opszaxis) are now automatically sized to the currentfontsize (matching browser behavior). - The
fontattribute now accepts an oblique angle (e.g.,"oblique 14deg 24px Recursive") and will set theslntaxis (and suppress italic letterforms) for variable fonts that support it. - Variable fonts can now be selected via named instances using the
fontInstanceproperty - The new
fontVariationSettingsproperty takes a CSSfont-variation-settingsstring (e.g.,'"wght" 625, "opsz" 48') or{tag:value}object for low-level control over arbitrary variable-font axes. Set it to"normal"to clear. - The new
fontFeatureSettingsproperty takes a CSSfont-feature-settingsstring (e.g.,'"liga" 0, "tnum" 1, "ss01"') or{tag:value}object for directly enabling or disabling OpenType features by tag. On conflicting tags it overridesfontVariantandfontKerning. Set it to"normal"to clear. - The FontLibrary’s
use()andfamily()methods now include additional fields describing the capabilities of user-loaded or system-installed fonts:variable: A boolean flag identifying that this is a variable fontinstances: A mapping of variable font instance names (e.g.,"Display Semibold","Subhead Light") to their corresponding variation axis settings. Names can be passed to thefontInstanceproperty and settings tofontVariationSettings.variations: Variable font axes represented as an object with supported axis tags (e.g.,wdth,slnt) mapping to{min, max, default, label}summariesfeatures: OpenType features represented as an object with supported feature tags (e.g.,liga,smcp) mapping to{type, label}summariesreplaces&replacedBy: File paths showing which fonts were kept or discarded when multiple faces with the same family and style were passed touse()
- The
textRenderingproperty is now supported, offering control over how text is rasterized:"auto": use browser-standard glyph placement (enabled by default)"geometricPrecision": use sub-pixel positioning (lining up exactly withoutlineText()and vector output)"optimizeLegibility": enable CoreText’s font smoothing, DirectWrite’s grid fitting, or FreeType’s light autohinting"optimizeSpeed": snap glyphs to whole pixels so GPU renders can reuse cached per-glyph bitmaps
Imagery
- Image objects can now load PDF documents (via
loadImage(),new Image(), or thesrcsetter) and render them as resolution-independent vectors.- Single pages can be loaded via
loadImage()which now accepts a 1-basedpagenumber option, so you can draw PDFs to other Canvas objects. - Whole documents can be loaded via
loadCanvas(), which adds each PDF page as a content-sized entry in the Canvas’spagesattribute allowing you to draw annotations onto them.
- Single pages can be loaded via
- SVG images now apply CSS rules contained in
<style>elements and support most CSS selectors (#276). Custom properties have only basic support:var()references only resolve against:rootor inlinestyledeclarations, and chained definitions like--foo: var(--bar)are not currently handled. - ImageData now supports
Float16Array(on Node 23+) for half-float pixel formats andgetImageData()will return one when a float-basedcolorTypeis requested (#271). - When exporting in
"raw"format, you can now include apremultipliedoption to leave the pixel values in their GPU-native state (and avoid a potentially lossy conversion to un-premultiplied at low alpha). - PNG exports now default to using no adaptive filtering while encoding (since this is both faster and produces smaller output files for the types of flat-shaded graphics produced by canvas drawing). You can opt back into using adaptive filtering by setting the
filtersexport option to"all", or try using"auto"which tests your canvas content for compressibility then selects the best mode to minimize file size.
Paths
- Path2D objects can now be measured by arc length: the
lengthproperty reports the total length of all contours, andpositionAt(),tangentAt(), andnormalAt()return the location (as{x, y}) and tangent/normal angles (in radians) at the requested distance along the path. - The new
contoursaccessor splits a path into an array of single-contour Path2D objects - The new
slice()method returns a Path2D that runs between the specified start- and stop-distances along the original path, along with an optional flag to invert the selected region.
Performance Optimizations
Drawing throughput
- Vector drawing operations are now queued on the JS side in a binary “drawlist” and sent to Rust in batches rather than making one bridged call per verb. In addition, paths are now constructed lazily via Skia’s
PathBuilder, leading to substantially faster rendering on verb-heavy workloads measureText()is substantially faster, especially for long strings and text-wrapped layouts
GPU rendering & exports
- All GPU work (offscreen exports as well as windows) now shares a dedicated render thread instead of giving every worker thread its own GPU context. This removes an unnecessary GPU⇄CPU roundtrip and duplicated per-thread resource caches, making exports meaningfully faster and lowering peak memory
- Text drawn with a shadow is now rasterized once into a
Pictureand blurred as a unit. Previously, each run of multiple lines, fallback fonts, or decorations triggered redundant blur passes. - PNG encoding is now faster due to disabling adaptive row filters and can be further tuned by lowering the
qualityargument to scale zlib compression effort getImageData()now writes directly into the returned buffer instead of allocating an intermediate copy.
Memory & resource consumption
- Native and GPU memory held by live Canvas and Image references are now reported to V8, so garbage collection has an accurate picture of memory pressure (instead of viewing these objects as effectively ‘free’). As a result, memory growth in canvas-creating loops is now bounded (fixes #284), though you’ll still get an even smaller footprint with explicit disposal via
using/.dispose()especially within synchronous allocation loops - Adding a missing autorelease pool in the Metal exporter removed a native-buffer memory leak on macOS, and all platforms now have a background thread that reclaims GPU memory on idle
- GPU resources are now evicted based on actual last-use time rather than on a fixed interval regardless of activity.
- On Linux/glibc, a background thread now calls
malloc_trimto return freed heap pages to the OS after an idle period (tunable via theSKIA_CANVAS_TRIMenvironment variable)
Frame pacing
- On-screen animations now run at a stable frame rate locked to the display’s refresh by syncing to a hardware vblank (using CVDisplayLink on macOS, DwmFlush on Windows, DRM vblank on X11, and redraw callbacks on Wayland). In addition, non-animated windows (i.e., no
frameordrawlistener) will consume near-zero CPU as they are only redrawn in response to UI events. - Pointer and mouse input is now coalesced to one sample per display refresh, so windows no longer redraw on every raw input event (most relevant to X11 and Wayland which report mouse events at whatever speed the mouse samples at; potentially hundreds of Hz)
Render caching
- Drawing a Canvas or vector Image onto a canvas now reuses a cached raster of the source rather than re-rendering on every call (but only when the canvas is exported to a bitmap).
- Repeatedly exporting a canvas (with accumulated/layered drawing in between) now uses a cached snapshot so only the newly added drawing layers need to be rendered each time.
- The cached rasters share a single 128 MB budget which can be overridden by setting the
SKIA_CANVAS_CACHEenvironment variable to a number of MBs.
Misc. Improvements
- The postinstall script for downloading precompiled native binaries has been replaced by platform-specific
optionalDependenciesin the@skia-canvas/*namespace. Installs now work with--ignore-scriptsenabled (#275) and behind firewalls that only mirror the npm registry (#287). As a fallback, the binaries can still be downloaded from the GitHub release via an explicitnpm run buildornpm run download. getContextAttributes()now also includes the Context’swidth&height, which is the only way to get page-specific dimensions in a multi-page canvas (since the Canvas’s size corresponds to the final page).- The
points()method now takes a samplingmodeargument: the default"even"fits the step to each contour so both endpoints are present, while the new"exact"mode samples at precise multiples of the requested step. loadImage()andloadImageData()now take atimeoutrequest option, rejecting the Promise if the request stalls. See also: theAbortSignal.timeout()signaloption.- Upgraded Skia to milestone 153 (via
skia-safe0.99.0) andwinitto 0.30.13 - Now backward compatible with Sharp ≥0.33.5
- Dropped the
string-split-byruntime dependency, reducing the total number of direct and transitive dependencies to 6.
Bugfixes
Canvas
- The Canvas constructor no longer ignores the
{gpu:false}option - The App singleton now initializes lazily, no longer keeping the node event loop alive and causing spurious “open handle” warnings in test runners (#286)
- The
canvas.engineproperty now reports the thread-count on CPU- as well as GPU-backed canvases
Text
- Variable font instancing now uses Skia’s font-argument path, fixing missing or blank glyphs, weights that drifted from glyph to glyph within a single string, and the weight axis being ignored entirely on Linux (#272, #280, #294)
textDecorationis now drawn behind the text (unless it’sline-through) and underlines now leave gaps for descenders- Fixed
textBaselinebeing applied incorrectly when drawing with the default font measureText()now reports more accurate, browser-like metrics:actualBoundingBox*values (and per-line metrics) are now traced from the glyph outlines and include diacriticswidthnow includes a trailing letter-space whenletterSpacingis non-zerofontBoundingBoxAscent/Descentare now derived from the font’s metrics tables and no longer include line-spacing or double-count thetextBaselineemHeightAscent/Descentdescribe the em square (proportionally dividing the font size) rather than duplicating the font box (which incorporates padding)
- Improved parsing of CSS-derived properties:
filternow ignores the entire assignment if any component is invalid (rather than filtering them out and keeping just the valid components)textDecorationhandles multi-word values (e.g., color functions), newlines, and repeated delimiters- a
normalkeyword in afontshorthand declaration no longer clobbers a precedingitalic(#278) fontVariantparsing has been corrected
- Form feeds & vertical tab escapes no longer truncate the text (or force a line break with
textWrapenabled) - The
top,middle&bottomtextBaselinesettings now agree with browsers, positioning relative to the em square (rather than the ascent/descent box) - Trailing spaces are now used for determining the width of the
textDecorationand positioning right-aligned or centered text letterSpacingno longer applies to words in Arabic or other cursive scripts; only the spaces between words are widened
GPU
- Fixed window flickering at the canvas’s edges when sizing to
fit(#285) - A GPU disappearing (after a device reset or an eGPU being unplugged) is now recoverable
- Vulkan device-selection:
- verifies candidate GPUs can actually build a working context before selection
- uses the device’s preferred
api_version, fixing crashes on some Intel drivers (#274) - falls back to CPU when no usable Vulkan device is available (#289)
- shuts down GPU cleanly on exit (preventing a crash on some Intel GPUs)
Paths
- Fixed
clip()andputImageData()corrupting saved graphics state through a missing internalsave(), which could break laterrestore()calls (#273) clip(),fill(), andisPointInPath()now treat anundefinedfill-rule argument as the default ("nonzero") rather than throwing (#282)- The Path2D boolean operators (
union(),difference(),intersect(),xor(),complement(), andsimplify()) now return paths that can be drawn with the defaultnonzerowinding rule - Now that
simplify()correctly honors the fill rule passed to it,unwind()has been deprecated (since it’s now equivalent to callingsimplify("evenodd"))
Imagery
drawImage(),getImageData(),putImageData(), and DOMRect now handle rectangles with negative widths or heights as the spec dictates (#283), and their coordinates are now truncated toward zero rather than floored (matching browser behavior).drawImage()anddrawCanvas()now clip the source rectangle to the image’s actual bounds and skip the draw entirely when the crop doesn’t overlap it. Previously a zero-overlap crop with a composite mode likecopyordestination-incould erase the whole canvas.- A failed Image load without an
errorhandler no longer terminates the process. - SVG exports no longer double-draw bitmaps added to the canvas via
putImageData. - The
toSharp()method now produces correct pixel values for all supported color types, not just the 8-bit formats.
Compositing
- Drawing one canvas onto another (via
drawImage()ordrawCanvas()) now isolates the source’s compositing from the destination. Previously, if the drawn canvas used a non-source-overblend mode, calledclearRect(), or blitted an ImageData it would blend against (or erase) the destination canvas’s content. - The
matteis now composited underneath the finished page rather than painted first, preventing a canvas that uses region-affecting composite operations from erasing it.
Geometry
DOMPoint’smatrixTransform()andDOMMatrix’stransformPoint()now accept a plain init dictionary and fill in omitted members according to the spec. Previously, omitting values would lead to unexpectedNaNs.
Breaking Changes
- The minimum supported Node version is now 18.
App.eventLoopmodes have been deprecated. The GUI event loop now always runs in harmony with Node’s, allowing timeouts and intervals to fire even while animating.- The
fontSynthesiscontext property now defaults totrue, matching browser behavior. Requesting a weight or slant that the selected font family doesn’t provide now generates a synthetic bold or oblique. Set it tofalseto fall back to the nearest available real face instead. - Drawing or sampling an image that failed to load now throws instead of silently doing nothing.
unwind()has been deprecated in favor ofsimplify("evenodd"), which selects the same region; it will be removed in a future release.- Boolean-op and
simplify()results now render differently when filled with the default"nonzero"rule. Results containing holes (e.g., viaxorordifference) previously filled in solid without an explicit"evenodd". - SVG Images lacking an explicit
widthandheightnow use the CSS default sizing algorithm (with a 300×150 default object size) to establish a default intrinsic size. An SVG with only one concrete dimension plus aviewBoxratio now resolves to a fully-determined intrinsic size. This changes both the reportedwidth/heightof such images and how they scale when drawn without explicit size arguments (including when used as fill/stroke-pattern tiles). - SVG strings passed to the Path2D constructor or
dsetter now follow browser behavior: invalid strings are truncated at the first error rather than rejected outright, and paths that don’t begin with a MoveTo are now empty (rather than starting with an implicitM 0,0). - An ImageData created from a Node
Bufferobject now shares the buffer’s memory rather than copying it. - The
windowCargo feature is no longer independently configurable (it is now automatically enabled by ametalorvulkanfeature being present). fontHintingis now ignored on macOS (since CoreText has no support for hinting). Previously it enabled heavier text rendering, which is now available by settingtextRenderingto"optimizeLegibility"instead- Text drawn with
textAlign: 'justify'anddirection: 'rtl'now extends leftward from the anchor like right-aligned text does - Vertical placement for non-alphabetic
textBaselinesettings has shifted in order to match browser behavior - When both a static and a variable font of the same family and style are registered via the same
FontLibrary.use()call, the variable font will now be preferred (rather than letting ordering decide)
📦 ⟩ v3.0.8 ⟩ Sep 25, 2025
Bugfix
- Fix rendering to windows with semi-transparent backgrounds
📦 ⟩ v3.0.7 ⟩ Sep 19, 2025
Bugfix
- Added missing TypeScript definitions for
resizableproperty (thanks to @goldenratio #265)
Misc. Improvements
- Upgraded Skia to milestone 140
- Added a bounding box hierarchy cache to further speed up canvas-to-canvas drawing via drawImage or drawCanvas (thanks to @Shiranuit #261)
📦 ⟩ v3.0.6 ⟩ Aug 28, 2025
Bugfix
- Fixed Windows CI build
📦 ⟩ v3.0.5 ⟩ Aug 28, 2025
Misc. Improvements
- Decreased memory usage when drawing one canvas's contents onto another (via drawImage or drawCanvas).
- Reduced dependency footprint (from 294 to 22 modules when installed with
devDependenciesincluded):- replaced
nodemonwithnode --watch - replaced
jestwithnode --testfor unit tests - replaced
expresswithhonofor visual tests - dropped
lodashandfast-globusage in test suite
- replaced
📦 ⟩ v3.0.4 ⟩ Aug 22, 2025
Bugfixes
- Variable fonts can now correctly function as fallbacks (previously only the first-matched font in a stack would be converted to a usable instance)
Misc. Improvements
- When installing the module, any proxy server defined via
npm config set proxyor anHTTPS_PROXYenvironment variable will be used to fetch the prebuilt binary - Replaced
fetchaltogether, now using Node's built-inhttpandhttpsmodules for better backward compatibility, support for additional request parameters for loadImage(), and a further reduction in the number of npm dependencies (now down to 8)
📦 ⟩ v3.0.3 ⟩ Aug 20, 2025
Bugfix
- Fixed a segfault where windows on Vulkan platforms were being deallocated incorrectly upon close.
📦 ⟩ v3.0.2 ⟩ Aug 17, 2025
Misc. Improvements
- Only use
node-fetchon systems lacking a built-infetch - Dropped
fast-glob(reducing external dependency count to 11)
Breaking Changes
- Glob-handling has been removed from FontLibrary.use(). If you want the old behavior, try using the
fast-globorglobmodules to prepare the file-list you pass to the method.
📦 ⟩ v3.0.1 ⟩ Aug 16, 2025
Misc. Improvements
- Updated
node-fetchto v3 to fix deprecation warnings on recent node versions - Updated
winitand other rust dependencies
📦 ⟩ v3.0.0 ⟩ Aug 15, 2025
New Features
GUI
- The
Appglobal now has aneventLoopproperty which can be set to:"native"(the default) in which case the Node event loop is suspended while the OS handles displaying GUI windows"node"where the Node event loop maintains control (allowingsetIntervalandsetTimeoutto run) and handles GUI events manually every few milliseconds (though note some of the caveats associated with the Winit feature this uses).
- Window objects now have a read-only
closedproperty and emit acloseevent when they are closed. Closed windows can later be re-opened by calling the newopen()method. - The new
borderlessattribute allows Window titlebars and borders to be hidden (thanks to @hydroperx #230)
Imagery
- The
loadImage()andloadImageData()helpers now usenode-fetchto handle web requests and can accept a fetch options object as the final argument. Imageobjects can now be created by passing a Buffer or dataURL-containing string as a constructor argument and will be immediately drawable (no asynchronous loading required).- Added support for integrating the Sharp image processor into canvas workflows (if the
sharpnpm module has been installed):- The new Canvas.toSharp() & ImageData.toSharp() convenience methods convert their contents to a Sharp bitmap object
loadImage()&loadImageData()can now be called with a Sharp object as their sole argument- The
srcproperty on a new Image object can be set to a Sharp object and it will begin asynchronously loading
- Added new options to
createTexture()for setting the line cap style and selecting whether vector patterns should be clipped or outlined
Rendering
- Significant speed-ups for deeply layered drawing in which the canvas isn't cleared or reset (potentially resulting in numerous vector objects being re-drawn despite being hidden by shapes drawn on top):
- The bitmap generated by getImageData()/toBuffer()/toFile() is now cached. When called repeatedly, only newly added drawing commands will need to be rasterized (and will be layered atop the bitmap saved in the prior call).
- Window contents are now cached between screen refreshes, improving performance during resizing and in cases where the canvas is drawn to in multiple passes and not cleared with every frame
- Calling clearRect() or fillRect() with an area that covers the canvas now erases all the vector shapes below
- The toFile(), toBuffer(), and toDataURL() methods now accept an optional
downsampleflag (for jpegs only), which enables 4:2:0 chroma-subsampling. By default, no subsampling (a.k.a. 4:4:4) will be performed - The getImageData() method now accepts additional rendering arguments (
density,matte, andmsaa) which behave the same as their equivalents in the toFile() method.
Typography
- Text lightness can now be fine-tuned through a pair of optional arguments that can be passed to the Canvas or Window constructors:
textContrast— a number in the range 0.0–1.0 controlling the amount of additional weight to add (defaults to0.0)textGamma— a number in the range 0.0–4.0 controlling how glyph edges are blended with the background (defaults to1.4)
- The
textAlignattribute can now be set to"justify" measureText()has been rewritten to calculate metrics based not just on the font specified infontbut also any fallback fonts that were used for character glyphs not present in the ‘main’ font. The line-by-line measurements now include arunsarray with bounds and metrics for each single-font range of characters on the line.
Supported Platforms
- Added precompiled binaries for Arm-based Windows systems
- Now providing pre-built ‘layer’ archives for use with AWS Lambda (for Node v20 and above)
- Linux builds now include a statically linked version of fontconfig, as a result:
libfontconfigpackages no longer need to be installed on the host system usingapt,apk,yum,dnf, etc.- it now runs on ‘serverless’ platforms like Vercel without modification (sadly Cloudflare doesn't support native modules at all though)
Breaking Changes
- Renamed export functions and options to be more consistent with similar browser APIs and other Node modules:
saveAs()andsaveAsSync()are now calledtoFile()andtoFileSync()toDataURL()now behaves the same as its browser equivalent: it is synchronous and its only configuration option is a numericalqualitysettingtoDataURLSync()has been removedtoURL()andtoURLSync()produce data URLs and support the same enhanced export options astoBuffer
- When exporting to an SVG, text is now converted to paths only if the
outlineoption is set totrue
Misc. Improvements
App.launch()now returns a Promise that resolves when the final window is closed, allowing you to schedule code to run before the process would otherwise exit (see also the newidleevent which fires under the same circumstances).inputevent objects now contain aninputTypeproperty to distinguish between insertion, deletion, and IME composition- Mouse events are no longer coalesced down to a single instance per frame (most relevant for
mousemoveevents) - Mouse events now include a standard
buttonsattribute - DPI metadata is now included in webp files (reflecting the
densityoption passed to toFile() or toBuffer()) - Argument validation now emulates browser behavior much more closely—including converting what were previously TypeErrors in certain cases into silent failures. To reënable these errors, set the
SKIA_CANVAS_STRICTenvironment variable to1ortrue. - Replaced
node-pre-gypwith a custom installation script andglobwithfast-glob, cutting the number ofnode_modulesdirectories installed from 83 to 29. - loadImage(), loadImageData(), and Image.src can now accept URL objects (using http(s), file, or data protocols). Likewise, toFile() now accepts
file:URLs (allowing relative paths to be constructed withimport.meta.url) - The Canvas constructor's options argument can now contain a
gpuproperty which can be set tofalsein order to use CPU-based rendering
Bugfixes
- Setting a window's
cursorproperty to "none" now hides the cursor - Spurious
movedwindow events are no longer emitted during resizes resizeevents now update the window object’s width & height properties in addition to providing the new size in the event objectroundRect()now reflects context's current transform state and accepts plain{x, y}objects for corner-radii in addition to Numbers and DOMPoints (thanks to @mpaperno #223)- Angles passed to
createConicGradient()are no longer incorrectly offset by 90° - Calling
lineToon an empty Path2D no longer adds a line from the origin to the specified coordinates: it now acts as if it were amoveTo measureText()now correctly calculates widths when letterSpacing has been setstartIndexandendIndexin TextMetrics.lines[] now correspond to character indices in the string passed to measureText(), not byte indices into the UTF-8 buffer backing it
📦 ⟩ v2.0.2 ⟩ Jan 27, 2025
New Features
- Added
fontHintingattribute (off by default to better match font weights in browser rendering). Setting it totruemay result in crisper edges but adds some weight to the font.
Bugfixes
- Text spacing
- Setting
letterSpacingno longer indents text at beginning of line letterSpacingnow properly handles negative values
- Setting
- Improved accuracy of
measureText()- Now uses font metrics' default leading when the line-height is left unspecified in the
ctx.fontstring (NB: this is likely to cause vertical shifts for non-alphabeticbaselines) - Updated baseline offset calculations for
middle&hangingto better match browsers - The
actualBoundingBox*&lines[].x/y/width/heightrectangles returned by measureText() are now just the glyph-occupied area, not the whole line-height of the textblock - Fixed the sign on
actualBoundingBoxLeft(positive values now mean left of the origin) lines[].baselinenow corresponds to the selectedctx.textBaseline, previously it was always the alphabetic baseline
- Now uses font metrics' default leading when the line-height is left unspecified in the
- TypeScript definitions no longer include the entire DOM library (which had been pulling in tons of non-Canvas-related object types that this library doesn't emulate)
📦 ⟩ v2.0.1 ⟩ Dec 8, 2024
Misc. Improvements
- Added support for Intel integrated GPUs that would previously throw an "instantiated but unable to render" error
- Note: you may need to upgrade to the latest Mesa drivers (24.3.1 or later), especially for in-window rendering to work correctly on Linux
- Fixed window initialization for Vulkan GPUs that default to a framebuffer color-format Skia doesn't support
- Vulkan drivers that fall back to the Mesa LLVMpipe software renderer now work correctly
- Optimized font library initialization to improve SVG parsing speed
📦 ⟩ v2.0.0 ⟩ Dec 2, 2024
New Features
Website
- Documentation is now hosted at skia-canvas.org. Go there for a more readable version of all the details that used to be wedged into the README file.
Imagery
- Added initial SVG rendering support. Images can now load SVG files and can be drawn in a resolution-independent manner via
drawImage()(thanks to @mpaperno #180). Note that Images loaded from SVG files that don't have awidthandheightset on their root<svg>element have some quirks as of this release:- The Image object's
heightwill report being150and thewidthwill be set to accurately capture the image's aspect ratio - When passed to
drawImage()without size arguments, the SVG will be scaled to a size that fits within the Canvas's current bounds (using an approach akin to CSS'sobject-fit: contain). - When using the 9-argument version of
drawImage(), the ‘crop’ arguments (sx,sy,sWidth, &sHeight) will correspond to this scaled-to-fit size, not the Image's reportedwidth&height.
- The Image object's
- WEBP support
- Canvas.saveAs() & toBuffer() can now generate WEBP images and Images can load WEBP files as well (contributed by @mpaperno #177, h/t @revam for the initial work on this)
- Raw pixel data support
- The
toBuffer()andsaveAs()methods now support"raw"as a format name and/or file extension, causing them to return non-encoded pixel data (by default in an"rgba"layout like a standard ImageData buffer) - Both functions now take an optional
colorTypeargument to specify alternative pixel data layouts (e.g.,"rgb"or"bgra")
- The
- ImageData enhancements
- The drawImage() and createPattern() methods have been extended to accept ImageData objects as arguments. Previously only putImageData() could be used for rendering, but this method ignores the context's current transform, filters, opacity, etc.
- When creating an ImageData via the getImageData() & createImageData() methods or
new ImageData()constructor, the optional settings arg now allows you to select thecolorTypefor the buffer's pixels.
Typography
- FontLibrary.use() now supports dynamically loaded WOFF & WOFF2 fonts
- The
outlineText()method now takes an optionalwidthargument and supports all the context's typographic settings (e.g.,.font,.fontVariant,.textWrap,.textTracking, etc.) - Fonts with condensed/expanded widths can now be selected with the
.fontStretchproperty. Note that stretch values included in the.fontstring will overwrite the current.fontStretchsetting (or will reset it tonormalif omitted). - Generic font family names are now mapped to fonts installed on the system. The
serif,sans-serif,monospace, andsystem-uifamilies are currently supported. - Underlines, overlines, and strike-throughs can now be set via the Context's
.textDecorationproperty. - Text spacing can now be fine-tuned using the
.letterSpacingand.wordSpacingproperties.
GUI
- The Window class now has a
resizableproperty which can be set tofalseto prevent the window from being manually resized or maximized (contributed by @nornagon #124). - Window event handlers now support Input Method Editor events for entering composed characters via the compositionstart, compositionupdate, & compositionend events. The
inputevent now reports the composed character, not the individual keystrokes.
Rendering
- The Canvas object has a new
engineproperty which describes whether the CPU or GPU is being used, which graphics device was selected, and what (if any) error prevented it from being initialized. - The
.transformand.setTransformmethods on Context, Path2D, and CanvasPattern objects can now take their arguments in additional formats. They can now be passed a DOMMatrix object or a string with a list of transformation operations compatible with the CSStransformproperty. The DOMMatrix constructor also supports these strings as well as plain, matrix-like objects with numeric attributes nameda,b,c,d,e, &f(contributed by @mpaperno #178). - The number of background threads used for asynchronous exports can now be controlled with the
SKIA_CANVAS_THREADSenvironment variable
Breaking Changes
- An upgrade to Neon with N-API v8 raised the minimum required Node version to 12.22+, 14.17+, or 16+.
- Images now load asynchronously in cases where the
srcproperty has been set to a local path. As a result, it's now necessary toawait img.decode()or set up an.on("load", …)handler before drawing it—even when thesrcis non-remote. - The KeyboardEvent object returned by the
keyup/keydownandinputevent listeners now has fields and values consistent with browser behavior. In particular,codeis now a name (e.g.,ShiftLeftorKeyS) rather than a numeric scancode,keyis a straightforward label for the key (e.g.,Shiftors) and the newlocationfield provides a numeric description of which variant of a key was pressed. - The deprecated
.asyncproperty has been removed. See the v0.9.28 release notes for details. - The non-standard
.textTrackingproperty has been removed in favor of the new.letterSpacingproperty
Bugfixes
- Initializing a GPU-renderer using Vulkan now uses the
vulkanocrate and makes better selections among devices present (previously it was just using the first result, which is not always optimal). - The Image.onload callback now properly sets
thisto point to the new image (contributed by @mpaperno & @ForkKILLET). - Creating a Window with
fullscreenset totruenow takes effect immediately (previously it was failing silently) - Drawing paths after setting an invalid transform no longer crashes (contributed by @mpaperno #175)
- Windows with
.on("draw")handlers no longer become unresponsive on macOS 14+ after being fully occluded by other windows - Ellipses with certain combinations of positive and negative start- and stop-angles now render correctly—previously they would not appear at all if the total sweep exceeded 360° (contributed by @mpaperno #176)
- The
drawCanvas()method now clips to the specified crop size (contributed by @mpaperno #179) - Hit-testing with
isPointInPathandisPointInStrokenow works correctly when called with a Path2D object as the first argument
Misc. Improvements
- Upgraded Skia to milestone 131
- Added TypeScript definitions for the Window object’s event types (contributed by @saantonandre #163) and the
roundRectmethod (contributed by @sandy85625 & @santilema) - Performance improvements to FontLibrary, speeding up operations like listing families and adding new typefaces.
- Updated
winitand replaced the end-of-life’d skulpin-based Vulkan renderer with a new implementation using Vulkano for window-drawing on Windows and Linux.It’s a fairly direct adaptation of Vulkano sample code for device setup with skia-specific rendering routines inspired by @pragmatrix’s renderer for emergent. All of which is to say, if you understand this better than I do I'd love some suggestions for improving the rendering setup.
- The GPU is now initialized only when it is needed, not at startup. As a result, setting that Canvas's
.gpuproperty tofalseimmediately after creation will prevent any GPU-related resource acquisition from occurring (though rendering speed will be predictably slower). - The sample-count used by the GPU for multisample antialiasing can now be configured through the optional
msaaexport argument. If omitted, defaults to 4x MSAA. - Added support for non-default imports (e.g.,
import {Image} from "skia-canvas") when used as an ES Module. - The getImageData() method now makes use of the GPU (if enabled) and caches data between calls, greatly improving performance for sequential queries
📦 ⟩ v1.0.2 ⟩ Aug 21, 2024
Maintenance
- After getting a surprise bill from Amazon for the S3 bucket hosting the pre-compiled binaries, I've moved them to GitHub Releases instead. Aside from resolving some security warnings by upgrading dependencies, this version should be functionally identical to 1.0.1…
Breaking Changes
- The 32-bit ARM-based linux builds are no longer provided pre-compiled; you'll now need to build from source.
📦 ⟩ v1.0.1 ⟩ Oct 15, 2022
Bugfixes
- If an offscreen buffer can't be allocated using the Vulkan renderer, CPU rendering is used as a fallback
- The
drawCanvas()routine now works even when the destination canvas is later saved as an SVG (previously, the source canvas would be missing from the output). Caveat: this only works if the destination canvas is using the defaultsource-overblend mode, has itsglobalAlphaset to 1, and is not using shadows or thefilterproperty. If any of those defaults have been changed, the drawn canvas will not appear in the saved SVG. Bitmap and PDF exports do not have this restriction.
Misc. Improvements
- Added a
fullscreenevent to theWindowclass to flag changes into and out of full-screen mode.
📦 ⟩ v1.0.0 ⟩ Aug 5, 2022
New Features
- The new Window class can display a Canvas on screen, respond to mouse and keyboard input, and fluidly animate by calling user-defined event handlers.
- Bitmap rendering now occurs on the GPU by default and can be configured using the Canvas's
.gpuproperty. If the platform supports hardware-accelerated rendering (using Metal on macOS and Vulkan on Linux & Windows), the property will betrueby default and can be set tofalseto use the software renderer. - Added support for recent Chrome features:
- the
reset()context method which erases the canvas, resets the transformation state, and clears the current path - the
roundRect()method on contexts and Path2D objects which adds a rounded rectangle using 1–4 corner radii (provided as a single value or an array of numbers and/or DOMPoint objects)
- the
Bugfixes
- The
FontLibrary.reset()method didn't actually remove previously installed fonts that had already been drawn with (and thus cached). It now clears those caches, which also means previously used fonts can now be replaced by calling.use()again with the same family name. - The
.drawCanvas()routine now applies filter effects and shadows consistent with the current resolution and transformation state.
Misc. Improvements
- The
.filterproperty's"blur(…)"and"drop-shadow(…)"effects now match browser behavior much more closely and scale appropriately with thedensityexport option. - Antialiasing is smoother, particularly when down-scaling images, thanks to the use of mipmaps rather than Skia's (apparently buggy?) implementation of bicubic interpolation.
- Calling
clearRect()with dimensions that fully enclose the canvas will now discard all the vector objects that have been drawn so far (rather than simply covering them up). - Upgraded Skia to milestone 103
📦 ⟩ v0.9.30 ⟩ Jun 7, 2022
New Features
- Enhancements to the shared FontLibrary object:
- Added pre-compiled binaries for Alpine Linux on arm64
Bugfixes
- Calling
clipwith an empty path (or one that does not intersect the current clipping mask) will now prevent drawing altogether - Transformation (
translate,rotate, etc.) and line-drawing methods (moveTo,lineTo,ellipse, etc.) are now silently ignored if called withNaN,Infinity, or non-Number values in the arguments rather than throwing an error- applies to both the Context and Path2D versions of the drawing methods
- a TypeError is thrown only if the number of arguments is too low (mirroring browser behavior)
conicCurveTo()now correctly reflects the canvas's transform state- The browser-based version of
loadImage()now returns a Promise that correctly resolves to an Image object - SVG exports no longer have an invisible, canvas-sized
<rect/>as their first element - Fixed an incompatibility on Alpine between the version of libstdc++ present on the
node:alpinedocker images and the version used when building the precompiled binaries
Misc. Improvements
- Upgraded Skia to milestone 101
📦 ⟩ v0.9.29 ⟩ Feb 7, 2022
New Features
- PDF exports now support the optional
matteargument.
Breaking Changes
- When the
drawImage()function is passed a Canvas object as its image source it will now rasterize the canvas before drawing. The prior behavior (in which it is drawn as a vector graphic) can now be accessed through the newdrawCanvas()method which supports the same numerical arguments asdrawImagebut requires that its first argument be a Canvas.
Bugfixes
- Regions erased using
clearRect()are now properly antialiased - The
clip()method now interprets the current translate/scale/rotate state correctly when combining clipping masks
Misc. Improvements
- Upgraded Skia to milestone 97
📦 ⟩ v0.9.28 ⟩ Jan 12, 2022
New Features
- Added TypeScript definitions for extensions to the DOM spec (contributed by @cprecioso)
- Added 3D-perspective transformations via the new createProjection() context method
- Colors can now use the hwb() model
Breaking Changes
- The Canvas
.asyncproperty has been deprecated and will be removed in a future release.- The
saveAs,toBuffer, andtoDataURLmethods will now be async-only (likewise the shorthand properties). - Use their synchronous counterparts (
saveAsSync,toBufferSync, andtoDataURLSync) if you want to block execution while exporting images.
- The
- The ImageData constructor now orders its arguments properly: the optional buffer/array argument now comes first
Bugfixes
- Fixed a stack overflow that was occurring when images became too deeply nested for the default deallocator to handle (primarily due to many thousands of image exports from the same canvas)
- The
source-in,source-out,destination-atop, andcopycomposite operations now work correctly for paths rather than rendering shapes without color (contributed by @meihuanyu) - Shape primitives now behave consistently with browsers when being added to a non-empty path:
rect()now issues an initialmoveTorather than extending the path, then leaves the ‘current’ point in its upper left cornerellipse()extends the current path rather than implicitly closing it (contributed by @meihuanyu)arc()also extends the current path rather than closing it
Misc. Improvements
- Upgraded Skia to milestone 96
- Added workflow for creating docker build environments
📦 ⟩ v0.9.27 ⟩ Oct 23, 2021
New Features
- Added pre-compiled binaries for Alpine Linux using the musl C library
📦 ⟩ v0.9.26 ⟩ Oct 18, 2021
New Features
- Added pre-compiled binaries for 32-bit and 64-bit ARM on Linux (a.k.a. Raspberry Pi)
Bugfixes
- Windows text rendering has been restored after failing due to changes involving the
icudtl.datfile FontLibrary.usenow reports an error if the specified font file doesn't exist- Fixed a crash that could result from calling
measureTextwith various unicode escapes
Misc. Improvements
- Upgraded Skia to milestone 94
- Now embedding a more recent version of the FreeType library on Linux with support for more font formats
📦 ⟩ v0.9.25 ⟩ Aug 22, 2021
Bugfixes
- Improved image scaling when a larger image is being shrunk down to a smaller size via
drawImage() - modified
imageSmoothingQualitysettings to provide a more meaningful range acrosslow,medium, andhigh measureText()now returns correct metrics regardless of currenttextAlignsetting- Rolled back
icudtl.datchanges on Windows (which suppressed the misleading warning message but required running as Administrator)
Misc. Improvements
- Now using Neon v0.9 (with enhanced async event scheduling)
📦 ⟩ v0.9.24 ⟩ Aug 18, 2021
New Features
- Path2D objects now have a read/write
dproperty with an SVG representation of the path’s contours and anunwind()method for converting from even-odd to non-zero winding rules - The
createTexture()context method returns CanvasTexture objects which can be assigned tofillStyleorstrokeStyle - Textures draw either a parallel-lines pattern or one derived from the provided Path2D object and positioning parameters
- The marker used when
setLineDashis active can now be customized by assigning a Path2D to the context’slineDashMarkerproperty (default dashing can be restored by assigningnull) - The marker’s orientation & shape relative to the path being stroked can be controlled by the
lineDashFitproperty which defaults to"turn"but can be set to"move"(which preserves orientation) or"follow"(which distorts the marker’s shape to match the contour)
Bugfixes
- Removed use of the
??operator which is unavailable prior to Node 14 - Prevented a spurious warning on windows incorrectly claiming that the
icudtl.datfile could not be found
Misc. Improvements
- The Path2D
simplify()method now takes an optional fill-rule argument - Added support for versions of macOS starting with 10.13 (High Sierra)
📦 ⟩ v0.9.23 ⟩ Jul 12, 2021
New Features
- Conic béziers can now be drawn to the context or a Path2D with the
conicCurveTo()method - Text can be converted to a Path2D using the context’s new
outlineText()method - Path2D objects can now report back on their internal geometry with:
- the
edgesproperty which contains an array of line-drawing commands describing the path’s individual contours - the
contains()method which tests whether a given point is on/within the path - the
points()method which returns an array of[x, y]pairs at the requested spacing along the curve’s periphery
- the
- A modified copy of a source Path2D can now be created using:
offset()ortransform()to shift position or apply a DOMMatrix respectivelyjitter()to break the path into smaller sections and apply random noise to the segments’ positionsround()to round off every sharp corner in a path to a particular radiustrim()to select a percentage-based subsection of the path
- Two similar paths can be ‘tweened’ into a proportional combination of their coordinates using the
interpolate()method
Bugfixes
- Passing a Path2D argument to the
fill()orstroke()method no longer disturbs the context’s ‘current’ path (if one has been created usingbeginPath()) - The
filterproperty will now accept percentage values greater than 999%
Misc. Improvements
- The
newPage()andsaveAs()methods now work in the browser, including the ability to save image sequences to a zip archive. The browser’s canvas is still doing all the drawing however, so file export formats will be limited to PNG and JPEG and none of the other Skia-specific extensions will be available. - The file-export methods now accept a
mattevalue in their options object which can be used to set the background color for any portions of the canvas that were left semi-transparent - Canvas dimensions are no longer rounded-off to integer values (at least until a bitmap needs to be generated for export)
- Linux builds will now run on some older systems going back to glibc 2.24
📦 ⟩ v0.9.22 ⟩ Jun 09, 2021
New Features
- Rasterization and file i/o are now handled asynchronously in a background thread. See the discussion of Canvas’s new
asyncproperty for details. - Output files can now be generated at pixel-ratios > 1 for High-DPI screens.
SaveAsand the other canvas output functions all accept an optionaldensityargument which is an integer ≥1 and will upscale the image accordingly. The density can also be passed using thefilenameargument by ending the name with an ‘@’ suffix likesome-image@2x.png. - SVG exports can optionally convert text to paths by setting the
outlineargument totrue.
Breaking Changes
- The canvas functions dealing with rasterization (
toBuffer,toDataURL,png,jpg,pdf, andsvg) and file i/o (saveAs) are now asynchronous and returnPromiseobjects. The old, synchronous behavior is still available on a canvas-by-canvas basis by setting itsasyncproperty tofalse. - The optional
qualityargument accepted by the output methods is now a float in the range 0–1 rather than an integer from 0–100. This is consistent with the encoderOptions arg in the spec. Quality now defaults to 0.92 (again, as per the spec) rather than lossless.
Bugfixes
measureTextwas reporting zero when asked to measure a string that was entirely made of whitespace. This is still the case for ‘blank’ lines whentextWrapis set totruebut in the default, single-line mode the metrics will now report the width of the whitespace.- Changed the way text rendering was staged so that SVG exports didn’t entirely omit(!) text from their output. As a result,
Context2Ds now use an externalTypesetterstruct to manage layout and rendering.
📦 ⟩ v0.9.21 ⟩ May 22, 2021
New Features
- Now runs on Windows and Apple Silicon Macs.
- Precompiled binaries support Node 10, 12, 14+.
- Image objects can be initialized from PNG, JPEG, GIF, BMP, or ICO data.
- Path2D objects can now be combined using boolean operators and can measure their own bounding boxes.
- Context objects now support
createConicGradient(). - Image objects now return a promise from their
decode()method allowing for async loading without theloadImagehelper.
Bugfixes
- Calling
drawImagewith aCanvasobject as the argument now uses a SkiaPictrather than aDrawableas the interchange format, meaning it can actually respect the canvas's currentglobalAlphaandglobalCompositeOperationstate (fixed #6). - Improved some spurious error messages when trying to generate a graphics file from a canvas whose width and/or height was set to zero (fixed #5).
CanvasPatterns now respect theimageSmoothingEnabledsetting- The
counterclockwisearg toellipseandarcis now correctly treated as optional.
Misc. Improvements
- Made the
console.logrepresentations of the canvas-related objects friendlier. - Added new test suites for
Path2D,Image, andCanvas’s format support. - Created workflows to automate precompiled binary builds, testing, and npm package updating.
📦 ⟩ v0.9.20 ⟩ Mar 27, 2021
Bugfixes
- The
loadImagehelper can now handleBufferarguments
Misc. Improvements
- Improved documentation of compilation steps and use of line height with
ctx.font
📦 ⟩ v0.9.19 ⟩ Aug 30, 2020
Initial public release 🎉