Compare commits

...

30 Commits

Author SHA1 Message Date
Lovell Fuller
6c96bd0d37 Version bumps 2014-05-22 00:18:43 +01:00
Lovell Fuller
6b5f2028b7 Add support for 32 bit Linux #27 2014-05-21 23:46:10 +01:00
Lovell Fuller
276ba5228b Add usage example and further unit test for new max option
Simplify max vs crop logic
2014-05-19 21:52:47 +01:00
Lovell Fuller
ad7735a0a6 Merge pull request #18 from pierreinglebert/master
Add 'max' canvas option to specify the maximum width and/or height.
Approximately equivalent to GraphicsMagick's geometry option.
2014-05-19 21:03:04 +01:00
Pierre Inglebert
88edad3fae add max option #18 2014-05-19 20:18:26 +02:00
Lovell Fuller
308d1971d8 Recalculate residual float value for affine after shrink #26
Switch to static casts for double values

Add unit tests that previously would have failed
2014-05-18 11:39:05 +01:00
Lovell Fuller
6622045172 Merge pull request #25 from pierreinglebert/fix-progressive-typo
Fix progressive typo
2014-05-14 11:17:41 +01:00
Pierre Inglebert
f68ba8ea57 fix progressive typo 2014-05-14 08:56:12 +02:00
Lovell Fuller
2e427bb28a Remove liborc from CI config (to match README) 2014-05-13 16:47:57 +01:00
Lovell Fuller
efc7504961 Merge branch 'pierreinglebert-0.11-wip' 2014-05-13 16:45:32 +01:00
Lovell Fuller
8118613fa0 Merge branch '0.11-wip' of https://github.com/pierreinglebert/sharp into pierreinglebert-0.11-wip
Conflicts:
	package.json
2014-05-13 16:45:12 +01:00
Pierre Inglebert
eb6a221cee use master branch of image-magick-native until the 0.11 compatible is out 2014-05-13 08:03:24 +02:00
Pierre Inglebert
acdfe02502 make travis test on node 0.11 2014-05-12 21:30:44 +02:00
Lovell Fuller
2e106f8e2e Version bumps 2014-05-12 21:15:23 +02:00
Lovell Fuller
10496881f1 Add quality and compressionLevel options for output image. #24 2014-05-12 21:15:23 +02:00
Lovell Fuller
e275f6f5dd Add warning about liborc memory leaks. #21 2014-05-12 21:15:23 +02:00
Lovell Fuller
d635c297a2 Replace use of deprecated libvips conv method.
Ensure unref of mask to fix minor memory leak of ~150 bytes/image.
2014-05-12 21:15:23 +02:00
Pierre Inglebert
817c0a2a5a do not publish tests files 2014-05-12 21:15:23 +02:00
Pierre Inglebert
92fd34c627 remove unnecessary gitignore file 2014-05-12 21:15:23 +02:00
Lovell Fuller
43086cf134 Merge branch 'master' into quality-option 2014-05-11 18:26:16 +01:00
Lovell Fuller
e607bac31c Version bumps 2014-05-10 20:23:08 +01:00
Lovell Fuller
f8338e7c4f Add quality and compressionLevel options for output image. #24 2014-05-10 19:45:12 +01:00
Lovell Fuller
8322b442e0 Add warning about liborc memory leaks. #21 2014-05-10 14:39:34 +01:00
Lovell Fuller
afc51df4d8 Replace use of deprecated libvips conv method.
Ensure unref of mask to fix minor memory leak of ~150 bytes/image.
2014-05-03 22:14:21 +01:00
Lovell Fuller
e3a70c1075 Merge pull request #22 from pierreinglebert/add-npmignore
Add npmignore to minimise published package size
2014-04-24 14:09:42 +01:00
Pierre Inglebert
e3ee2b2976 do not publish tests files 2014-04-24 11:28:02 +02:00
Pierre Inglebert
cb285a6fb3 remove unnecessary gitignore file 2014-04-24 11:27:34 +02:00
Pierre Inglebert
aed3ca63b3 use NanNull, NanNew & NanSymbol for 0.11.11+ compat 2014-04-23 10:03:11 +02:00
Lovell Fuller
cbcf5e0dcc Update instructions for libvips installation on Ubuntu 2014-04-18 13:09:45 +01:00
Lovell Fuller
59f5c2d31b Update async dependency 2014-04-18 09:11:01 +01:00
9 changed files with 462 additions and 274 deletions

