Light Weight Image Processor for NodeJS
This module provides comprehensive, fast, and simple image processing and
manipulation capabilities.
There are no external runtime dependencies, which means you don’t have to
install anything else on your system.
This module is in active development. New features are being added.
Read the background for the development of this module.
npm install lwip
Or, clone this repo and cd lwip && npm install
.
You can run tests with npm test
.
Note: Installation of this module involves compiling native code.
If npm install lwip
failes, you probably need to setup your system.
See instructions.
Building on Windows with Visual Studio requires version 2013 or higher.
Typical workflow:
Example (batch operations):
// obtain an image object:
require('lwip').open('image.jpg', function(err, image){
// check err...
// define a batch of manipulations and save to disk as JPEG:
image.batch()
.scale(0.75) // scale to 75%
.rotate(45, 'white') // rotate 45degs clockwise (white fill)
.crop(200, 200) // crop a 200X200 square from center
.blur(5) // Gaussian blur with SD=5
.writeFile('output.jpg', function(err){
// check err...
// done.
});
});
Example (non-batch):
var lwip = require('lwip');
// obtain an image object:
lwip.open('image.jpg', function(err, image){
// check err...
// manipulate image:
image.scale(0.5, function(err, image){
// check err...
// manipulate some more:
image.rotate(45, 'white', function(err, image){
// check err...
// encode to jpeg and get a buffer object:
image.toBuffer('jpg', function(err, buffer){
// check err...
// save buffer to disk / send over network / etc.
});
});
});
});
Decoding (reading):
Encoding (writing):
Other formats may also be supported in the future, but are probably less urgent.
Check the issues to see which formats are planned to be supported.
Open an issue if you need support for a format which is not already listed.
In LWIP colors are coded as RGBA values (red, green, blue and an alpha channel).
Colors are specified in one of three ways:
As a string. possible values:
"black" // {r: 0, g: 0, b: 0, a: 100}
"white" // {r: 255, g: 255, b: 255, a: 100}
"gray" // {r: 128, g: 128, b: 128, a: 100}
"red" // {r: 255, g: 0, b: 0, a: 100}
"green" // {r: 0, g: 255, b: 0, a: 100}
"blue" // {r: 0, g: 0, b: 255, a: 100}
"yellow" // {r: 255, g: 255, b: 0, a: 100}
"cyan" // {r: 0, g: 255, b: 255, a: 100}
"magenta" // {r: 255, g: 0, b: 255, a: 100}
As an array [R, G, B, A]
where R
, G
and B
are integers between 0 and
255 and A
is an integer between 0 and 100.
As an object {r: R, g: G, b: B, a: A}
where R
, G
and B
are integers
between 0 and 255 and A
is an integer between 0 and 100.
Note: The A
value (alpha channel) is always optional and defaults to
100 (completely opaque).
All operations are asynchronous, and processing takes place in a thread pool
managed by libuv which is part of NodeJS. This thread pool is separate from the event
loop used to process HTTP requests, so use of lwip should not significantly affect the
handling of HTTP requests by a web application. The thread pool is however shared with
other threaded native modules such as those providing database and filesystem IO.
The default thread pool size of 4 will be appropriate for most applications. However
if your application regularly processes many images concurrently and and you wish
to take full advantage of a multicore system or prevent heavy image processing work
from delaying database or filesystem IO, you may want to increase the size of the
thread pool by setting the UV_THREADPOOL_SIZE environmental variable to the NodeJS
process, e.g.:
UV_THREADPOOL_SIZE=8 node your_script.js
All operations are done on an image
object. An image
object can be obtained
by:
open
create
method.image.clone
method.image.extract
method.lwip.open(source, type, callback)
source {String/Buffer}
: The path to the image on disk or an image buffer.type {String/Object}
: Optional type of the image. If omitted, the typesource
is a buffer, type
source
is an encoded image buffer, type
must be"jpg"
). If source
is a raw pixels buffertype
must be an object with type.width
and type.height
properties.callback {Function(err, image)}
Note about raw pixels buffers: source
may be a buffer of raw pixels. The
buffer may contain pixels of 1-4 channels, where:
In other words, if the image in the buffer has width W
and height H
, the
size of the buffer can be W*H
, 2*W*H
, 3*W*H
or 4*W*H
.
The channel values in the buffer must be stored sequentially. I.e. first all the
Red values, then all the Green values, etc.
var lwip = require('lwip');
lwip.open('path/to/image.jpg', function(err, image){
// check 'err'. use 'image'.
// image.resize(...), etc.
});
var fs = require('fs'),
lwip = require('lwip');
fs.readFile('path/to/image.png', function(err, buffer){
// check err
lwip.open(buffer, 'png', function(err, image){
// check 'err'. use 'image'.
// image.resize(...), etc.
});
});
lwip.create(width, height, color, callback)
width {Integer>0}
: The width of the new image.height {Integer>0}
: The height of the new image.color {String / Array / Object}
: Optional Color of the canvas. See{r:0, g:0, b:0, a:0}
.callback {Function(err, image)}
Example:
var lwip = require('lwip');
lwip.create(500, 500, 'yellow', function(err, image){
// check err
// 'image' is a 500X500 solid yellow canvas.
});
image.resize(width, height, inter, callback)
width {Integer}
: Width in pixels.height {Integer}
: Optional height in pixels. If omitted, width
willinter {String}
: Optional interpolation method. Defaults to "lanczos"
."nearest-neighbor"
"moving-average"
"linear"
"grid"
"cubic"
"lanczos"
callback {Function(err, image)}
image.scale(wRatio, hRatio, inter, callback)
wRatio {Float}
: Width scale ratio.hRatio {Float}
: Optional height scale ratio. If omitted, wRatio
willinter {String}
: Optional interpolation method. Defaults to "lanczos"
."nearest-neighbor"
"moving-average"
"linear"
"grid"
"cubic"
"lanczos"
callback {Function(err, image)}
Contain the image in a colored canvas. The image will be resized to the largest
possible size such that it’s fully contained inside the canvas.
image.contain(width, height, color, inter, callback)
width {Integer}
: Canvas’ width in pixels.height {Integer}
: Canvas’ height in pixels.color {String / Array / Object}
: Optional Color of the canvas. Seeinter {String}
: Optional interpolation method. Defaults to "lanczos"
."nearest-neighbor"
"moving-average"
"linear"
"grid"
"cubic"
"lanczos"
callback {Function(err, image)}
Cover a canvas with the image. The image will be resized to the smallest
possible size such that both its dimensions are bigger than the canvas’s
dimensions. Margins of the image exceeding the canvas will be discarded.
image.cover(width, height, inter, callback)
width {Integer}
: Canvas’ width in pixels.height {Integer}
: Canvas’ height in pixels.inter {String}
: Optional interpolation method. Defaults to "lanczos"
."nearest-neighbor"
"moving-average"
"linear"
"grid"
"cubic"
"lanczos"
callback {Function(err, image)}
image.rotate(degs, color, callback)
degs {Float}
: Clockwise rotation degrees.color {String / Array / Object}
: Optional Color of the canvas. Seecallback {Function(err, image)}
image.crop(left, top, right, bottom, callback)
left, top, right, bottom {Integer}
: Coordinates of the crop rectangle.callback {Function(err, image)}
image.crop(width, height, callback)
width, height {Integer}
: Width and height of the rectangle to crop from thecallback {Function(err, image)}
Gaussian blur.
image.blur(sigma, callback)
sigma {Float>=0}
: Standard deviation of the Gaussian filter.callback {Function(err, image)}
Inverse diffusion shapren.
image.sharpen(amplitude, callback)
amplitude {Float}
: Sharpening amplitude.callback {Function(err, image)}
Mirror an image along the ‘x’ axis, ‘y’ axis or both.
image.mirror(axes, callback)
axes {String}
: 'x'
, 'y'
or 'xy'
(case sensitive).callback {Function(err, image)}
Alias of mirror
.
Add a colored border to the image.
image.border(width, color, callback)
width {Integer}
: Border width in pixels.color {String / Array / Object}
: Optional Color of the border. Seecallback {Function(err, image)}
Pad image edges with colored pixels.
image.pad(left, top, right, bottom, color, callback)
left, top, right, bottom {Integer}
: Number of pixels to add to each edge.color {String / Array / Object}
: Optional Color of the padding. Seecallback {Function(err, image)}
Adjust image saturation.
image.saturate(delta, callback)
delta {Float}
: By how much to increase / decrease the saturation.callback {Function(err, image)}
Examples:
image.saturate(0, ...)
will have no effect on the image.image.saturate(0.5, ...)
will increase the saturation by 50%.image.saturate(-1, ...)
will decrease the saturation by 100%, effectivelyAdjust image lightness.
image.lighten(delta, callback)
delta {Float}
: By how much to increase / decrease the lightness.callback {Function(err, image)}
Examples:
image.lighten(0, ...)
will have no effect on the image.image.lighten(0.5, ...)
will increase the lightness by 50%.image.lighten(-1, ...)
will decrease the lightness by 100%, effectivelyAdjust image lightness.
image.darken(delta, callback)
Equivalent to image.lighten(-delta, callback)
.
Adjust image hue.
image.hue(shift, callback)
shift {Float}
: By how many degrees to shift each pixel’s hue.callback {Function(err, image)}
Examples:
image.lighten(0, ...)
will have no effect on the image.image.lighten(100, ...)
will shift pixels’ hue by 100 degrees.Note: The hue is shifted in a circular manner in the range [0,360] for each
pixel individually.
Adjust image transperancy.
image.fade(delta, callback)
delta {Float}
: By how much to increase / decrease the transperancy.callback {Function(err, image)}
Note: The transparency is adjusted independently for each pixel.
Examples:
image.fade(0, ...)
will have no effect on the image.image.fade(0.5, ...)
will increase the transparency by 50%.image.fade(1, ...)
will make the image completely transparent.Make image completely opaque.
image.opacify(callback)
callback {Function(err, image)}
Paste an image on top of this image.
image.paste(left, top, img, callback)
left, top {Integer}
: Coordinates of the top-left corner of the pastedimg {Image object}
: The image to paste.callback {Function(err, image)}
Notes:
img
is pasted in the state it was at the time image.paste( ... )
wascallback
is called asynchronously.Set the color of a pixel.
image.setPixel(left, top, color, callback)
left, top {Integer}
: Coordinates of the pixel from the left-top corner ofcolor {String / Array / Object}
: Color of the pixel to set.callback {Function(err, image)}
Notes:
Set the metadata in an image. This is currently only supported for PNG files.
Sets a tEXt chunk with the key lwip_data
and comment as the given string. If
called with a null
parameter, removes existing metadata from the image,
if present.
image.setMetadata(metadata)
metadata {String}
: a string of arbitrary length, or null.image.width()
returns the image’s width in pixels.
image.height()
returns the image’s height in pixels.
image.getPixel(left, top)
returns the color of the pixel at the (left, top)
coordinate.
left {Integer>=0}
top {Integer>=0}
Color is returned as an object. See colors specification.
Clone the image into a new image object.
image.clone(callback)
callback {Function(err, newImage)}
Example: See examples/clone.js
Note: The image is cloned to the state it was at the time
image.clone( ... )
was called, eventhough callback
is called asynchronously.
image.width(); // 500
image.clone(function(err, clone){
clone.width(); // 500
});
image.resize(100, 100, function(err, image){
image.width(); //100
});
Copy an area of the image into a new image object.
image.extract(left, top, right, bottom, callback)
left, top, right, bottom {Integer}
: Coordinates of the area to copy.callback {Function(err, newImage)}
Example: See examples/extract.js
Note: The sub-image is extracted from the original image in the state it was
at the time image.extract( ... )
was called, eventhough callback
is called
asynchronously.
Get encoded binary image data as a NodeJS
Buffer.
When opening an image, it is decoded and stored in memory as an uncompressed
image. All manipulations are done on the uncompressed data in memory. This
method allows to encode the image to one of the specified formats and get the
encoded data as a NodeJS Buffer object.
image.toBuffer(format, params, callback)
format {String}
: Encoding format. Possible values:"jpg"
"png"
"gif"
params {Object}
: Optional Format-specific parameters (See below).callback {Function(err, buffer)}
Supported encoding formats:
The params
object should have the following fields:
quality {Integer}
: Defaults to 100
.Note that when encoding to JPEG the alpha channel is discarded.
The params
object should have the following fields:
compression {String}
: Defaults to "fast"
. Possible values:
"none"
- No compression. Fastest."fast"
- Basic compression. Fast."high"
- High compression. Slowest.interlaced {Boolean}
: Defaults to false
.transparency {true/false/'auto'}
: Preserve transparency? Defaults to'auto'
. Determines if the encoded image will have 3 or 4 channels. If'auto'
, the image will be encoded with 4 channels if it has transparentThe params
object should have the following fields:
colors {Integer}
: Defaults to 256
. Number of colors in the color tableinterlaced {Boolean}
: Defaults to false
.transparency {true/false/'auto'}
: Preserve transparency? Defaults to'auto'
. Determines if the encoded image will have 3 or 4 channels. If'auto'
, the image will be encoded with 4 channels if it has transparentthreshold {Integer}
- Between 0 and 100. Pixels in a gif image are eitherWrite encoded binary image data directly to a file.
image.writeFile(path, format, params, callback)
path {String}
: Path of file to write.format {String}
: Optional Encoding format. If omitted, will be inferredpath
extension. Possible values are specified inparams {Object}
: Optional Format-specific parameters.callback {Function(err)}
Get the textual metadata from an image. This is currently only supported for
tEXt chunks in PNG images, and will get the first tEXt chunk found with the key
lwip_data
. If none is found, returns null.
image.getMetadata()
Each of the image operations above can be done as part of
a batch of operations. Operations can be queued, and executed as a batch at any
time.
Each one of the image operations has a batch equivalent
which takes the same arguments, except the callback, which is not needed.
When all batch operations had been queued, they can be executed in one of
several methods, as explained below.
In order to start queueing operations, a batch object first needs to be obtained
from the image.
// obtain a batch object from the image:
var batch = image.batch();
Use the batch object to queue image operations. Each of the
operations above has a batch equivalent. Operations can be chained.
Remember, the batch manipulation methods do not take a callback.
Example:
batch.rotate(45, 'white').scale(0.5).blur(5);
There are several methods which start the execution of a batch. Once a batch
finishes an execution, it becomes empty and can be resued to queue additional
operations.
When all desired operations had been queued, execute the batch with the exec()
method. exec
takes a callback
argument; callback
is a function which
receives an error object and the manipulated image object:
batch.exec(callback)
callback {Function(err, image)}
:
err
: An error object or null
when no error.image
: An image object of the manipulated image.batch.exec(function(err, image){
// check err, use image
});
Batch objects have a toBuffer
convenience method.
batch.toBuffer(format, params, callback)
See parameters of image.toBuffer()
.
Batch objects have a writeFile
convenience method.
batch.writeFile(path, format, params, callback)
See parameters of image.writeFile()
.
An image can have more than one batch object, but all batch objects modify the
same underlying image. This means the order of execution matters.
var batch1 = image.batch().rotate('45', 'black');
var batch2 = image.batch().border(15, 'black');
This will rotate the image 45degs and then add a black border:
batch1.exec(function(err, image){
batch2.exec(function(err, image){
// ...
});
});
While this will add a black border and then rotate the image 45degs:
batch2.exec(function(err, image){
batch1.exec(function(err, image){
// ...
});
});
The native part of this module is compiled from source which uses the following: