Skip to content

Commit

Permalink
add initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
oliver-moran committed Sep 13, 2014
1 parent e9595e3 commit 33bdc4f
Show file tree
Hide file tree
Showing 6 changed files with 816 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
82 changes: 80 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,82 @@
jimp
====
# Jimp #

The "JavaScript Image Manipulation Program" :-)

An image processing library written entirely in JavaScript for Node, with zero external or native dependencies.

Example usage:

var Jimp = require("jimp");

// open a file called "lenna.png"
var lenna = new Jimp("lenna.png", function () {
this.resize(220, 220) // resize
.write("lenna-small.png") // save
.quality(60) // set JPEG quality
.write("lenna-small.jpg") // save as JPEG
.greyscale() // set greyscale
.write("lena-small-bw.png") // save again
.crop(80, 100, 80, 50) // crop
.write("lena-small-bw-cropped.png"); // save again
});;

## Methods ##

The Jimp constructor takes two arugments, the path to a JPEG or PNG image and an optional call back when the image is parsed:

var image = new Jimp("./path/to/image.jpg", function () {
// ready
});

Once the callback has fired the following methods can be called on the image:

image.crop( x, y, w, h ); // crop to the given region
image.invert(); // invert the image colours
image.greyscale(); // remove colour from the image
image.sepia(); // apply a sepia wash to the image
image.opacity( f ); // apply an opacity of 0-1 to the image
image.resize( w, h ); // resize the image
image.scale( f ); // scale the image by the factor f

(Contributions of more methods are welcome!)

The image can be written to disk in JPEG or PNG format using:

image.write( path, cb ); // callback will be fired when write is successful

The quality of saved JPEGs can be set with:

image.quality( n ); // set the quality of saved JPEG, 0 - 100

## Advanced ##

The library enables low-level manipulation of images in memory through the bitmap property of each Jimp object:

image.bitmap.data; // a buffer of the raw bitmap data
image.bitmap.width; // the width of the image
image.bitmap.height // the height of the image

This can be manipulated directory, but remember: garbage in, garbage out.

A helper method is available to scan a region of the bitmap:

image.scan(x, y, w, h, cb); // scan a given region of the bitmap and call cb on every pixel

Example usage:

image.scan(0, 0, image.bitmap.width, image.bitmap.height, function (x, y, idx) {
// x, y is the position of this pixel on the image
// idx is the position start position of this rgba tuple in the bitmap buffer
var red = this.bitmap.data[idx];
var green = this.bitmap.data[idx+1];
var blue = this.bitmap.data[idx+2];
var alpha = this.bitmap.data[idx+3];
// rgba values run from 0 - 255
// e.g. this.bitmap.data[idx] = 0; // removes red from this pixel
});

## License ##

Jimp is licensed under the MIT license.
280 changes: 280 additions & 0 deletions jimp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
var FS = require("fs");
var PNG = require("pngjs").PNG;
var JPEG = require("jpeg-js");
var MIME = require("mime");
var Resize = require("./resize.js");

var MIME_PNG = "image/png";
var MIME_JPEG = "image/jpeg";

/**
* Jimp constructor
* @param path a path to a JPEG or PNG file
* @param (optional) cb a function to call when the image is parsed to a bitmap
*/
function Jimp(path, cb) {
if ("string" != typeof path)
throw new Error("path must be a string");
if ("undefined" == typeof cb) cb = function () {};
if ("function" != typeof cb)
throw new Error("cb must be a function");

var _this = this;
var mime = MIME.lookup(path);

switch (mime) {
case MIME_PNG:
var png = new PNG();
var reader = FS.createReadStream(path);
reader.pipe(png).on("parsed", function() {
_this.bitmap.data = new Buffer(this.data);
_this.bitmap.width = this.width;
_this.bitmap.height = this.height;
cb.call(_this);
});
break;
case MIME_JPEG:
FS.readFile(path, function (err, data) {
if (err) throw err;
_this.bitmap = JPEG.decode(data);
cb.call(_this);
});
break;
default:
throw new Error("Unsupported MIME type: " + mime);
}
}

// An object representing a bitmap in memory, comprising:
// - data: a buffer of the bitmap data
// - width: the width of the image in pixels
// - height: the height of the image in pixels
Jimp.prototype.bitmap = {
data: null,
width: null,
height: null
};

// The quality to be used when saving JPEG images
Jimp.prototype._quality = 100;

/**
* Sets the quality of the image when saving as JPEG format
* @param n The quality to use 0-100
* @returns this for chaining of methods
*/
Jimp.prototype.quality = function (n) {
if ("number" != typeof n)
throw new Error("n must be a number");
if (n < 0 || n > 100)
throw new Error("n must be a number 0 - 100");
this._quality = n;
return this;
};

/**
* Scanes through a region of the bitmap, calling a function for each pixel.
* @param x the x coordinate to begin the scan at
* @param y the y coordiante to begin the scan at
* @param w the width of the scan region
* @param h the height of the scan region
* @param cb a function to call on even pixel; the (x, y) position of the pixel
* and the index of the pixel in the bitmap buffer are passed to the function
* @returns this for chaining of methods
*/
Jimp.prototype.scan = function (x, y, w, h, cb) {
if ("number" != typeof x || "number" != typeof y)
throw new Error("x and y must be numbers");
if ("number" != typeof w || "number" != typeof h)
throw new Error("w and h must be numbers");
if ("function" != typeof cb)
throw new Error("cb must be a function");

for (var _y = y; _y < (y + h); _y++) {
for (var _x = x; _x < (x + w); _x++) {
var idx = (this.bitmap.width * _y + _x) << 2;
cb.call(this, _x, _y, idx);
}
}

return this;
};