2
.gitignore vendored
View File

@@ -13,5 +13,3 @@ results
build build
node_modules node_modules
tests/fixtures/output.* tests/fixtures/output.*
npm-debug.log

18
.npmignore Normal file
View File

@@ -0,0 +1,18 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
build
node_modules
.gitignore
tests
.travis.yml

View File

@@ -1,16 +1,17 @@
language: node_js language: node_js
node_js: node_js:
- "0.10" - "0.10"
before_install: - "0.11"
- sudo add-apt-repository ppa:lyrasis/precise-backports -y before_install:
- sudo apt-get update -qq - sudo add-apt-repository ppa:lyrasis/precise-backports -y
- sudo apt-get install -qq automake gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev liborc-0.4-dev libxml2-dev swig graphicsmagick libmagick++-dev - sudo apt-get update -qq
- git clone https://github.com/jcupitt/libvips.git - sudo apt-get install -qq automake gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libxml2-dev swig graphicsmagick libmagick++-dev
- cd libvips - git clone https://github.com/jcupitt/libvips.git
- git checkout 7.38 - cd libvips
- ./bootstrap.sh - git checkout 7.38
- ./configure --enable-debug=no --enable-cxx=no --without-python - ./bootstrap.sh
- make - ./configure --enable-debug=no --enable-cxx=no --without-orc --without-python
- sudo make install - make
- sudo ldconfig - sudo make install
- cd $TRAVIS_BUILD_DIR - sudo ldconfig
- cd $TRAVIS_BUILD_DIR

View File

