node-lwip
Version:
Comprehensive, fast, and simple image processing and manipulation
621 lines (579 loc) • 20.1 kB
JavaScript
(function(undefined) {
var Image = require('./Image'),
path = require('path'),
fs = require('fs'),
decree = require('decree'),
defs = require('./defs'),
util = require('./util'),
encoder = require('bindings')('lwip_encoder'),
lwip_image = require('bindings')('lwip_image'),
normalizeColor = util.normalizeColor;
var judges = {
scale: decree(defs.args.scale),
resize: decree(defs.args.resize),
contain: decree(defs.args.contain),
cover: decree(defs.args.cover),
rotate: decree(defs.args.rotate),
blur: decree(defs.args.blur),
hslaAdjust: decree(defs.args.hslaAdjust),
saturate: decree(defs.args.saturate),
lighten: decree(defs.args.lighten),
darken: decree(defs.args.darken),
fade: decree(defs.args.fade),
opacify: decree(defs.args.opacify),
hue: decree(defs.args.hue),
crop: decree(defs.args.crop),
mirror: decree(defs.args.mirror),
pad: decree(defs.args.pad),
border: decree(defs.args.border),
sharpen: decree(defs.args.sharpen),
paste: decree(defs.args.paste),
clone: decree(defs.args.clone),
extract: decree(defs.args.extract),
toBuffer: decree(defs.args.toBuffer),
writeFile: decree(defs.args.writeFile),
setPixel: decree(defs.args.setPixel),
getPixel: decree(defs.args.getPixel)
};
Image.prototype.__lock = function() {
if (!this.__locked) this.__locked = true;
else throw Error("Another image operation already in progress");
};
Image.prototype.__release = function() {
this.__locked = false;
};
Image.prototype.getMetadata = function() {
return this.__metadata;
};
Image.prototype.setMetadata = function(data) {
if (typeof data != "string" && data != null) {
throw Error("Metadata must be a string or null");
}
this.__metadata = data;
return data;
};
Image.prototype.width = function() {
return this.__lwip.width();
};
Image.prototype.height = function() {
return this.__lwip.height();
};
Image.prototype.size = function() {
return {
width: this.__lwip.width(),
height: this.__lwip.height()
};
};
Image.prototype.getPixel = function() {
var args = judges.getPixel(arguments),
left = args[0],
top = args[1];
if (left >= this.width() || top >= this.height())
throw Error("Coordinates exceed dimensions of image");
var rgba = this.__lwip.getPixel(left, top);
return {
r: rgba[0],
g: rgba[1],
b: rgba[2],
a: rgba[3]
};
};
Image.prototype.scale = function() {
this.__lock();
var that = this;
judges.scale(
arguments,
function(wRatio, hRatio, inter, callback) {
hRatio = hRatio || wRatio;
var width = +wRatio * that.width(),
height = +hRatio * that.height();
that.__lwip.resize(width, height, defs.interpolations[inter], function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.resize = function() {
this.__lock();
var that = this;
judges.resize(
arguments,
function(width, height, inter, callback) {
height = height || width;
that.__lwip.resize(+width, +height, defs.interpolations[inter], function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.contain = function() {
var that = this;
judges.contain(
arguments,
function(width, height, color, inter, callback) {
var s = Math.min(width / that.width(), height / that.height());
that.scale(s, s, inter, function(err){
if (err) return callback(err);
var padX = (width - that.width()) / 2,
padY = (height - that.height()) / 2;
that.pad(
Math.ceil(padX),
Math.ceil(padY),
Math.floor(padX),
Math.floor(padY),
color,
callback
);
});
}
);
};
Image.prototype.cover = function() {
var that = this;
judges.cover(
arguments,
function(width, height, inter, callback) {
var s = Math.max(width / that.width(), height / that.height());
that.scale(s, s, inter, function(err){
if (err) return callback(err);
that.crop(width, height, callback);
});
}
);
};
Image.prototype.rotate = function() {
this.__lock();
var that = this;
judges.rotate(
arguments,
function(degs, color, callback) {
color = normalizeColor(color);
if (color.a < 100) that.__trans = true;
that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, +color.a, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.blur = function() {
this.__lock();
var that = this;
judges.blur(
arguments,
function(sigma, callback) {
that.__lwip.blur(+sigma, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.hslaAdjust = function() {
this.__lock();
var that = this;
judges.hslaAdjust(
arguments,
function(hs, sd, ld, ad, callback) {
that.__lwip.hslaAdj(+hs, +sd, +ld, +ad, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.saturate = function() {
this.__lock();
var that = this;
judges.saturate(
arguments,
function(delta, callback) {
that.__lwip.hslaAdj(0, +delta, 0, 0, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.lighten = function() {
this.__lock();
var that = this;
judges.lighten(
arguments,
function(delta, callback) {
that.__lwip.hslaAdj(0, 0, +delta, 0, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.darken = function() {
this.__lock();
var that = this;
judges.darken(
arguments,
function(delta, callback) {
that.__lwip.hslaAdj(0, 0, -delta, 0, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.fade = function() {
this.__lock();
var that = this;
judges.fade(
arguments,
function(delta, callback) {
that.__lwip.hslaAdj(0, 0, 0, -delta, function(err) {
if (+delta > 0) that.__trans = true;
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.opacify = function() {
this.__lock();
var that = this;
judges.opacify(
arguments,
function(callback) {
that.__lwip.opacify(function(err) {
that.__trans = false;
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.hue = function() {
this.__lock();
var that = this;
judges.hue(
arguments,
function(shift, callback) {
that.__lwip.hslaAdj(+shift, 0, 0, 0, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.crop = function() {
this.__lock();
var that = this;
judges.crop(
arguments,
function(left, top, right, bottom, callback) {
if (!right && !bottom) {
var size = that.size(),
width = left,
height = top;
left = 0 | (size.width - width) / 2;
top = 0 | (size.height - height) / 2;
right = left + width - 1;
bottom = top + height - 1;
}
that.__lwip.crop(left, top, right, bottom, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.mirror = function() {
this.__lock();
var that = this;
judges.mirror(
arguments,
function(axes, callback) {
var xaxis = false,
yaxis = false;
if ('x' === axes) xaxis = true;
if ('y' === axes) yaxis = true;
if ('xy' === axes || 'yx' === axes) {
xaxis = true;
yaxis = true;
}
that.__lwip.mirror(xaxis, yaxis, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
// mirror alias:
Image.prototype.flip = Image.prototype.mirror;
Image.prototype.pad = function() {
this.__lock();
var that = this;
judges.pad(
arguments,
function(left, top, right, bottom, color, callback) {
color = normalizeColor(color);
if (color.a < 100) that.__trans = true;
that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, +color.a, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.border = function() {
this.__lock();
var that = this;
judges.border(
arguments,
function(width, color, callback) {
color = normalizeColor(color);
if (color.a < 100) that.__trans = true;
// we can just use image.pad...
that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, +color.a, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.sharpen = function() {
this.__lock();
var that = this;
judges.sharpen(
arguments,
function(amplitude, callback) {
that.__lwip.sharpen(+amplitude, function(err) {
that.__release();
callback(err, that);
});
},
function(err) {
that.__release();
throw err;
}
);
};
Image.prototype.paste = function() {
this.__lock();
var that = this;
try{
judges.paste(
arguments,
function(left, top, img, callback) {
// first we retrieve what we need (buffer, dimensions, ...)
// synchronously so that the pasted image doesn't have a chance
// to be changed
var pixbuff = img.__lwip.buffer(),
width = img.__lwip.width(),
height = img.__lwip.height();
if (left + width > that.__lwip.width() || top + height > that.__lwip.height())
throw Error("Pasted image exceeds dimensions of base image");
that.__lwip.paste(+left, +top, pixbuff, +width, +height, function(err) {
that.__release();
callback(err, that);
});
}
);
} catch(err){
that.__release();
throw err;
}
};
Image.prototype.setPixel = function() {
this.__lock();
var that = this;
try{
judges.setPixel(
arguments,
function(left, top, color, callback) {
if (left >= that.width() || top >= that.height())
throw Error("Coordinates exceed dimensions of image");
color = normalizeColor(color);
if (color.a < 100) that.__trans = true;
that.__lwip.setPixel(+left, +top, +color.r, +color.g, +color.b, +color.a, function(err) {
that.__release();
callback(err, that);
});
}
);
} catch(err){
that.__release();
throw err;
}
};
Image.prototype.clone = function() {
// no need to lock the image. we don't modify the memory buffer.
// just copy it.
var that = this;
judges.clone(
arguments,
function(callback) {
// first we retrieve what we need (buffer, dimensions, ...)
// synchronously so that the original image doesn't have a chance
// to be changed (remember, we don't lock it); and only then call
// the callback asynchronously.
var pixbuff = that.__lwip.buffer(),
width = that.__lwip.width(),
height = that.__lwip.height(),
trans = that.__trans;
setImmediate(function() {
callback(null, new Image(pixbuff, width, height, trans));
});
}
);
};
Image.prototype.extract = function() {
// no need to lock the image. we don't modify the memory buffer.
// just copy it and then crop it.
var that = this;
judges.extract(
arguments,
function(left, top, right, bottom, callback) {
// first we retrieve what we need (buffer, dimensions, ...)
// synchronously so that the original image doesn't have a chance
// to be changed (remember, we don't lock it); then we crop it and
// only call the callback asynchronously.
var pixbuff = that.__lwip.buffer(),
width = that.__lwip.width(),
height = that.__lwip.height(),
trans = that.__trans,
eximg = new Image(pixbuff, width, height, trans);
eximg.__lwip.crop(left, top, right, bottom, function(err) {
callback(err, eximg);
});
}
);
};
Image.prototype.toBuffer = function() {
this.__lock();
var that = this;
try{
judges.toBuffer(
arguments,
function(type, params, callback) {
if (type === 'jpg' || type === 'jpeg') {
util.normalizeJpegParams(params);
return encoder.jpeg(
that.__lwip.buffer(),
that.__lwip.width(),
that.__lwip.height(),
params.quality,
encoderCb
);
} else if (type === 'png') {
util.normalizePngParams(params);
return encoder.png(
that.__lwip.buffer(),
that.__lwip.width(),
that.__lwip.height(),
params.compression,
params.interlaced,
params.transparency === 'auto' ? that.__trans : params.transparency,
that.__metadata,
encoderCb
);
} else if (type === 'gif') {
util.normalizeGifParams(params);
return encoder.gif(
that.__lwip.buffer(),
that.__lwip.width(),
that.__lwip.height(),
util.getClosest2Exp(params.colors),
params.colors,
params.interlaced,
params.transparency === 'auto' ? that.__trans : params.transparency,
params.threshold,
encoderCb
);
} else throw Error('Unknown type \'' + type + '\'');
function encoderCb(err, buffer) {
that.__release();
callback(err, buffer);
}
}
);
} catch (err){
that.__release();
throw err;
}
};
Image.prototype.writeFile = function() {
var that = this;
judges.writeFile(
arguments,
function(outpath, type, params, callback) {
type = type || path.extname(outpath).slice(1).toLowerCase();
that.toBuffer(type, params, function(err, buffer) {
if (err) return callback(err);
fs.writeFile(outpath, buffer, {
encoding: 'binary'
}, callback);
});
}
);
};
})(void 0);