CanvasPlus is a universal Node.js and browser library, which adds image processing capabilities onto the HTML5 Canvas API. In Node.js we rely on the amazing canvas module, which is built on the Cairo graphics library. In the browser we simply extend (well, wrap) the native canvas.
Check out our live demo playground to try out the library in your favorite browser.
- Load images from buffers, files or URLs
- Resize with special modes including cover, contain, and letterbox
- Render text with extra features
- Multi-line, paragraph text
- Custom character spacing and line spacing
- Word-wrap
- Auto-scale to fit line or paragraph
- Quantization into indexed mode for PNGs and GIFs
- Optional dithering
- Super-fast quantize mode
- Curves
- Similar to Adobe Photoshop's Curves filter
- Optionally apply different curves to each channel
- Macro functions including Posterize, Solarize, Invert, Gamma, Sepia, etc.
- Adjust hue, saturation, brightness, contrast and color temperature
- Normalize (Auto-Enhance)
- Generate histogram data
- Access EXIF Metadata
- Auto-Orient
- Convolution Kernel
- Macro functions including Emboss, Find Edges, Blur, Gaussian Blur, Sharpen
- Crop images
- Manual or automatic based on corner pixel
- Rotation and flip horizontal / vertical
- Read/Write JPEG, PNG, GIF and WebP images
- Full support for indexed palette PNGs and GIFs
- Full support for alpha PNGs, even indexed alpha
- Full support for alpha WebP images.
There are already some extremely cool image processing libraries out there. I played with several, but none of them provided all the features I needed. Specifically, I needed fast JPEG load and save, fast resize, TrueType / OpenType font support, advanced text rendering features like word wrap and auto-scale, quantization to 8-bit indexed mode with dithering, and curves. I also wanted a universal library that worked in the browser as well as Node.js. That being said, I want to recommend some amazing free libraries that you should absolutely check out:
Library | Notes |
---|---|
LWIP | Very nice, fast engine with a clean API. Only missing text rendering, curves, and 8-bit quantization. |
JIMP | Incredible pure-JS engine that does just about everything. However, JPEG load/save and resize are too slow for me. Also, no TrueType / OpenType font support, and there are some licensing issues. Otherwise this is very, very cool. |
Sharp | Very fast (uses libvips), and cool SVG integration. Missing text rendering, curves, 8-bit quantization, and browser support. |
GM | I love this library, but it actually just shells out to the command-line GraphicsMagick/ImageMagick binary, so there are some performance concerns. Also, no advanced text features, no curves, and no browser support. |
- Usage
- API
- Development
- Acknowledgments
- License
Use npm to install the module:
npm install pixl-canvas-plus
Please note that the canvas module dependency is a binary compiled library, which depends on Cairo being preinstalled on your machine. See their installation wiki for assistance. That being said, canvas v2.x comes with precompiled binaries for macOS, Windows and Linux, so it might just slip right in.
Here is a simple usage example:
const CanvasPlus = require('pixl-canvas-plus');
let canvas = new CanvasPlus();
canvas.load( 'waterfall.jpg', function(err) {
if (err) throw err;
canvas.resize({
"width": 640,
"height": 480,
"mode": "fit"
});
canvas.adjust({
"brightness": -20,
"contrast": 20
});
canvas.write({"format":"jpeg", "quality":90}, function(err, buf) {
if (err) throw err;
// 'buf' will be a binary buffer containing final image...
require('fs').writeFileSync('my_image.jpg', buf);
});
});
For the browser we use the native built-in HTML5 Canvas API, and there are no dependencies. Simply download and host this pre-built file (compiled using browserify) on your own web server:
https://raw.githubusercontent.com/jhuckaby/canvas-plus/master/canvas-plus.js
This will expose a global CanvasPlus
class in the window object. Here is how to use it:
<script src="canvas-plus.js"></script>
<script>
let canvas = new CanvasPlus();
canvas.load( 'waterfall.jpg', function(err) {
if (err) throw err;
canvas.resize({
"width": 640,
"height": 480,
"mode": "fit"
});
canvas.adjust({
"brightness": -20,
"contrast": 20
});
canvas.write({"format":"jpeg","quality":90}, function(err, buf) {
if (err) throw err;
// 'buf' will be a binary buffer containing final image...
let blob = new Blob( [ buf ], { type: "image/jpeg" } );
let object_url = URL.createObjectURL( blob );
// insert new image into DOM
let img = new Image();
img.src = object_url;
document.body.appendChild( img );
});
});
</script>
To create a blank canvas, and optionally fill with a background color, you can pass arguments to the constructor, like this:
let canvas = new CanvasPlus( 640, 480 );
Or with an optional background color:
let canvas = new CanvasPlus( 640, 480, "#FF0000" );
Or you can use the explicit create() method:
let canvas = new CanvasPlus();
canvas.create({
width: 640,
height: 480,
background: "#FF0000"
});
The background color can be any CSS-compatible color string, including RGBA, e.g. rgba(255, 0, 0, 0.5)
.
You can load images from a variety of sources, including buffers, files and URLs. JPEGs, PNGs, GIFs and WebP are supported, and possibly others depending on your environment:
Environment | Sources | Image Formats |
---|---|---|
Node.js | Buffer, File Path, URL | JPEG, PNG, GIF, WebP |
Browser | ArrayBuffer, File, Blob, URL | JPEG, PNG, GIF, BMP, WebP |
To load an image, pass the source (e.g. file path, URL) to the load() method. Please note that this is an asynchronous call, so you need to provide a callback, or you can use the promise / async / await pattern with Node 8+. Example with callback:
let canvas = new CanvasPlus();
canvas.load( 'waterfall.jpg', function(err) {
if (err) throw err;
});
Or with Node 8+:
try {
await canvas.load( 'waterfall.jpg' );
}
catch (err) {
// handle error here
}
In Node.js this example would look for a waterfall.jpg
file on disk in the current directory. In the browser this would be treated as a URL to waterfall.jpg
relative to the current page.
Note that loading images in the browser requires that the file is hosted on the same domain as the page, or is hosted on a server that sends back proper CORS headers. However, if you need to load an image from a 3rd party URL that does not support CORS, you can use the special loadRemote() method. This works well enough, but due to browser security restrictions we will not have access to the raw binary bytes, so features like EXIF data and Auto-Orient are not available.
To save the contents of your canvas to a binary image format, use the write() method. This will compress the image into a JPEG, PNG or GIF, and provide you with a Buffer object. Please note that this is an asynchronous call, so you need to provide a callback, or you can use the promise / async / await pattern with Node 8+. Example with callback:
canvas.write({"format":"jpeg", "quality":90}, function(err, buf) {
if (err) throw err;
// 'buf' will be a binary buffer containing final image
});
Or with async/await:
try {
let buf = await canvas.write({"format":"jpeg", "quality":90});
// 'buf' will be a binary buffer containing final image
}
catch (err) {
// handle error here
}
The format
property should be one of jpeg
, png
, gif
or webp
. The quality
property is only used for JPEGs and WebP, and should be a number from 0
to 100
.
Note that in the browser the buffer is provided using the buffer module, which is is a subclass of Uint8Array. So there is no need to explicitly convert to typed array -- just use the buffer as you would a native Uint8Array.
All filter methods are synchronous, so they do not follow the callback pattern. So by default all filter methods do not throw (however you can enable this behavior if you want, see below). Instead, they set an internal error state which you can query after the fact. Example:
canvas.adjust({
"brightness": -20,
"contrast": 20
});
if (canvas.getLastError()) {
// an error occurred
let err = canvas.getLastError();
}
This works even if you chain multiple filter calls together. CanvasPlus will "abort" the chain on the first error. Example:
canvas.desaturate().normalize().solarize().sepia().rotate(45);
if (canvas.getLastError()) {
// an error occurred
let err = canvas.getLastError();
}
If you would prefer to use try/catch, you can enable throw
mode by calling set() before running any filters:
canvas.set('throw', true);
try {
canvas.adjust({
"brightness": -20,
"contrast": 20
});
}
catch (err) {
// an error occurred
}
To enable basic debug logging, use the set() method to enable the debug
parameter. Example:
canvas.set('debug', true);
Example debug log, which is emitted using console.log()
(and console.error()
for errors):
[DEBUG] Setting property: debug: true
[DEBUG] Loading image data from file: 640x480.jpg
[DEBUG] Loading image from buffer {"size":296673}
[DEBUG] Image ping: {"height":480,"width":640,"type":"jpg"}
[DEBUG] Setting property: width: 640
[DEBUG] Setting property: height: 480
[DEBUG] Setting property: format: jpg
[DEBUG] Setting property: mode: image
[DEBUG] Setting property: origWidth: 640
[DEBUG] Setting property: origHeight: 480
[DEBUG] Setting property: origFormat: jpg
[DEBUG] Image load complete
[DEBUG] Resizing image to target size: 320x240 {"mode":"Fit","gravity":"center","background":"","orig_width":640,"orig_height":480,"target_width":320,"target_height":240,"dest_width":320,"dest_height":240,"canvas_width":320,"canvas_height":240,"x":0,"y":0}
[DEBUG] Setting property: width: 320
[DEBUG] Setting property: height: 240
[DEBUG] Creating new canvas: 320x240 {"background":"(transparent)"}
[DEBUG] Setting property: mode: rgba
[DEBUG] Canvas created successfully
[DEBUG] Image resize complete
[DEBUG] Adjusting image {"brightness":-20,"contrast":20,"hue":0,"saturation":0}
[DEBUG] Image adjustment complete
[DEBUG] Setting property: file: out.png
[DEBUG] Setting property: format: png
[DEBUG] Compressing image to format: png
[DEBUG] Compressing into 32-bit PNG {"compression":9,"filter":"PNG_ALL_FILTERS"}
[DEBUG] PNG compression complete
[DEBUG] Image compression complete {"size":165279}
[DEBUG] Saving to file: out.png
[DEBUG] Image write complete
In Node.js, you can attach a log agent compatible with our pixl-logger module, or write your own that implements the interface. Example of the former:
const Logger = require('pixl-logger');
let columns = ['hires_epoch', 'date', 'hostname', 'component', 'category', 'code', 'msg', 'data'];
let logger = new Logger( 'logs/debug.log', columns );
canvas.attachLogAgent( logger );
Example custom logger implementation:
let logger = {
debug: function(level, msg, data) {
if (data) msg += " (" + JSON.stringify(data) + ")";
console.log('[DEBUG]['+level+'] ' + msg);
},
error: function(code, msg, data) {
if (data) msg += " (" + JSON.stringify(data) + ")";
console.log('[ERROR]['+code+'] ' + msg);
}
};
canvas.attachLogAgent( logger );
The create()
method creates a new blank canvas, or replaces one if it already exists. It accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
width |
Integer | (Required) Canvas width in pixels. |
height |
Integer | (Required) Canvas height in pixels. |
background |
String | Optional background color (any CSS color string). |
Example use:
canvas.create({
width: 640,
height: 480,
background: "#FF0000"
});
The background color can be any CSS-compatible color string, including RGBA, e.g. rgba(255, 0, 0, 0.5)
. If omitted, the canvas is initialized to transparent black.
Canvases are always created in 32-bit RGBA mode.
The load()
method loads an image into CanvasPlus. It accepts buffers, files and URLs. For image formats, JPEGs, PNGs, GIFs and WebP are supported, and possibly others depending on your environment. Please note that this is an asynchronous call, so you need to provide a callback as the 2nd argument. Example use:
canvas.load( 'waterfall.jpg', function(err) {
if (err) throw err;
});
In Node.js this example would look for a waterfall.jpg
file on disk in the current directory. In the browser this would be treated as a URL to waterfall.jpg
relative to the current page.
Alternatively you can import an existing canvas using importCanvas(), or an image object using importImage().
By default, CanvasPlus will automatically orient (rotate) your image on load, so it is always right-side up. This is accomplished by detecting the image's EXIF data, if present, and reading the special orientation flag. Many digital cameras do not fix image rotation when shots are taken, and instead simply include an EXIF flag in the files, indicating the camera's orientation.
If you do not want this behavior for some reason, you can disable the feature by calling set() before loading your image, and disabling the autoOrient
parameter. Example:
let canvas = new CanvasPlus();
canvas.set('autoOrient', false);
canvas.load( 'waterfall.jpg', function(err) {
if (err) throw err;
});
The loadRemote()
method is a special, browser-only API, which allows you to load 3rd party image URLs. That is, images that are hosted on 3rd party domains, and do not provide proper CORS headers. Example use:
let url = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/1024px-HTML5_logo_and_wordmark.svg.png";
canvas.loadRemote( url, function(err) {
if (err) throw err;
});
Note that due to browser security restrictions we have no access to the raw binary bytes, so features like EXIF data and Auto-Orient are not available when using loadRemote()
.
The write()
method will compress your canvas into a JPEG, PNG, GIF or WebP, and provide you with a Buffer object. Please note that this is an asynchronous call, so you need to provide a callback.
canvas.write({"format":"jpeg", "quality":90}, function(err, buf) {
if (err) throw err;
// 'buf' will be a binary buffer containing final image
});
The format
property should be one of jpeg
, png
, gif
or webp
. The quality
property is only used for JPEGs and WebP, and should be a number from 0
to 100
.
Note that in the browser the buffer is provided using the buffer module, which is is a subclass of Uint8Array. So there is no need to explicitly convert to typed array. Just use the buffer as you would a native Uint8Array.
The clone()
method makes a copy of your canvas, including the raw pixel data, settings and all. This is a synchronous call, and the new cloned object is returned. Example use:
let copy = canvas.clone();
All filter methods are synchronous, and do not require a callback. You can also chain them together, like this:
canvas.desaturate().brightness(-15).contrast(30).invert();
Live Demo: Adjust Brightness/Contrast
The adjust()
method allows you to adjust the image hue, saturation, brightness, and/or contrast. It accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
hue |
Integer | Adjust hue (-360 to 360). |
saturation |
Integer | Adjust saturation (-255 to 255). |
brightness |
Integer | Adjust brightness (-255 to 255). |
contrast |
Integer | Adjust contrast (-255 to 255). |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.adjust({
"brightness": -15,
"contrast": 30
});
The following method shortcuts are provided for convenience:
Method Name | Example |
---|---|
hue |
canvas.hue( 180 ); |
saturation |
canvas.saturation( -128 ); |
brightness |
canvas.brightness( 64 ); |
contrast |
canvas.contrast( 32 ); |
Note: If you want to completely desaturate an image, check out the desaturate() filter below, as it is considerably faster than using adjust()
.
Live Demo: Blur
The blur()
method applies a simple box blur to your image, using a custom convolution kernel. This is a fast blur filter, but isn't particularly high quality. It accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
amount |
Integer | (Required) Blur amount (2 to 100). |
edges |
String | How to calculate out-of-bounds pixels, can be one of repeat , wrap , mirror or discard (case-insensitive). Defaults to repeat . |
channels |
String | Which channels to apply the filter to, defaults to rgba . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.blur({
"amount": 5
});
See Also: gaussianBlur()
Live Demo: Border
The border()
method draws a border around your canvas, with a custom size and color. It accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
size |
Integer | (Required) Size of the border in pixels. |
color |
String | (Required) Color of the border (any CSS color string). |
mode |
String | (Required) Border render mode (inside , center or outside ). |
The mode can be one of inside
(draw on top of edge pixels), outside
(expand canvas by border size), or center
(draw border half inside, half outside). The latter two modes will actually expand the canvas to accommodate the border.
Example use:
canvas.border({
"size": 5,
"color": "#FF0000",
"mode": "inside"
});
Note that transparent colors aren't supported for this filter.
Live Demo: Composite
The composite()
method superimposes a separate image or canvas atop the current one. The source can be another CanvasPlus object, a native HTML5 Canvas object, or a native Image object. Several parameters are provided for positioning, scaling and setting the compositing mode. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
image |
Object | (Required) Source image or canvas to superimpose atop current canvas. |
width |
Integer | Optional desired width, will trigger a resize during composite. |
height |
Integer | Optional desired height, will trigger a resize during composite. |
gravity |
String | Image starting position (alignment), which can be further adjusted with margins and/or offsets. See Gravity. |
marginX |
Integer | Horizontal margin in pixels, defaults to 0 . |
marginY |
Integer | Vertical margin in pixels, defaults to 0 . |
offsetX |
Integer | Horizontal offset in pixels, defaults to 0 . |
offsetY |
Integer | Vertical offset in pixels, defaults to 0 . |
opacity |
Float | Image opacity (0.0 to 1.0), defaults to 1.0 . |
mode |
String | Composite rendering mode, see globalCompositeOperation, defaults to source-over . |
antialias |
String | Image scaling quality, one of best , good , fast or nearest . Defaults to best . See Anti-Aliasing. |
Example use:
canvas.composite({
"image": my_other_canvas,
"gravity": "northwest",
"marginX": 10,
"marginY": 10
});
Note that when width
and/or height
are specified here, the image is resized in exact (scale) mode.
Live Demo: 3x3 Sharpen Kernel
The convolve()
method applies a custom convolution kernel to the canvas. This is used to apply effects such as blur, sharpen, emboss, find edges, and more. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
matrix |
Array | (Required) An array of numbers for the convolution matrix. The length should be a perfect square (e.g. 9, 16, 25). |
offset |
Integer | Optionally offset the destination channel values, causing a brightening effect (used by emboss). |
edges |
String | How to calculate out-of-bounds pixels, can be one of repeat , wrap , mirror or discard . Defaults to repeat . |
channels |
String | Which channels to apply the filter to, defaults to rgba . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.convolve({
"matrix": [ // 3x3 sharpen
0, -1, 0,
-1, 5, -1,
0, -1, 0
]
});
Live Demo: Square Earth
The crop()
method crops the canvas down to the specified size, at the specified coordinates (top-left corner). All values are specified in pixels. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
x |
Integer | (Required) The coordinate of the left side of the crop rectangle. |
y |
Integer | (Required) The coordinate of the top side of the crop rectangle. |
width |
Integer | (Required) The width of the crop rectangle. |
height |
Integer | (Required) The width of the crop rectangle. |
Example use:
canvas.crop({
"x": 50,
"y": 50,
"width": 200,
"height": 200
});
Note that cropping regenerates the underlying canvas object. It effectively creates a new canvas at the final cropped size, then copies the source into the destination, then discards the source.
See Also: trim()
Live Demo: Increase mid-tones (see below for more)
The curves()
method applies a tonality curve to all the pixels in your canvas. You need only provide key points, and all the curve values are interpolated using monotone cubic interpolation (similar to how Photoshop does it). Curves are used to implement all kinds of common image filters, including gamma adjustment, inversion, posterize, solarize, normalize, sepia, and color temperature. But here you can supply your own custom curve by specifying key points, including different curves per channel if desired. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
rgb |
Array | An array of curve points for the red, green and blue channels together (same curve applied to all). |
red |
Array | An array of curve points for the red channel specifically. |
green |
Array | An array of curve points for the green channel specifically. |
blue |
Array | An array of curve points for the blue channel specifically. |
alpha |
Array | An array of curve points for the alpha channel specifically. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
You can specify the curve points in two ways. First, a simple one-dimensional array of "Y" axis values (each 0 - 255), which are evenly distributed over the "X" axis, from darkest to lightest. And second, an array of X/Y pairs specifying a more precise curve. Example of the former:
canvas.curves({
"rgb": [0, 191, 255] // increase mid-tones
});
To better explain how this works, here is a table with some common curves, a visualization, and a live demo of each:
Curve Points | Visualization | Description | Demo Link |
---|---|---|---|
[0, 255] |
Baseline (no change) | Live Demo | |
[63, 255] |
Increase brightness | Live Demo | |
[0, 127] |
Decrease brightness | Live Demo | |
[0, 0, 255, 255] |
Increase contrast | Live Demo | |
[63, 191] |
Decrease contrast | Live Demo | |
[0, 191, 255] |
Increase mid-tones | Live Demo | |
[0, 63, 255] |
Decrease mid-tones | Live Demo | |
[0, 96, 160, 216, 255] |
Increase shadow detail | Live Demo | |
[255, 0] |
Invert image | Live Demo | |
[0, 127, 0] |
Solarize effect | Live Demo | |
[0, 255, 0, 255] |
Alice in Wonderland | Live Demo |
You can also specify different curves to each channel. For example, here is one way to apply a sepia tone effect to an image:
canvas.desaturate().curves({
"green": [0, 108, 255],
"blue": [0, 64, 255]
});
As mentioned above, you can alternatively specify an array of X/Y pairs for the key points, to describe a more precise curve. Here is an example of that:
canvas.curves({
"rgb": [
[0, 0],
[32, 192],
[255, 255]
]
});
Note that if your point array contains exactly 256 elements, and is a simple one-dimensional array of integers, then no interpolation is performed, and the points are mapped exactly to each value as specified.
Live Demo: Desaturate
The desaturate()
method removes all color from your canvas, resulting in a pure grayscale image. Instead of averaging the channels together, they are weighted in such a way that the grayscale output is more natural-looking (see relative luminance).
This is similar to calling adjust() and setting the saturation
to -255
, however desaturate()
is much faster. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.desaturate();
Live Demo: Draw Primitives
The draw()
method allows you to draw primitive geometric objects onto the canvas, such as rectangles, lines, or custom shapes. This is basically just a convenience wrapper around the HTML5 Canvas drawing API. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
params |
Object | Optional parameters to set on the canvas context, e.g. fillStyle , strokeStyle . |
commands |
Object | An array of commands to run on the canvas context, e.g. ['fill'] . |
antialias |
String | Draw rendering quality, one of best , good , fast or nearest . Defaults to best . See Anti-Aliasing. |
Here is an example:
canvas.draw({
params: {
fillStyle: '#888888',
strokeStyle: '#000000',
lineWidth: 2
},
commands: [
['rect', 50, 50, 100, 100],
['fill'],
['stroke']
]
});
This example would draw a filled + stroked rectangle onto the canvas. The params
object can contain any properties you want to set on the canvas context before running the draw commands (i.e. colors, stroke width, line caps, etc.), and the commands
array contains each draw call to make. Each element in the commands
array should be a nested array containing the draw call name (e.g. rect
), followed by any additional arguments required by the call, such as coordinates for a rectangle, or points for a moveTo / lineTo.
Using this syntax, you can draw complex shapes by calling any of the HTML5 Canvas API draw methods. Here is an example which draws a filled triangle:
canvas.draw({
params: {
fillStyle: '#000000'
},
commands: [
['beginPath'],
['moveTo', 75, 70],
['lineTo', 100, 75],
['lineTo', 100, 25],
['fill']
]
});
Convenience properties are provided for drawing simple rectangles and lines, to make the syntax a little easier for common use cases. Here is the list of optional properties:
Property Name | Type | Description |
---|---|---|
line |
Array | An array of 4 coordinates, the first two being the X/Y of the line start point, and the latter two being the X/Y of the line end point. |
rect |
Array | An array of 4 coordinates, the first two being the X/Y of the rectangle's top-left corner, and the latter two being the width and height. |
fill |
String | Setting this to a color will set the canvas context fillStyle property, and append a ['fill'] command to the end of the list. |
stroke |
String | Setting this to a color will set the canvas context strokeStyle property, and append a ['stroke'] command to the end of the list. |
Examples:
canvas.draw({
"rect": [ 75, 75, 200, 200 ],
"fill": "#888888",
"stroke": "#000000"
});
canvas.draw({
"line": [ 150, 100, 350, 300 ],
"stroke": "#ff0000"
});
Live Demo: Emboss
The emboss()
method applies an emboss filter to the canvas, which is implemented using a 3x3 convolution kernel. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.emboss();
This is equivalent to calling desaturate(), followed by convolve() with the following parameters:
canvas.desaturate().convolve({
matrix: [2, 0, 0, 0, -1, 0, 0, 0, -1],
offset: 127,
edges: 'repeat',
channels: 'rgb'
});
Live Demo: Expand
The expand()
method increases the canvas size, while not resizing the image itself. This is essentially a "reverse crop". You can specify an alignment (gravity) for positioning the existing image on the expanded canvas, and an optional background color. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
width |
Integer | Number of pixels to increase the width by (this is a delta). |
height |
Integer | Number of pixels to increase the height by (this is a delta). |
gravity |
String | Image position (alignment) of pre-existing pixels on new expanded canvas. See Gravity. |
background |
String | Background color for canvas expansion (any CSS color string). Fully transparent if omitted. |
Example use:
canvas.expand({
"width": 100,
"height": 100,
"gravity": "center"
});
This would increase the canvas size by 100px horizontally and vertically, essentially adding a 50px transparent border on all sides of the image.
Note that expanding regenerates the underlying canvas object. It effectively creates a new canvas at the expanded size, then copies the source into the destination, then discards the source.
Live Demo: Exposure
The exposure()
method simulates increasing or decreasing the camera film exposure. Internally, this is accomplished by a special curve. To use, specify an amount
between -100 (darken) and 100 (lighten). The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
amount |
Integer | Exposure adjustment, from -100 (darken) to 100 (lighten). |
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.exposure({
"amount": 25
});
Note that if amount
is the sole argument, you can simply pass the number itself:
canvas.exposure( 25 );
Live Demo: Find Edges
The findEdges()
method applies a Sobel operator to the canvas, resulting in a strong highlight of all the apparent edges in the image. This uses a 3x3 convolution kernel. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.findEdges();
This is equivalent to calling desaturate(), followed by convolve() with the following parameters:
canvas.desaturate().convolve({
matrix: [-1, -1, -1, -1, 8, -1, -1, -1, -1],
offset: 0,
edges: 'repeat',
channels: 'rgb'
});
Live Demo: Flatten
The flatten()
method effectively removes the alpha channel from your canvas, resulting in a fully opaque image. Of course, the alpha channel still technically exists, as all canvases are RGBA, but this allows you to remove all transparency. You can choose to place a background color matte behind the image, or simply set the direct alpha values to 255. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
background |
String | Optional background color (any CSS color string). |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.flatten({
"background": "#000000"
});
When a background
is specified, the underlying canvas object is regenerated. The library effectively creates a new canvas with the specified background color, superimposes the source onto the destination, then discards the source.
If you omit the background
property (or it evaluates to false
) then the alpha pixel values are all set to 255 in a direct sense. This may expose otherwise "hidden" visual elements in your image, depending on how it was encoded.
Note that flattening the canvas sets an internal alpha
flag to false
, which indicates that PNG and GIF output should not contain any alpha information. You can manually get/set this flag using get() and set(), although you should rarely need to.
Live Demo: Gamma Adjust
The gamma()
method applies a gamma correction curve to your canvas. Values less than 1.0 will lighten the image, whereas values greater than 1.0 will darken it. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
amount |
Float | Gamma correction value, typically in the range 0.25 to 4.0 . The default value is 1.0 (no change). |
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.gamma({
"amount": 0.5
});
Live Demo: Gaussian Blur
The gaussianBlur()
method applies a Gaussian blur to your image, using a custom convolution kernel. This is a rather computationally expensive filter, but is much higher quality than the standard blur. It accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
amount |
Integer | (Required) Blur amount (3 to 100). |
sigma |
Integer | Gaussian sigma operator (defaults to amount / 3 ). |
edges |
String | How to calculate out-of-bounds pixels, can be one of repeat , wrap , mirror or discard (case-insensitive). Defaults to repeat . |
channels |
String | Which channels to apply the filter to, defaults to rgba . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
The amount
is really just the size of one dimension of the Gaussian convolution matrix (it is squared to compute the full matrix size), so the larger the amount
the slower the operation is. The sigma
actually controls the amount of blurring that occurs. For best results, set sigma
to 1/3 of amount
(this is the default behavior if sigma
is omitted).
Example use:
canvas.gaussianBlur({
"amount": 9
});
Live Demo: Invert
The invert()
method inverts all colors in the image (i.e. creates a photographic negative), or can be restricted to certain specified channels. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.invert();
Inversion is implemented using a curve, and is functionally equivalent to calling:
canvas.curves({
"rgb": [255, 0]
});
Live Demo: Shadow Detail
The lighting()
method can adjust both shadows and highlights, to bring out hidden details. Internally, this is accomplished by a special multi-point curve. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
shadows |
Integer | Shadow detail adjustment, from -100 (darken) to 100 (lighten). |
highlights |
Integer | Highlight detail adjustment, from -100 (darken) to 100 (lighten). |
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.lighting({
"shadows": 25
});
Live Demo: Apply Mask
The mask()
method applies a separate image or canvas as a mask onto the current one. Meaning, the alpha channel of one is applied to the other. The source can be another CanvasPlus object, a native HTML5 Canvas object, or a native Image object. Several parameters are provided for positioning and scaling the mask. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
image |
Object | (Required) Source image or canvas to apply as a mask. |
width |
Integer | Optional desired width, will trigger a resize during composite. |
height |
Integer | Optional desired height, will trigger a resize during composite. |
gravity |
String | Image starting position (alignment), which can be further adjusted with margins and/or offsets. See Gravity. |
marginX |
Integer | Horizontal margin in pixels, defaults to 0 . |
marginY |
Integer | Vertical margin in pixels, defaults to 0 . |
offsetX |
Integer | Horizontal offset in pixels, defaults to 0 . |
offsetY |
Integer | Vertical offset in pixels, defaults to 0 . |
opacity |
Float | Mask opacity (0.0 to 1.0), defaults to 1.0 . |
antialias |
String | Mask scaling quality, one of best , good , fast or nearest . Defaults to best . See Anti-Aliasing. |
Example use:
canvas.mask({
"image": my_mask_image,
"gravity": "center"
});
Masking is effectively the same as calling composite() with the mode
set to destination-in
.
Note that when width
and/or height
are specified, the mask is resized in exact (scale) mode.
Live Demo: Normalize
The normalize()
method stretches the image contrast to cover the entire range of possible values. So for example, if the image never reaches full back and/or full white, this will increase the contrast so both sides are maxed. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.normalize();
Normalize is implemented by requesting a histogram to locate the white and black points, and then applying a custom curve.
Live Demo: Half Transparent
The opacity()
method fades the canvas out towards pure transparent, or a custom background color. The method accepts an opacity float, or an object containing the following properties:
Property Name | Type | Description |
---|---|---|
opacity |
Float | The target image opacity from 0.0 (transparent) to 1.0 (opaque). |
background |
String | Optional background color, defaults to transparent. |
Example use:
canvas.opacity({
opacity: 0.5,
background: 'red'
});
If you are only specifying opacity (no background), you can just pass the number as the sole argument:
canvas.opacity( 0.5 );
Live Demo: Posterize
The posterize()
method reduces the image to a limited number of color levels per channel. Smaller level amounts have the most obvious effect. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
levels |
Integer | (Required) Number of levels to allow per channel. |
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.posterize({
"levels": 4
});
Posterize is implemented using a custom stair-step curve.
Live Demo: Quantize
The quantize()
method reduces your image to a fixed number of unique colors, then generates a palette and converts the canvas into indexed mode. This is used for saving GIFs and indexed PNGs. We rely on the awesome image-q library here, which is slow but produces beautiful results. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
colors |
Integer | (Required) Number of unique colors in the palette (2 - 256). |
dither |
Boolean | Dither option for quantization. Defaults to false (disabled). |
ditherType |
String | Dithering algorithm for quantization. Defaults to FloydSteinberg . |
Example use:
canvas.quantize({
"colors": 32,
"dither": true
});
Quantize should always be the last filter applied before calling write(). Calling any other filters after quantization will convert the canvas back to RGBA mode, which is likely not what you want.
The supported dithering algorithms are FloydSteinberg
, Stucki
, Atkinson
, Jarvis
, Burkes
, and Sierra
. They each have different trade-offs, and produce slightly different results. See Dithering Algorithms for more information.
Note that quantization includes and preserves alpha transparency in the palette, unless you first flatten your canvas.
Live Demo: Quantize Fast
The quantizeFast()
method works similarly to quantize(), in that it reduces your image to a fixed number of unique colors, then generates a palette and converts the canvas into indexed mode. However, this algorithm is approximately 10X faster. It does this by "crushing" (a.k.a posterizing) the image, optionally dithering it at the same time, then simply building a palette of all unique colors. This generally produces lower quality results than quantize() (depending on the image), but at a fraction of the CPU cost. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
colors |
Integer | Target number of colors in the palette, defaults to 256 . |
crushRGB |
Integer | Optional starting number of posterization levels for the RGB channels, defaults to 16 . |
crushAlpha |
Integer | Optional starting number of posterization levels for the Alpha channel, defaults to 16 . |
dither |
Boolean | Optional pattern dither, defaults to false (disabled). |
Example use:
canvas.quantizeFast({
"colors": 256,
"dither": true
});
The Quantize Fast algorithm will begin by crushing the image at the specified (or default) posterize levels, and see if the target number of colors was achieved. If not, it will crush further by reducing the posterization levels, and keep trying until the palette size is equal to or less than the colors
parameter. To optimize for speed, depending on the image and with possible quality loss, you can change the starting crushRGB
and/or crushAlpha
to values below 16.
It is important to note that Quantize Fast may not achieve the exact number of colors you want in your palette, but it tries to get as close as possible without going over. The algorithm builds its own palette based on the total unique colors after "crushing" (posterizing) the image. You can control the level of posterization of course, and adjust it differently for the RGB and Alpha channels. The higher the crushRGB
and crushAlpha
values are, the more unique colors are produced. Also, while dithering is provided here as an option, only a pattern dither is supported.
Note that quantization includes and preserves alpha transparency in the palette, unless you first flatten your canvas.
Live Demo: Resize
The resize()
method scales the canvas to the desired pixel width and/or height. Several modes are available, to control how your image is scaled to fit the target dimensions. You can also control the gravity (alignment), direction (e.g. shrink only, enlarge only), and anti-alias settings. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
width |
Integer | Desired canvas width in pixels. Can be omitted if height is specified. |
height |
Integer | Desired canvas height in pixels. Can be omitted if width is specified. |
mode |
String | Resize mode, one of fit , fitPad , fitOver or scale (case-insensitive). Defaults to fit . See below. |
background |
String | Background padding color, for fitPad mode only. |
gravity |
String | Image alignment, which can be further adjusted with offsets. See Gravity. |
direction |
String | Resize direction restriction, one of shrink , enlarge , or both . Defaults to both . |
antialias |
String | Resize scaling quality, one of best , good , fast or nearest . Defaults to best . See Anti-Aliasing. |
offsetX |
Integer | Horizontal offset in pixels, defaults to 0 . |
offsetY |
Integer | Vertical offset in pixels, defaults to 0 . |
delta |
Boolean | Interpret width and height as delta offsets, not absolute values. Defaults to false . |
Example use:
canvas.resize({
"width": 640,
"height": 480,
"mode": "fit"
});
If either width
or height
are omitted, they will be extrapolated from the other, maintaining the original aspect ratio. Here is an example which scales the canvas down to 50% width, maintaining its aspect ratio:
canvas.resize({
"width": canvas.get('width') / 2
});
Actually, you can just specify percentage as a string with a %
suffix, which has the same effect:
canvas.resize({
"width": "50%"
});
Here is a description of all the available resize modes:
Resize Mode | Alias | Description |
---|---|---|
fit |
contain |
The default mode. Will resize so the image "fits" into the destination width/height without exceeding either value, and maintaining the original aspect ratio. Similar to the CSS background-size: contain rule. |
fitPad |
letterbox |
This mode scales the image similar to fit , but then adds background padding to fill in the extra area (if any), so the final image size exactly matches your desired dimensions, but the original image aspect ratio is also preserved. This is similar to film letterboxing. The background property controls the padding color, and gravity controls the image alignment. |
fitOver |
cover |
This mode scales the image in such a way that it fits "over" the destination width/height, covering the entire area. The aspect ratio is maintained, and the extra content is cropped off. This is similar to the CSS background-size: cover rule. |
scale |
exact |
This mode scales the image to your exact specifications, ignoring the aspect ratio, and distorting as needed. |
Note that resizing regenerates the underlying canvas object. It effectively creates a new canvas at the target size, then copies the source into the destination, applying the resize at the same time, then finally discards the source.
Live Demo: Sepia Tone
The sepia()
method applies a sepia tone filter to your image, using a custom curve. This is a commonly used filter which gives a photograph an "old western" look. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example:
canvas.sepia();
This is equivalent to calling desaturate(), followed by curves() using the following properties:
canvas.desaturate().curves({
green: [0, 108, 255],
blue: [0, 64, 255]
});
Live Demo: Sharpen
The sharpen()
method applies a simple sharpen effect to your image, using a custom 3x3 convolution kernel. It accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
edges |
String | How to calculate out-of-bounds pixels, can be one of repeat , wrap , mirror or discard (case-insensitive). Defaults to repeat . |
channels |
String | Which channels to apply the filter to, defaults to rgba . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.sharpen({
"edges": "repeat"
});
This is equivalent to calling convolve() with the following properties:
canvas.convolve({
"matrix": [0, -1, 0, -1, 5, -1, 0, -1, 0],
"edges": "repeat"
});
Live Demo: Solarize
The solarize()
method applies a solarisation filter to your canvas, which basically inverts all colors above half brightness. This can be restricted to certain specified channels if desired. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.solarize();
Solarisation is implemented using a custom sawtooth inverted-V curve, and is similar to calling:
canvas.curves({
"rgb": [0, 127, 0]
});
However, the solarize()
method uses a 256-element curve array, with an exact sawtooth inverted-V shape.
Live Demo: Color Temperature
The temperature()
method simulates increasing or decreasing the image's color temperature. Increasing the temperature produces a "warmer" (redder) image, and decreasing it produces a "cooler" (bluer) image. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
amount |
Integer | Amount to increase or decrease temperature. Range is -255 to 255 . |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.temperature({
"amount": -128
});
This is a very simple implementation of color temperature, which applies a curve to increase the red or blue channels in a linear fashion, based on the supplied amount
. This filter is only for visual effect, and doesn't pertain to any photographic or color standard. I'm sure there are much better and more accurate algorithms out there.
Live Demos: Basic Meme, Auto-Shrink, Word-Wrap, Shrink-Wrap
The text()
method renders text onto your canvas. Beyond the built-in features of the HTML5 Canvas Text API, we provide both character and line spacing, automatic scaling to fit, word-wrap, and the almighty paragraph word-wrap + auto-shrink combo. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
text |
String | (Required) Actual text to render (can be multi-line). |
font |
String | (Required) Font file path (Node.js) or font family name (browser). See Fonts. |
size |
Integer | (Required) Text size, measured in points (often equivalent to height in pixels, depending on font family). |
color |
String | (Required) Text color (any CSS color string). |
background |
String | Optional background color (any CSS color string), defaults to transparent. |
gravity |
String | Text alignment, which can be further adjusted with margins and/or offsets. See Gravity. |
overflow |
String | How to handle text overflow. See Text Overflow. |
marginX |
Integer | Horizontal margin in pixels, defaults to 0 . |
marginY |
Integer | Vertical margin in pixels, defaults to 0 . |
offsetX |
Integer | Horizontal offset in pixels, defaults to 0 . |
offsetY |
Integer | Vertical offset in pixels, defaults to 0 . |
maxWidth |
Integer | Maximum text render width in pixels, defaults to canvas width (minus margins). |
maxHeight |
Integer | Maximum text render height in pixels, defaults to canvas height (minus margins). |
characterSpacing |
Float | Adjust horizontal space between each character, with sub-pixel precision. Specify values less than 0 to condense, and greater than 0 to expand. |
lineSpacing |
Float | Adjust vertical space between each line, with sub-pixel precision. Specify values less than 0 to condense, and greater than 0 to expand. |
shadowColor |
String | Optional shadow color (any CSS color string), defaults to empty string (disabled). |
shadowOffsetX |
Integer | When shadowColor is specified, this controls the horizontal offset of the shadow (default to 0). |
shadowOffsetY |
Integer | When shadowColor is specified, this controls the vertical offset of the shadow (defaults to 0). |
shadowBlur |
Integer | When shadowColor is specified, this controls the shadow blur amount (defaults to 0). |
outlineColor |
String | Optional text outline color (any CSS color string), defaults to empty string (disabled). |
outlineThickness |
Integer | When outlineColor is specified, this controls the thickness of the outline. |
outlineStyle |
String | When outlineColor is specified, this controls the style of the outline (see lineJoin. |
autoCrop |
String | Optionally crop canvas down to rendered text size, can be horiz , vert or both . Defaults to disabled. |
opacity |
Float | Text opacity (0.0 to 1.0), defaults to 1.0 . |
mode |
String | Text rendering mode, see globalCompositeOperation, defaults to source-over . |
When specifying fonts via the font
property, please note that things are handled differently if you are running in a browser, vs. using Node.js on the server-side. For Node.js, you need to specify a filesystem path to the font file, in either OpenType (OTF) or TrueType (TTF) format, and preload the font using loadFont()
before creating the canvas (see below). The font weight and style are expected to be embedded into the file itself, e.g. Arial-Narrow-bold.otf
or Helvetica-bold-italic.ttf
. Example:
// Node.js text example
let canvas = new CanvasPlus();
canvas.loadFont( "/path/to/fonts/Arial-Narrow-bold.otf" ); // do this first!
canvas.create({
width: 640,
height: 480,
background: "#FFFFFF"
});
canvas.text({
"text": "Hello there, Node.js!",
"font": "/path/to/fonts/Arial-Narrow-bold.otf",
"size": 48,
"color": "#ffffff",
"gravity": "south",
"marginX": 10,
"marginY": 10,
"outlineColor": "#000000",
"outlineThickness": 4
});
Note that you must load all fonts before creating the canvas in which they will be used. This is a limitation of node-canvas v2. See registerFont for details.
When running CanvasPlus in the browser, things are a bit different. You must specify a CSS font family instead, e.g. Arial Narrow
. This can be any built-in system font, or a custom web font that you loaded previously (make sure it is completely loaded before using in CanvasPlus). In addition, if you want to specify a font weight or style, please use the special browser-only fontWeight
and/or fontStyle
properties, respectively. Example:
// Browser-specific text example
canvas.text({
"text": "Hello there, browser!",
"font": "Arial Narrow", // CSS font-family
"fontWeight": "bold", // CSS font-weight
"fontStyle": "normal", // CSS font-style
"size": 48,
"color": "#ffffff",
"gravity": "south",
"marginX": 10,
"marginY": 10,
"outlineColor": "#000000",
"outlineThickness": 4
});
As a convenience, a special version of loadFont()
is made available for use in the browser. It accepts a font family name, a URL, and an optional callback. The callback will be fired when the font is completely loaded and ready to use. Example:
// browser font loading example
canvas.loadFont( "Open Sans", "fonts/open-sans-regular.woff2", function(err) {
if (err) throw err;
// font is ready to use!
} );
When your text does not fit into the available width/height (this is the canvas size minus margins and adjusted by maxWidth
and/or maxHeight
), you have several options on how to handle the overflow. The behavior is controlled by the overflow
property, which accepts one of the following values:
Overflow Value | Description |
---|---|
"" |
The default behavior is to take no action on overflow, and simply render the text outside the bounds. |
"shrink" |
This mode will automatically shrink the text until it fits, both horizontally and vertically. |
"wrap" |
This mode will apply automatic word-wrapping to prevent horizontal overflow (it disregards vertical). |
"shrinkWrap" |
This mode will apply both word-wrapping and auto-scale to provide the best fit into the available width/height. |
Example use:
canvas.text({
"text": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"font": "/path/to/fonts/Arial-italic.otf",
"size": 12,
"color": "rgba(255, 0, 0, 1.0)",
"gravity": "northwest",
"overflow": "shrinkwrap",
"marginX": 10,
"marginY": 10
});
Note that while shrink
is very fast, both wrap
and shrinkWrap
are rather computationally expensive operations, because they need to flow your text into a smaller space that it was designed for. shrinkWrap
in particular is extremely expensive, because it has to constantly re-flow and re-wrap the text as it shrinks, and also it cannot always provide perfectly accurate fits (it always errs on the small side tho). You can help these algorithms by providing a starting font size
property that is "somewhat close" to what you will think will fit. This way it doesn't have to work so hard.
Also note that when a single word cannot fit on a line, it is broken into pieces. Automatic hyphenation is not supported, nor is "justify".
One final gotcha is that both the wrapper algorithms require that the text rendered at your provided base size
must fit at least one character from your text, or else the entire operation aborts.
Live Demos: Color Threshold, Black & White Threshold
The threshold()
method applies a threshold filter to your image, which essentially minimizes or maximizes the value of selected channels in each pixel, if the value is above or below a threshold level. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
level |
Integer | Threshold level. Range is 0 to 255 . |
channels |
String | Which channels to apply the filter to, defaults to rgb . See Channels. |
clip |
Object | Optional clipping rectangle (see Clipping below). |
Example use:
canvas.threshold({
"level": 128,
"channels": "rgb"
});
For a more classic black & white threshold effect, simply call desaturate() before thresholding:
canvas.desaturate().threshold({
"level": 128,
"channels": "rgb"
});
Live Demos: Rotate 45 Degrees, Custom Matrix
The transform()
method applies transformations to your canvas, which can include rotation, horizontal or vertical flipping, or a custom 3x3 transformation matrix. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
rotate |
Float | Optional degrees to rotate image. |
fliph |
Boolean | Optional flip horizontal transformation. |
flipv |
Boolean | Optional flip vertical transformation. |
matrix |
Array | Optional 3x3 transformation matrix, for custom distortions. |
background |
String | Background color to use when expanding canvas, defaults to transparent. |
antialias |
String | Image scaling quality, one of best , good , fast or nearest . Defaults to best . See Anti-Aliasing. |
fixed |
Boolean | Set this to true to keep the canvas dimensions fixed, instead of auto-expanding them. Only applicable for rotation. |
Example use:
canvas.transform({
"rotate": 45
});
Only one transformation is allowed per call to transform()
.
Note that when rotating, the canvas dimensions are automatically adjusted so the rotated image fits exactly into the new container. If you do not want this behavior, set the fixed
property to true
.
The following transform method shortcuts are provided for convenience:
Method Name | Example |
---|---|
rotate |
canvas.rotate( 45 ); |
flipHorizontal |
canvas.flipHorizontal(); |
flipVertical |
canvas.flipVertical(); |
Live Demo: Trim Transparent Edges
The trim()
method automatically crops the canvas based on a custom color if provided, or the value of the top-left pixel if not. For example, if the top-left corner pixel is pure transparent, then all transparent pixels are cropped from all sides. You can also specify a tolerance
to allow similar colors from being trimmed. The method accepts an object containing the following properties:
Property Name | Type | Description |
---|---|---|
color |
String | Optional color to trim. If omitted, will use top-left corner pixel. |
tolerance |
Integer | How much pixel value difference to allow when trimming, from 0 to 255 . Defaults to 0 . |
Example (will trim based on top-left pixel):
canvas.trim();
Example with specific trim color and tolerance:
canvas.trim({
color: "rgba(255, 0, 0, 1.0)",
tolerance: 32
});
This filter will silently fail if no pixels are available for trimming, or if it is about to trim the entire image.
The getLastError()
method returns the last error that occurred, or null
if the last operation was a success.
See Errors for details.
The getMetrics()
method returns an object that contains performance metrics for all operations that have taken place. This uses the pixl-perf module, and all metrics are in milliseconds. The format is as follows:
{
"scale": 1000,
"perf": {
"total": 217.399,
"download": 3.7,
"read": 8.8,
"resize": 0.6,
"adjust": 61.299,
"convolve": 41.6,
"curves": 15.3,
"desaturate": 11.3,
"write": 18.1
},
"counters": {
"bytes_read": 1040704,
"bytes_written": 82510
}
}
The getPerf()
method returns the current performance tracking object, which is from the pixl-perf module. This is so you can add your own metrics to it.
The getDimensions()
method returns an object with the current canvas width
and height
in pixels. Example:
{
"width": 640,
"height": 480
}
The getEXIF()
method returns an object containing all the parsed EXIF metadata obtained from the image when it was first loaded. This is only applicable if you loaded an image (as opposed to a blank canvas), and it also only works for JPEG images. Example snippet of data:
{
"Make": "Apple",
"Model": "iPhone 7",
"Orientation": 1,
"XResolution": 72,
"YResolution": 72,
"ResolutionUnit": 2,
"Software": "10.3.1",
"DateTime": "2017:04:29 16:36:23",
...
}
We use the exifreader module for parsing this data.
Please note that if you want to capture EXIF data from images, you should do it immediately after loading. Any transforms or filters that rebuild the canvas will scrub the EXIF data.
The getCanvas()
method returns the underlying HTML5 Canvas object currently in use by CanvasPlus. Please note that this is only applicable in RGBA mode (see Modes below), and that the canvas is often discarded and rebuilt by many filters and transforms.
The getContext()
method returns the underlying CanvasRenderingContext2D object currently in use by CanvasPlus. Please note that this is only applicable in RGBA mode (see Modes below), and that the context is often discarded and rebuilt by many filters and transforms.
The getPixels()
method returns a direct pointer to the raw pixels in the current canvas. The format varies depending on the current mode (see Modes below).
In rgba
mode, getPixels()
returns a Uint8ClampedArray of RGBA pixels, obtained from the data
property of the current canvas ImageData. Each element in the array is one 8-bit channel value of one pixel, and every 4 elements is one full 32-bit pixel. Example:
let width = canvas.get('width');
let height = canvas.get('height');
let data = canvas.getPixels();
let offset = 0;
for (let y = 0; y < height; y++) {
// foreach row
for (let x = 0; x < width; x++) {
// for each pixel
let red = data[ offset + 0 ];
let green = data[ offset + 1 ];
let blue = data[ offset + 2 ];
let alpha = data[ offset + 3 ];
offset += 4;
} // x loop
} // y loop
In indexed
mode, getPixels()
returns a Uint8Array of 8-bit palette indexes. Each value will be between 0 and 255, representing an index in the palette array. See getPalette() below. Example:
canvas.quantize({ colors: 256, dither: true });
let width = canvas.get('width');
let height = canvas.get('height');
let pixels = canvas.getPixels();
let colors = canvas.getPalette();
let offset = 0;
for (let y = 0; y < height; y++) {
// foreach row
for (let x = 0; x < width; x++) {
// for each pixel
let color = colors[ pixels[offset] ];
let red = color.r;
let green = color.g;
let blue = color.b;
let alpha = color.a;
offset++;
} // x loop
} // y loop
If getPixels()
is called while in image
mode, the canvas is converted to rgba
first.
The getPalette()
method returns an array of indexed palette colors. This is only applicable in indexed
mode (see Modes below). The palette array may contain up to 256 colors, and each array element is an object containing the following properties:
Property Name | Description |
---|---|
r |
Red channel value, from 0 - 255. |
g |
Green channel value, from 0 - 255. |
b |
Blue channel value, from 0 - 255. |
a |
Alpha channel value, from 0 - 255. |
uint32 |
A 32-bit integer value representing the palette color as a "pixel". |
To convert your canvas into indexed
mode, call quantize() or quantizeFast().
The get()
method is a generic accessor for fetching named parameters on the CanvasPlus object. Using this you can retrieve the current canvas width, height, image mode, and more. Many are used as defaults for filters. See Parameters below for the full list. Example:
let width = canvas.get('width');
let height = canvas.get('height');
The width()
method is an alias for get('width')
. It returns the current canvas width in pixels. Example:
let width = canvas.width();
The height()
method is an alias for get('height')
. It returns the current canvas height in pixels. Example:
let height = canvas.height();
The set()
method sets an arbitrary named parameter on the CanvasPlus object. Using this you can disable features such as automatic EXIF rotation correction, enable debug mode, and more. See Parameters below for the full list. Example:
canvas.set( 'autoOrient', false ); // do not auto-rotate based on EXIF data
canvas.set( 'debug', true ); // output debugging data to console
The clearLastError()
method clears the previous error state, if any. Afterwards, getLastError() will return null
. This method is called internally by every filter and transform just prior to executing.
The attachLogAgent()
method allows you to attach your own debug and error logging agent (object). The object should have debug
and error
methods defined, which accept three arguments (code
, message
, and data
). See Logging above for more details and an example implementation.
The importImage()
method allows you to import your own native HTML Image object into CanvasPlus. Please note that the image must be completely loaded before importing, otherwise an error will be raised. Example use:
let img = new Image();
img.onload = function() {
canvas.importImage( img );
};
img.src = "my-image.jpg";
The canvas will be completely replaced with the image, and inherit its exact width, height, and content. This also sets the internal mode to image
.
The importCanvas()
method allows you to import your own native HTML5 Canvas object into CanvasPlus. Example:
canvas.importCanvas( document.getElementById('mycanvas') );
The canvas used by CanvasPlus will be completely replaced with the new canvas, and inherit its exact width, height, and content. This also sets the internal mode to rgba
.
The reset()
method completely resets CanvasPlus, removing all pixels, data and reverting settings to defaults. It is effectively the same as destroying and recreating the object. Example:
canvas.reset();
The render()
method converts CanvasPlus into rgba
mode, rendering the internal image or indexed pixels onto the canvas. This is only applicable when in image
or indexed
mode (see Modes below), and is called automatically by most filters and transforms. You should rarely need to call it directly, but it is provided just in case. Example:
canvas.render();
The histogram()
method generates an image histogram of the current canvas pixels, returning an object with the following properties:
Property Name | Type | Description |
---|---|---|
red |
Array | An array of all the red histogram counts per value. |
green |
Array | An array of all the green histogram counts per value. |
blue |
Array | An array of all the blue histogram counts per value. |
alpha |
Array | An array of all the alpha histogram counts per value. |
redMax |
Integer | The maximum red value across all the array elements. |
greenMax |
Integer | The maximum green value across all the array elements. |
blueMax |
Integer | The maximum blue value across all the array elements. |
alphaMax |
Integer | The maximum alpha value across all the array elements. |
Example use:
let histo = canvas.histogram();
For example, let's say you had an image that contained a large amount of pure black (rgb(0, 0, 0)
). Your histogram would therefore contain high numbers for the first (zeroth) array indexes in each color channel. The array indexes are the color channel brightnesses, and the values are the counts of pixels in that specific brightness slice. In this case histo.red[0]
, histo.green[0]
and histo.blue[0]
would all contain high numbers, because those elements are literally "counts" of the darkest pixels.
You can use the redMax
, greenMax
and blueMax
properties to determine the maximum slice counts across the arrays. These maximums can be used to normalize the data and scale it down to fit into a 2D histogram display (see the playground for an example of this).
The histogram feature only works in rgba
mode (see Modes below), and will convert the canvas into RGBA if necessary.
The hash()
method generates a perceptual hash of the current canvas pixels, using the fast Blockhash algorithm. This is basically a unique ID or thumbprint for the image, represented as a 16-character hexadecimal string, which is almost always the same when compared to similar images. Example use:
let hash = canvas.hash();
// "02fe3c3c7ca2471b"
The basic idea with these hash IDs is that you can compare them using a Hamming distance, and see exactly how similar two images are. CanvasPlus provides a hammingDistance()
method for you to use for this purpose. Here is an example:
// compute initial hash
let hash1 = canvas.hash();
// filter image
canvas.adjust({
brightness: -40,
contrast: 40,
hue: 180,
saturation: 50
});
// compute new hash
let hash2 = canvas.hash();
// get hamming distance
let dist = canvas.hammingDistance( hash1, hash2 );
console.log("Initial Image Hash: " + hash1);
console.log("Final Image Hash: " + hash2);
console.log("hash Distance: " + dist);
Example output:
Initial Image Hash: 02fe3c3c7c92471b
Final Image Hash: 02fe3c3c7ca2471b
Hash Distance: 2
Note that the Blockhash algorithm has some limitations:
For images in general, the algorithm generates the same blockhash value for two different images in 1% of the cases (data based on a random sampling of 100,000 images).
For photographs, the algorithm generates practically unique blockhashes, but for icons, clipart, maps and other images, the algorithm generates less unique blockhashses. Larger areas of the same color in an image, either as a background or borders, result in hashes that collide more frequently.
The CanvasPlus hash()
method only works in rgba
mode (see Modes below), and will convert the canvas into RGBA if necessary.
CanvasPlus has three internal "modes" which specify how your image data is represented in memory:
Mode | Description |
---|---|
image |
This mode is set when an image is loaded, but not yet rendered onto a canvas. load() sets this mode. |
rgba |
This mode means the pixels are stored in a RGBA canvas. This is the primary mode of operation. create() sets this mode. |
indexed |
This mode is only set when the image is quantized, and is only used for outputting a GIF or indexed PNG image. |
Modes are automatically changed according to how the library is used. For example, all filters require an RGBA canvas, so the mode is automatically converted to rgba
if necessary. Only quantize() and quantizeFast() convert the mode to indexed
.
If you request access to the underlying canvas object via getCanvas() or getContext(), and the canvas is in either image
or indexed
mode, it is converted back to rgba
. getPixels() works with both rgba
and indexed
modes.
The reason why image
mode exists is that the first filter applied after loading is very often a resize(), and it is much faster to delay rendering of the image until resize time, so it can be done in one single operation.
You can use get() to query the current mode, but you should rarely need to.
CanvasPlus works in "RGBA" mode, which stands for Red, Green, Blue, and Alpha. Many filters can customize which channels they are applied to, and take a single channels
property. This property is a string, which expects the abbreviated form of the channels to act upon, e.g. rgba
for all channels including alpha. Here are some more examples:
Channels | Description |
---|---|
rgba |
Apply to all color channels including the alpha channel (red, green, blue and alpha). |
rgb |
Apply to only the RGB color channels (red, green and blue) excluding the alpha channel. |
r |
Apply to only the red channel. |
g |
Apply to only the green channel. |
b |
Apply to only the blue channel. |
a |
Apply to only the alpha channel. |
gb |
Apply to the green and blue channels only. |
ra |
Apply to the red and alpha channels only. |
Here are all the possible gravity
parameter values, and what they mean. Note that all gravity strings are case-insensitive.
Gravity Value | Alias | Description |
---|---|---|
center |
- | Center both horizontally and vertically. This is typically the default. |
northwest |
topleft |
Pin to top-left corner. |
north |
top |
Pin to top, center horizontally. |
northeast |
topright |
Pin to top-right corner. |
east |
right |
Pin to right side, center vertically. |
southeast |
bottomright |
Pin to bottom-right corner. |
south |
bottom |
Pin to bottom, center horizontally. |
southwest |
bottomleft |
Pin to bottom-left corner. |
west |
left |
Pin to left side, center vertically. |
The antialias
parameter is used by several filters including resize, transform, composite, mask, and draw. Here are all the possible antialias
parameter values, and what they mean. Note that this is highly implementation-dependent, as in your results may vary between browsers and Node.js canvas. All strings are case-insensitive. The default is always best
.
Value | Description |
---|---|
best |
Highest quality setting, but also the slowest. In the browser imageSmoothingEnabled is enabled and imageSmoothingQuality is set to high . |
good |
Medium quality setting. In the browser imageSmoothingEnabled is enabled and imageSmoothingQuality is set to medium . |
fast |
Low quality setting. In the browser imageSmoothingEnabled is enabled and imageSmoothingQuality is set to low . |
nearest |
Lowest quality setting, but also the fastest. In the browser imageSmoothingEnabled is disabled. |
The word nearest
refers to nearest-neighbor interpolation, and results in pixelated transforms (no anti-aliasing).
Many methods accept a clip
property, which allows you to optionally restrict the filter to a rectangle inside the canvas. This is similar to the marquee rectangle selection tool in many paint apps. If specified, clip
should be an object with the following properties:
Property Name | Type | Description |
---|---|---|
x |
Integer | Coordinate of the left side of the clipping rectangle. |
y |
Integer | Coordinate of the top side of the clipping rectangle. |
width |
Integer | Width of the clipping rectangle, in pixels. |
height |
Integer | Height of the clipping rectangle, in pixels. |
Example use:
canvas.sepia({
clip: { x: 50, y: 50, width: 100, height: 100 }
});
Here are all the general parameters you can access using get() and change using set():
Parameter Name | Type | Description |
---|---|---|
readEXIF |
Boolean | Automatically read image EXIF data on load. Defaults to true (enabled). |
autoOrient |
Boolean | Automatically correct image rotation on load, using EXIF data. Defaults to true (enabled). |
throw |
Boolean | Enables try/catch mode (will throw on error). Defaults to false (disabled). |
debug |
Boolean | Enables debug logging mode using console.log and console.error . Defaults to false (disabled). |
gravity |
String | Default gravity (alignment) for filters such as composite(), expand(), resize() and text(). Defaults to center . |
colors |
Integer | Default number of palette colors for quantization. Defaults to 256 . |
dither |
Boolean | Default dither option for quantization. Defaults to false (disabled). |
ditherType |
String | Default dither algorithm for quantization. Defaults to FloydSteinberg . |
alpha |
Boolean | Flag indicating whether canvas contains alpha (transparent) pixels or not. Used by GIF and PNG output. |
mode |
String | Current internal mode of the canvas. Do not change this manually. |
width |
Number | Current canvas width in pixels. Do not change this manually (use resize()). |
height |
Number | Current canvas height in pixels. Do not change this manually (use resize()). |
maxArea |
Number | Maximum allowed canvas area in square pixels (designed to limit memory usage). Defaults to 0 (no limit). |
The following parameters are only applicable in Node.js, when using the canvas module:
Parameter Name | Type | Description |
---|---|---|
progressive |
Boolean | Optionally save JPEGs in progressive scan mode. Defaults to false . |
chromaSubsampling |
Boolean | Optionally save JPEGs with chroma subsampling. Defaults to true . |
compressionLevel |
Integer | Compression level for PNG images. Defaults to 6 . |
pngFilter |
String | PNG filter algorithm, for 32-bit PNG images. Defaults to PNG_ALL_FILTERS . See below. |
webp |
Object | WebP encoding settings. See webp-wasm for details. |
The pngFilter
parameter specifies the algorithm for preparing the PNG data for compression. The available values are: PNG_ALL_FILTERS
, PNG_FILTER_SUB
, PNG_FILTER_UP
, PNG_FILTER_AVG
, PNG_FILTER_PAETH
, and PNG_FILTER_NONE
. This is only applicable for writing 32-bit PNGs. See the PNG Filter Spec for more information.
The following parameters are only applicable when running in the browser:
Parameter Name | Type | Description |
---|---|---|
fontWeight |
String | Set the font weight, e.g. bold (defaults to normal ). |
fontStyle |
String | Set the font style, e.g. italic (defaults to normal ). |
useDataURLs |
Boolean | Use Data URLs instead of Blobs when loading and saving images. This may improve performance when using headless Chrome on Linux. |
To compile the browser version of the library from the Node.js source, you will need to have browserify installed. Then change into the main package directory and type:
npm run browserify
This should generate the canvas-plus.js
standalone file.
CanvasPlus would not be possible without these awesome open-source packages:
Module Name | Description | License |
---|---|---|
canvas | Cairo backed Canvas implementation for Node.js. | MIT |
image-q | Complete Image Quantization Library | MIT |
omggif | GIF 89a encoder and decoder | MIT |
exifreader | Read EXIF meta data from image files. | MPL 2.0 |
browserify | Browser-side require() the node.js way | MIT |
squoosh | Make images smaller using best-in-class codecs, right in the browser. | Apache 2.0 |
Also, special thanks go to:
- Matt Lockyer's Gaussian Blur Convolution Kernel Generator, (c) Matt Lockyer, MIT License
- The Monotone Cubic Interpolation article on Wikipedia, and sample code therein.
- The TinyColor library, (c) Brian Grinstead, MIT License.
- The onFontReady library, (c) Dwight House, MIT License.
- The Blockhash algorithm and JS code, (c) 2014 Commons Machinery, MIT License.
- Stack Overflow
- Mozilla Developer Network (MDN)
The MIT License (MIT)
Copyright (c) 2017 - 2023 Joseph Huckaby.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Please note that the exifreader package, one of our dependencies, is MPL 2.0 licensed. This license is a bit more restrictive than the MIT license, so please use with caution. If you don't need any EXIF features, it would be relatively easy to fork canvas-plus and remove the dependency.
Please note that the WebP implementation we use in Node.js is borrowed from Squoosh, which is Apache 2.0 licensed. This license is a bit more restrictive than the MIT license, so please use with caution. If you don't need any WebP features, it would be relatively easy to fork canvas-plus and remove the dependency.