@@ -28,6 +28,8 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
* Node.js v0.10+ * Node.js v0.10+
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+ * [libvips](https://github.com/jcupitt/libvips) v7.38.5+
_libvips_ will take advantage of [liborc](http://code.entropywave.com/orc/) if present, however versions of _liborc_ prior to 0.4.19 suffer memory leaks.
### Install libvips on Mac OS ### Install libvips on Mac OS
brew install homebrew/science/vips --with-webp --with-graphicsmagick brew install homebrew/science/vips --with-webp --with-graphicsmagick
@@ -36,24 +38,36 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
brew link gettext --force brew link gettext --force
### Install libvips on Ubuntu/Debian Linux ### Install libvips on Ubuntu Linux
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev liborc-0.4-dev libxml2-dev swig #### Ubuntu 14.x
sudo apt-get install libvips-dev
#### Ubuntu 13.x
Compiling from source is recommended:
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libxml2-dev swig
git clone https://github.com/jcupitt/libvips.git git clone https://github.com/jcupitt/libvips.git
cd libvips cd libvips
git checkout 7.38 git checkout 7.38
./bootstrap.sh ./bootstrap.sh
./configure --enable-debug=no --enable-cxx=no --without-python ./configure --enable-debug=no --enable-cxx=no --without-python --without-orc
make make
sudo make install sudo make install
sudo ldconfig sudo ldconfig
Ubuntu 12.04 requires `libtiff4-dev` instead of `libtiff5-dev` and has [a bug](https://bugs.launchpad.net/ubuntu/+source/libwebp/+bug/1108731) in the libwebp package. Work around these problems by running these command first: #### Ubuntu 12.x
Requires `libtiff4-dev` instead of `libtiff5-dev` and has [a bug](https://bugs.launchpad.net/ubuntu/+source/libwebp/+bug/1108731) in the libwebp package. Work around these problems by running these command first:
sudo add-apt-repository ppa:lyrasis/precise-backports sudo add-apt-repository ppa:lyrasis/precise-backports
sudo apt-get update sudo apt-get update
sudo apt-get install libtiff4-dev sudo apt-get install libtiff4-dev
Then follow Ubuntu 13.x instructions.
## Usage examples ## Usage examples
```javascript ```javascript
@@ -71,25 +85,25 @@ sharp('input.jpg').resize(300, 200).write('output.jpg', function(err) {
``` ```
```javascript ```javascript
sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, buffer) { sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, outputBuffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains progressive JPEG image data, 200 pixels high // outputBuffer contains progressive JPEG image data, 200 pixels high
}); });
``` ```
```javascript ```javascript
sharp('input.png').resize(300).sharpen().webp(function(err, buffer) { sharp('input.png').resize(300).sharpen().quality(90).webp(function(err, outputBuffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains sharpened WebP image data (converted from PNG), 300 pixels wide // outputBuffer contains 300 pixels wide, sharpened, 90% quality WebP image data
}); });
``` ```
```javascript ```javascript
sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) { sharp(inputBuffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
if (err) { if (err) {
throw err; throw err;
} }
@@ -99,15 +113,25 @@ sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
``` ```
```javascript ```javascript
sharp('input.gif').resize(200, 300).embedBlack().webp(function(err, buffer) { sharp('input.gif').resize(200, 300).embedBlack().webp(function(err, outputBuffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains WebP image data of a 200 pixels wide and 300 pixels high image // outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
// containing a scaled version, embedded on a black canvas, of input.gif // containing a scaled version, embedded on a black canvas, of input.gif
}); });
``` ```
```javascript
sharp(inputBuffer).resize(200, 200).max().jpeg(function(err, outputBuffer) {
if (err) {
throw err;
}
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
// than 200 pixels regardless of the inputBuffer image dimensions
});
```
## API ## API
### sharp(input) ### sharp(input)
@@ -129,6 +153,12 @@ Scale to `width` x `height`. By default, the resized image is cropped to the exa
Crop the resized image to the exact size specified, the default behaviour. Crop the resized image to the exact size specified, the default behaviour.
### max()
Preserving aspect ratio, resize the image to the maximum width or height specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
### embedWhite() ### embedWhite()
Embed the resized image on a white background of the exact size specified. Embed the resized image on a white background of the exact size specified.
@@ -145,6 +175,18 @@ Perform a mild sharpen of the resultant image. This typically reduces performanc
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed. Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
### quality(quality)
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
`quality` is a Number between 1 and 100.
### compressionLevel(compressionLevel)
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
`compressionLevel` is a Number between -1 and 9.
### sequentialRead() ### sequentialRead()
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems. An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.

View File

@@ -11,6 +11,7 @@
'/usr/include/glib-2.0', '/usr/include/glib-2.0',
'/usr/lib/glib-2.0/include', '/usr/lib/glib-2.0/include',
'/usr/lib/x86_64-linux-gnu/glib-2.0/include', '/usr/lib/x86_64-linux-gnu/glib-2.0/include',
'/usr/lib/i386-linux-gnu/glib-2.0/include',
'<!(node -e "require(\'nan\')")' '<!(node -e "require(\'nan\')")'
], ],
'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'], 'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'],

284
index.js
View File