/**
* Crops the image at a given point to a give size
* @param x the x coordinate to crop form
* @param y the y coordiante to crop form
* @param w the width of the crop region
* @param h the height of the crop region
* @returns this for chaining of methods
*/
Jimp.prototype.crop = function (x, y, w, h) {
if ("number" != typeof x || "number" != typeof y)
throw new Error("x and y must be numbers");
if ("number" != typeof w || "number" != typeof h)
throw new Error("w and h must be numbers");

var bitmap = [];
this.scan(x, y, w, h, function (x, y, idx) {
bitmap = bitmap.concat([
this.bitmap.data[idx],
this.bitmap.data[idx+1],
this.bitmap.data[idx+2],
this.bitmap.data[idx+3]
]);
});

this.bitmap.data = new Buffer(bitmap);
this.bitmap.width = w;
this.bitmap.height = h;

return this;
};

/**
* Inverts the image
* @returns this for chaining of methods
*/
Jimp.prototype.invert = function () {
this.scan(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
this.bitmap.data[idx] = 255 - this.bitmap.data[idx];
this.bitmap.data[idx+1] = 255 - this.bitmap.data[idx+1];
this.bitmap.data[idx+2] = 255 - this.bitmap.data[idx+2];
});

return this;
};

/**
* Removes colour from the image
* @returns this for chaining of methods
*/
Jimp.prototype.greyscale = function () {
this.scan(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
var grey = (this.bitmap.data[idx] + this.bitmap.data[idx+1] + this.bitmap.data[idx+2] ) / 3;
this.bitmap.data[idx] = grey;
this.bitmap.data[idx+1] = grey;
this.bitmap.data[idx+2] = grey;
});

return this;
};

/**
* Applies a sepia tone to the image
* @returns this for chaining of methods
*/
Jimp.prototype.sepia = function () {
this.scan(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
var red = this.bitmap.data[idx];
var green = this.bitmap.data[idx+1];
var blue = this.bitmap.data[idx+2];

red = (red * 0.393) + (green * 0.769) + (blue * 0.189);
green = (red * 0.349) + (green * 0.686) + (blue * 0.168);
blue = (red * 0.272) + (green * 0.534) + (blue * 0.131);
this.bitmap.data[idx] = (red < 255) ? red : 255;
this.bitmap.data[idx+1] = (green < 255) ? green : 255;
this.bitmap.data[idx+2] = (blue < 255) ? blue : 255;
});

return this;
}

/**
* Multiplies the opacity of each pizel by a factor between 0 and 1
* @returns this for chaining of methods
*/
Jimp.prototype.opacity = function (f) {
if ("number" != typeof f)
throw new Error("f must be a number");
if (f < 0 || f > 1)
throw new Error("f must be a number from 0 to 1");

this.scan(0, 0, this.bitmap.width, this.bitmap.height, function (x, y, idx) {
var v = this.bitmap.data[idx+3] * f;
this.bitmap.data[idx+3] = v;
});

return this;
};

/**
* Resizes the image to a set width and height using a 2-pass bilinear algorithm
* @param w the width to resize the image to
* @param h the height to resize the image to
* @returns this for chaining of methods
*/
Jimp.prototype.resize = function (w, h) {
if ("number" != typeof w || "number" != typeof h)
throw new Error("w and h must be numbers");
var _this = this;
var resize = new Resize(this.bitmap.width, this.bitmap.height, w, h, true, true, function (buffer) {
_this.bitmap.data = new Buffer(buffer);
_this.bitmap.width = w;
_this.bitmap.height = h;
});
resize.resize(this.bitmap.data);

return this;
};

/**
* Uniformly scales the image by a factor.
* @param f the factor to scale the image by
* @returns this for chaining of methods
*/
Jimp.prototype.scale = function (f) {
if ("number" != typeof f)
throw new Error("f must be a number");
if (f < 0)
throw new Error("f must be a positive number");

var w = this.bitmap.width * f;
var h = this.bitmap.height * f;
this.resize(w, h);
return this;
};

/**
* Writes the image to a file
* @param path a path to the destination file (either PNG or JPEG)
* @param (optional) cb a function to call when the image is saved to disk
* @returns this for chaining of methods
*/
Jimp.prototype.write = function (path, cb) {
if ("string" != typeof path)
throw new Error("path must be a string");
if ("undefined" == typeof cb) cb = function () {};
if ("function" != typeof cb)
throw new Error("cb must be a function");

var _this = this;
var mime = MIME.lookup(path);

switch (mime) {
case MIME_PNG:
var png = new PNG();
png.data = new Buffer(this.bitmap.data);
png.width = this.bitmap.width;
png.height = this.bitmap.height;
png.pack().pipe(FS.createWriteStream(path), {end: false});
png.on("end", function () {
cb.call(_this);
});
break;
case MIME_JPEG:
var jpeg = JPEG.encode(_this.bitmap, _this._quality);
var stream = FS.createWriteStream(path);
stream.write(jpeg.data);
stream.end();
cb.call(_this);
break;
default:
throw new Error("Unsupported MIME type: " + mime);
}

return this;
};

module.exports = Jimp;
Binary file added lenna.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "jimp",
"version": "0.1.0",
"description": "An image processing library written entirely in JavaScript (i.e. zero external or native dependencies).",
"main": "jimp.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"image",
"processing",
"manipulation",
"png",
"jpg",
"jpeg",
"resize",
"scale",
"crop"
],
"author": "Oliver Moran <[email protected]>",
"license": "MIT",
"dependencies": {
"pngjs": "~0.4.0",
"jpeg-js": "0.0.4",
"mime": "~1.2.11"
}
}
Loading

0 comments on commit 33bdc4f

Please sign in to comment.