@@ -1,128 +1,156 @@
/*jslint node: true */ /*jslint node: true */
'use strict'; 'use strict';
var sharp = require('./build/Release/sharp'); var sharp = require('./build/Release/sharp');
var Sharp = function(input) { var Sharp = function(input) {
if (!(this instanceof Sharp)) { if (!(this instanceof Sharp)) {
return new Sharp(input); return new Sharp(input);
} }
this.options = { this.options = {
width: -1, width: -1,
height: -1, height: -1,
canvas: 'c', canvas: 'c',
sharpen: false, sharpen: false,
progressive: false, progressive: false,
sequentialRead: false, sequentialRead: false,
output: '__jpeg' quality: 80,
}; compressionLevel: 6,
if (typeof input === 'string') { output: '__jpeg'
this.options.inFile = input; };
} else if (typeof input ==='object' && input instanceof Buffer) { if (typeof input === 'string') {
this.options.inBuffer = input; this.options.inFile = input;
} else { } else if (typeof input ==='object' && input instanceof Buffer) {
throw 'Unsupported input ' + typeof input; this.options.inBuffer = input;
} } else {
return this; throw 'Unsupported input ' + typeof input;
}; }
module.exports = Sharp; return this;
};
Sharp.prototype.crop = function() { module.exports = Sharp;
this.options.canvas = 'c';
return this; Sharp.prototype.crop = function() {
}; this.options.canvas = 'c';
return this;
Sharp.prototype.embedWhite = function() { };
this.options.canvas = 'w';
return this; Sharp.prototype.embedWhite = function() {
}; this.options.canvas = 'w';
return this;
Sharp.prototype.embedBlack = function() { };
this.options.canvas = 'b';
return this; Sharp.prototype.embedBlack = function() {
}; this.options.canvas = 'b';
return this;
Sharp.prototype.sharpen = function(sharpen) { };
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
return this; Sharp.prototype.max = function() {
}; this.options.canvas = 'm';
return this;
Sharp.prototype.progressive = function(progressive) { };
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this;
}; Sharp.prototype.sharpen = function(sharpen) {
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
Sharp.prototype.sequentialRead = function(sequentialRead) { return this;
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true; };
return this;
}; Sharp.prototype.progressive = function(progressive) {
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
Sharp.prototype.resize = function(width, height) { return this;
if (!width) { };
this.options.width = -1;
} else { Sharp.prototype.sequentialRead = function(sequentialRead) {
if (!Number.isNaN(width)) { this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
this.options.width = width; return this;
} else { };
throw 'Invalid width ' + width;
} Sharp.prototype.quality = function(quality) {
} if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
if (!height) { this.options.quality = quality;
this.options.height = -1; } else {
} else { throw 'Invalid quality (1 to 100) ' + quality;
if (!Number.isNaN(height)) { }
this.options.height = height; return this;
} else { };
throw 'Invalid height ' + height;
} Sharp.prototype.compressionLevel = function(compressionLevel) {
} if (!Number.isNaN(compressionLevel) && compressionLevel >= -1 && compressionLevel <= 9) {
return this; this.options.compressionLevel = compressionLevel;
}; } else {
throw 'Invalid compressionLevel (-1 to 9) ' + compressionLevel;
Sharp.prototype.write = function(output, callback) { }
if (!output || output.length === 0) { return this;
throw 'Invalid output'; };
} else {
this._sharp(output, callback); Sharp.prototype.resize = function(width, height) {
} if (!width) {
return this; this.options.width = -1;
}; } else {
if (!Number.isNaN(width)) {
Sharp.prototype.toBuffer = function(callback) { this.options.width = width;
return this._sharp('__input', callback); } else {
}; throw 'Invalid width ' + width;
}
Sharp.prototype.jpeg = function(callback) { }
return this._sharp('__jpeg', callback); if (!height) {
}; this.options.height = -1;
} else {
Sharp.prototype.png = function(callback) { if (!Number.isNaN(height)) {
return this._sharp('__png', callback); this.options.height = height;
}; } else {
throw 'Invalid height ' + height;
Sharp.prototype.webp = function(callback) { }
return this._sharp('__webp', callback); }
}; return this;
};
Sharp.prototype._sharp = function(output, callback) {
sharp.resize( Sharp.prototype.write = function(output, callback) {
this.options.inFile, if (!output || output.length === 0) {
this.options.inBuffer, throw 'Invalid output';
output, } else {
this.options.width, this._sharp(output, callback);
this.options.height, }
this.options.canvas, return this;
this.options.sharpen, };
this.options.progressive,
this.options.sequentialRead, Sharp.prototype.toBuffer = function(callback) {
callback return this._sharp('__input', callback);
); };
return this;
}; Sharp.prototype.jpeg = function(callback) {
return this._sharp('__jpeg', callback);
module.exports.cache = function(limit) { };
if (Number.isNaN(limit)) {
limit = null; Sharp.prototype.png = function(callback) {
} return this._sharp('__png', callback);
return sharp.cache(limit); };
};
Sharp.prototype.webp = function(callback) {
return this._sharp('__webp', callback);
};
Sharp.prototype._sharp = function(output, callback) {
sharp.resize(
this.options.inFile,
this.options.inBuffer,
output,
this.options.width,
this.options.height,
this.options.canvas,
this.options.sharpen,
this.options.progressive,
this.options.sequentialRead,
this.options.quality,
this.options.compressionLevel,
callback
);
return this;
};
module.exports.cache = function(limit) {
if (Number.isNaN(limit)) {
limit = null;
}
return sharp.cache(limit);
};

View File

@@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.3.0", "version": "0.4.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>" "Pierre Inglebert <pierre.inglebert@gmail.com>"
@@ -31,13 +31,13 @@
"buffer" "buffer"
], ],
"dependencies": { "dependencies": {
"nan": "^0.8.0" "nan": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^0.2.9", "imagemagick-native": "^1.0.0",
"gm": "^1.14.2", "gm": "^1.16.0",
"async": "^0.6.2", "async": "^0.9.0",
"benchmark": "^1.0.0" "benchmark": "^1.0.0"
}, },
"license": "Apache 2.0", "license": "Apache 2.0",

View File

@@ -20,13 +20,16 @@ struct resize_baton {
int width; int width;
int height; int height;
bool crop; bool crop;
bool max;
VipsExtend extend; VipsExtend extend;
bool sharpen; bool sharpen;
bool progessive; bool progressive;
VipsAccess access_method; VipsAccess access_method;
int quality;
int compressionLevel;
std::string err; std::string err;
resize_baton(): buffer_in_len(0), buffer_out_len(0) {} resize_baton(): buffer_in_len(0), buffer_out_len(0), crop(false), max(false), sharpen(false), progressive(false) {}
}; };
typedef enum { typedef enum {
@@ -133,17 +136,25 @@ class ResizeWorker : public NanAsyncWorker {
double factor; double factor;
if (baton->width > 0 && baton->height > 0) { if (baton->width > 0 && baton->height > 0) {
// Fixed width and height // Fixed width and height
double xfactor = (double)(in->Xsize) / (double)(baton->width); double xfactor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
double yfactor = (double)(in->Ysize) / (double)(baton->height); double yfactor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor); factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
// if max is set, we need to compute the real size of the thumb image
if (baton->max) {
if (xfactor > yfactor) {
baton->height = round(static_cast<double>(in->Ysize) / xfactor);
} else {
baton->width = round(static_cast<double>(in->Xsize) / yfactor);
}
}
} else if (baton->width > 0) { } else if (baton->width > 0) {
// Fixed width, auto height // Fixed width, auto height
factor = (double)(in->Xsize) / (double)(baton->width); factor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
baton->height = floor((double)(in->Ysize) / factor); baton->height = floor(static_cast<double>(in->Ysize) / factor);
} else if (baton->height > 0) { } else if (baton->height > 0) {
// Fixed height, auto width // Fixed height, auto width
factor = (double)(in->Ysize) / (double)(baton->height); factor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
baton->width = floor((double)(in->Xsize) / factor); baton->width = floor(static_cast<double>(in->Xsize) / factor);
} else { } else {
// Identity transform // Identity transform
factor = 1; factor = 1;
@@ -154,7 +165,7 @@ class ResizeWorker : public NanAsyncWorker {
if (shrink < 1) { if (shrink < 1) {
shrink = 1; shrink = 1;
} }
double residual = shrink / (double)factor; double residual = static_cast<double>(shrink) / factor;
// Try to use libjpeg shrink-on-load // Try to use libjpeg shrink-on-load
int shrink_on_load = 1; int shrink_on_load = 1;
@@ -176,7 +187,7 @@ class ResizeWorker : public NanAsyncWorker {
factor = std::max(factor, 1.0); factor = std::max(factor, 1.0);
shrink = floor(factor); shrink = floor(factor);
residual = shrink / factor; residual = shrink / factor;
// Reload input using shrink-on-load // Reload input using shrink-on-load
if (baton->buffer_in_len > 1) { if (baton->buffer_in_len > 1) {
if (vips_jpegload_buffer(baton->buffer_in, baton->buffer_in_len, &shrunk_on_load, "shrink", shrink_on_load, NULL)) { if (vips_jpegload_buffer(baton->buffer_in, baton->buffer_in_len, &shrunk_on_load, "shrink", shrink_on_load, NULL)) {
return resize_error(baton, in); return resize_error(baton, in);
@@ -197,6 +208,14 @@ class ResizeWorker : public NanAsyncWorker {
if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) { if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) {
return resize_error(baton, shrunk_on_load); return resize_error(baton, shrunk_on_load);
} }
// Recalculate residual float based on dimensions of required vs shrunk images
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunk->Xsize);
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunk->Ysize);
if (baton->crop) {
residual = std::max(residualx, residualy);
} else {
residual = std::min(residualx, residualy);
}
} else { } else {
vips_copy(shrunk_on_load, &shrunk, NULL); vips_copy(shrunk_on_load, &shrunk, NULL);
} }
@@ -217,7 +236,7 @@ class ResizeWorker : public NanAsyncWorker {
VipsImage *canvased = vips_image_new(); VipsImage *canvased = vips_image_new();
if (affined->Xsize != baton->width || affined->Ysize != baton->height) { if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
if (baton->crop) { if (baton->crop) {
// Crop // Crop/max
int width = std::min(affined->Xsize, baton->width); int width = std::min(affined->Xsize, baton->width);
int height = std::min(affined->Ysize, baton->height); int height = std::min(affined->Ysize, baton->height);
int left = (affined->Xsize - width + 1) / 2; int left = (affined->Xsize - width + 1) / 2;
@@ -241,14 +260,16 @@ class ResizeWorker : public NanAsyncWorker {
// Mild sharpen // Mild sharpen
VipsImage *sharpened = vips_image_new(); VipsImage *sharpened = vips_image_new();
if (baton->sharpen) { if (baton->sharpen) {
INTMASK* sharpen = im_create_imaskv("sharpen", 3, 3, VipsImage *sharpen = vips_image_new_matrixv(3, 3,
-1, -1, -1, -1.0, -1.0, -1.0,
-1, 32, -1, -1.0, 32.0, -1.0,
-1, -1, -1); -1.0, -1.0, -1.0);
sharpen->scale = 24; vips_image_set_double(sharpen, "scale", 24);
if (im_conv(canvased, sharpened, sharpen)) { if (vips_conv(canvased, &sharpened, sharpen, NULL)) {
g_object_unref(sharpen);
return resize_error(baton, canvased); return resize_error(baton, canvased);
} }
g_object_unref(sharpen);
} else { } else {
vips_copy(canvased, &sharpened, NULL); vips_copy(canvased, &sharpened, NULL);
} }
@@ -257,37 +278,37 @@ class ResizeWorker : public NanAsyncWorker {
// Output // Output
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) { if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) { if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) { } else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
// Write PNG to buffer // Write PNG to buffer
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) { if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) { } else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, NULL)) { if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_jpeg(baton->file_out)) { } else if (is_jpeg(baton->file_out)) {
// Write JPEG to file // Write JPEG to file
if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) { if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_png(baton->file_out)) { } else if (is_png(baton->file_out)) {
// Write PNG to file // Write PNG to file
if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) { if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_webp(baton->file_out)) { } else if (is_webp(baton->file_out)) {
// Write WEBP to file // Write WEBP to file
if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, NULL)) { if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_tiff(baton->file_out)) { } else if (is_tiff(baton->file_out)) {
// Write TIFF to file // Write TIFF to file
if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", 80, NULL)) { if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else { } else {
@@ -300,10 +321,10 @@ class ResizeWorker : public NanAsyncWorker {
void HandleOKCallback () { void HandleOKCallback () {
NanScope(); NanScope();
Handle<Value> argv[2] = { Null(), Null() }; Handle<Value> argv[2] = { NanNull(), NanNull() };
if (!baton->err.empty()) { if (!baton->err.empty()) {
// Error // Error
argv[0] = String::New(baton->err.data(), baton->err.size()); argv[0] = NanNew<String>(baton->err.data(), baton->err.size());
} else if (baton->buffer_out_len > 0) { } else if (baton->buffer_out_len > 0) {
// Buffer // Buffer
argv[1] = NanNewBufferHandle((char *)baton->buffer_out, baton->buffer_out_len); argv[1] = NanNewBufferHandle((char *)baton->buffer_out, baton->buffer_out_len);
@@ -319,7 +340,7 @@ class ResizeWorker : public NanAsyncWorker {
NAN_METHOD(resize) { NAN_METHOD(resize) {
NanScope(); NanScope();
resize_baton *baton = new resize_baton; resize_baton *baton = new resize_baton;
baton->file_in = *String::Utf8Value(args[0]->ToString()); baton->file_in = *String::Utf8Value(args[0]->ToString());
if (args[1]->IsObject()) { if (args[1]->IsObject()) {
@@ -331,20 +352,23 @@ NAN_METHOD(resize) {
baton->width = args[3]->Int32Value(); baton->width = args[3]->Int32Value();
baton->height = args[4]->Int32Value(); baton->height = args[4]->Int32Value();
Local<String> canvas = args[5]->ToString(); Local<String> canvas = args[5]->ToString();
if (canvas->Equals(String::NewSymbol("c"))) { if (canvas->Equals(NanSymbol("c"))) {
baton->crop = true; baton->crop = true;
} else if (canvas->Equals(String::NewSymbol("w"))) { } else if (canvas->Equals(NanSymbol("w"))) {
baton->crop = false;
baton->extend = VIPS_EXTEND_WHITE; baton->extend = VIPS_EXTEND_WHITE;
} else if (canvas->Equals(String::NewSymbol("b"))) { } else if (canvas->Equals(NanSymbol("b"))) {
baton->crop = false;
baton->extend = VIPS_EXTEND_BLACK; baton->extend = VIPS_EXTEND_BLACK;
} else if (canvas->Equals(NanSymbol("m"))) {
baton->crop = true;
baton->max = true;
} }
baton->sharpen = args[6]->BooleanValue(); baton->sharpen = args[6]->BooleanValue();
baton->progessive = args[7]->BooleanValue(); baton->progressive = args[7]->BooleanValue();
baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
baton->quality = args[9]->Int32Value();
baton->compressionLevel = args[10]->Int32Value();
NanCallback *callback = new NanCallback(args[9].As<v8::Function>()); NanCallback *callback = new NanCallback(args[11].As<v8::Function>());
NanAsyncQueueWorker(new ResizeWorker(callback, baton)); NanAsyncQueueWorker(new ResizeWorker(callback, baton));
NanReturnUndefined(); NanReturnUndefined();
@@ -352,17 +376,17 @@ NAN_METHOD(resize) {
NAN_METHOD(cache) { NAN_METHOD(cache) {
NanScope(); NanScope();
// Set cache limit // Set cache limit
if (args[0]->IsInt32()) { if (args[0]->IsInt32()) {
vips_cache_set_max_mem(args[0]->Int32Value() * 1048576); vips_cache_set_max_mem(args[0]->Int32Value() * 1048576);
} }
// Get cache statistics // Get cache statistics
Local<Object> cache = Object::New(); Local<Object> cache = NanNew<Object>();
cache->Set(String::NewSymbol("current"), Number::New(vips_tracked_get_mem() / 1048576)); cache->Set(NanSymbol("current"), NanNew<Number>(vips_tracked_get_mem() / 1048576));
cache->Set(String::NewSymbol("high"), Number::New(vips_tracked_get_mem_highwater() / 1048576)); cache->Set(NanSymbol("high"), NanNew<Number>(vips_tracked_get_mem_highwater() / 1048576));
cache->Set(String::NewSymbol("limit"), Number::New(vips_cache_get_max_mem() / 1048576)); cache->Set(NanSymbol("limit"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
NanReturnValue(cache); NanReturnValue(cache);
} }

View File

@@ -1,73 +1,149 @@
var sharp = require("../index"); var sharp = require("../index");
var path = require("path"); var path = require("path");
var imagemagick = require("imagemagick"); var imagemagick = require("imagemagick");
var assert = require("assert"); var assert = require("assert");
var async = require("async"); var async = require("async");
var fixturesPath = path.join(__dirname, "fixtures"); var fixturesPath = path.join(__dirname, "fixtures");
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/ var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
var outputJpg = path.join(fixturesPath, "output.jpg"); var outputJpg = path.join(fixturesPath, "output.jpg");
async.series([ var inputTiff = path.join(fixturesPath, "G31D.TIF"); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
// Resize with exact crop var outputTiff = path.join(fixturesPath, "output.tiff");
function(done) {
sharp(inputJpg).resize(320, 240).write(outputJpg, function(err) { async.series([
if (err) throw err; // Resize with exact crop
imagemagick.identify(outputJpg, function(err, features) { function(done) {
if (err) throw err; sharp(inputJpg).resize(320, 240).write(outputJpg, function(err) {
assert.strictEqual(320, features.width); if (err) throw err;
assert.strictEqual(240, features.height); imagemagick.identify(outputJpg, function(err, features) {
done(); if (err) throw err;
}); assert.strictEqual(320, features.width);
}); assert.strictEqual(240, features.height);
}, done();
// Resize to fixed width });
function(done) { });
sharp(inputJpg).resize(320).write(outputJpg, function(err) { },
if (err) throw err; // Resize to fixed width
imagemagick.identify(outputJpg, function(err, features) { function(done) {
if (err) throw err; sharp(inputJpg).resize(320).write(outputJpg, function(err) {
assert.strictEqual(320, features.width); if (err) throw err;
assert.strictEqual(261, features.height); imagemagick.identify(outputJpg, function(err, features) {
done(); if (err) throw err;
}); assert.strictEqual(320, features.width);
}); assert.strictEqual(261, features.height);
}, done();
// Resize to fixed height });
function(done) { });
sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) { },
if (err) throw err; // Resize to fixed height
imagemagick.identify(outputJpg, function(err, features) { function(done) {
if (err) throw err; sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) {
assert.strictEqual(391, features.width); if (err) throw err;
assert.strictEqual(320, features.height); imagemagick.identify(outputJpg, function(err, features) {
done(); if (err) throw err;
}); assert.strictEqual(391, features.width);
}); assert.strictEqual(320, features.height);
}, done();
// Identity transform });
function(done) { });
sharp(inputJpg).write(outputJpg, function(err) { },
if (err) throw err; // Identity transform
imagemagick.identify(outputJpg, function(err, features) { function(done) {
if (err) throw err; sharp(inputJpg).write(outputJpg, function(err) {
assert.strictEqual(2725, features.width); if (err) throw err;
assert.strictEqual(2225, features.height); imagemagick.identify(outputJpg, function(err, features) {
done(); if (err) throw err;
}); assert.strictEqual(2725, features.width);
}); assert.strictEqual(2225, features.height);
}, done();
// Upscale });
function(done) { });
sharp(inputJpg).resize(3000).write(outputJpg, function(err) { },
if (err) throw err; // Upscale
imagemagick.identify(outputJpg, function(err, features) { function(done) {
if (err) throw err; sharp(inputJpg).resize(3000).write(outputJpg, function(err) {
assert.strictEqual(3000, features.width); if (err) throw err;
assert.strictEqual(2449, features.height); imagemagick.identify(outputJpg, function(err, features) {
done(); if (err) throw err;
}); assert.strictEqual(3000, features.width);
}); assert.strictEqual(2449, features.height);
} done();
]); });
});
},
// Quality
function(done) {
sharp(inputJpg).resize(320, 240).quality(70).jpeg(function(err, buffer70) {
if (err) throw err;
sharp(inputJpg).resize(320, 240).jpeg(function(err, buffer80) {
if (err) throw err;
sharp(inputJpg).resize(320, 240).quality(90).jpeg(function(err, buffer90) {
assert(buffer70.length < buffer80.length);
assert(buffer80.length < buffer90.length);
done();
});
});
});
},
// TIFF with dimensions known to cause rounding errors
function(done) {
sharp(inputTiff).resize(240, 320).embedBlack().write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(240, features.width);
assert.strictEqual(320, features.height);
done();
});
});
},
function(done) {
sharp(inputTiff).resize(240, 320).write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(240, features.width);
assert.strictEqual(320, features.height);
done();
});
});
},
// Resize to max width or height considering ratio (landscape)
function(done) {
sharp(inputJpg).resize(320, 320).max().write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(320, features.width);
assert.strictEqual(261, features.height);
done();
});
});
},
// Resize to max width or height considering ratio (portrait)
function(done) {
sharp(inputTiff).resize(320, 320).max().write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(243, features.width);
assert.strictEqual(320, features.height);
done();
});
});
},
// Attempt to resize to max but only provide one dimension, so should default to crop
function(done) {
sharp(inputJpg).resize(320).max().write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(320, features.width);
assert.strictEqual(261, features.height);
done();
});
});
}
]);