Compare commits

..

36 Commits

Author SHA1 Message Date
Lovell Fuller
bc3311cbad Version bump 2014-05-26 09:23:41 +01:00
Lovell Fuller
7b03eb89d7 Merge pull request #39 from pierreinglebert/update-nan
update to nan 1.1.0
2014-05-26 09:15:35 +01:00
Pierre Inglebert
15a519ebd9 update to nan 1.1.0 2014-05-26 09:51:53 +02:00
Lovell Fuller
3f8e9f6487 Merge pull request #38 from pierreinglebert/fix-max-cropping-29
fix max factor #29
2014-05-24 11:54:59 +01:00
Pierre Inglebert
39688371a8 fix max factor #29 2014-05-24 10:29:33 +02:00
Lovell Fuller
e32faac17a Prevent writing output file over input file - closes #28 2014-05-22 22:27:02 +01:00
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 473 additions and 275 deletions

2
.gitignore vendored
View File

@@ -13,5 +13,3 @@ results
build
node_modules
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
node_js:
- "0.10"
before_install:
- sudo add-apt-repository ppa:lyrasis/precise-backports -y
- sudo apt-get update -qq
- 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
- git clone https://github.com/jcupitt/libvips.git
- cd libvips
- git checkout 7.38
- ./bootstrap.sh
- ./configure --enable-debug=no --enable-cxx=no --without-python
- make
- sudo make install
- sudo ldconfig
- cd $TRAVIS_BUILD_DIR
language: node_js
node_js:
- "0.10"
- "0.11"
before_install:
- sudo add-apt-repository ppa:lyrasis/precise-backports -y
- sudo apt-get update -qq
- 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
- git clone https://github.com/jcupitt/libvips.git
- cd libvips
- git checkout 7.38
- ./bootstrap.sh
- ./configure --enable-debug=no --enable-cxx=no --without-orc --without-python
- make
- sudo make install
- 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+
* [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
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
### 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
cd libvips
git checkout 7.38
./bootstrap.sh
./configure --enable-debug=no --enable-cxx=no --without-python
./configure --enable-debug=no --enable-cxx=no --without-python --without-orc
make
sudo make install
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 apt-get update
sudo apt-get install libtiff4-dev
Then follow Ubuntu 13.x instructions.
## Usage examples
```javascript
@@ -71,25 +85,25 @@ sharp('input.jpg').resize(300, 200).write('output.jpg', function(err) {
```
```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) {
throw err;
}
// buffer contains progressive JPEG image data, 200 pixels high
// outputBuffer contains progressive JPEG image data, 200 pixels high
});
```
```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) {
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
sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
sharp(inputBuffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
if (err) {
throw err;
}
@@ -99,15 +113,25 @@ sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
```
```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) {
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
});
```
```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
### 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.
### 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()
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.
### 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()
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/lib/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\')")'
],
'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'],

288
index.js
View File

@@ -1,128 +1,160 @@
/*jslint node: true */
'use strict';
var sharp = require('./build/Release/sharp');
var Sharp = function(input) {
if (!(this instanceof Sharp)) {
return new Sharp(input);
}
this.options = {
width: -1,
height: -1,
canvas: 'c',
sharpen: false,
progressive: false,
sequentialRead: false,
output: '__jpeg'
};
if (typeof input === 'string') {
this.options.inFile = input;
} else if (typeof input ==='object' && input instanceof Buffer) {
this.options.inBuffer = input;
} else {
throw 'Unsupported input ' + typeof input;
}
return this;
};
module.exports = Sharp;
Sharp.prototype.crop = function() {
this.options.canvas = 'c';
return this;
};
Sharp.prototype.embedWhite = function() {
this.options.canvas = 'w';
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.progressive = function(progressive) {
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this;
};
Sharp.prototype.sequentialRead = function(sequentialRead) {
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
return this;
};
Sharp.prototype.resize = function(width, height) {
if (!width) {
this.options.width = -1;
} else {
if (!Number.isNaN(width)) {
this.options.width = width;
} else {
throw 'Invalid width ' + width;
}
}
if (!height) {
this.options.height = -1;
} else {
if (!Number.isNaN(height)) {
this.options.height = height;
} else {
throw 'Invalid height ' + height;
}
}
return this;
};
Sharp.prototype.write = function(output, callback) {
if (!output || output.length === 0) {
throw 'Invalid output';
} else {
this._sharp(output, callback);
}
return this;
};
Sharp.prototype.toBuffer = function(callback) {
return this._sharp('__input', callback);
};
Sharp.prototype.jpeg = function(callback) {
return this._sharp('__jpeg', callback);
};
Sharp.prototype.png = function(callback) {
return this._sharp('__png', callback);
};
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,
callback
);
return this;
};
module.exports.cache = function(limit) {
if (Number.isNaN(limit)) {
limit = null;
}
return sharp.cache(limit);
};
/*jslint node: true */
'use strict';
var sharp = require('./build/Release/sharp');
var Sharp = function(input) {
if (!(this instanceof Sharp)) {
return new Sharp(input);
}
this.options = {
width: -1,
height: -1,
canvas: 'c',
sharpen: false,
progressive: false,
sequentialRead: false,
quality: 80,
compressionLevel: 6,
output: '__jpeg'
};
if (typeof input === 'string') {
this.options.inFile = input;
} else if (typeof input ==='object' && input instanceof Buffer) {
this.options.inBuffer = input;
} else {
throw 'Unsupported input ' + typeof input;
}
return this;
};
module.exports = Sharp;
Sharp.prototype.crop = function() {
this.options.canvas = 'c';
return this;
};
Sharp.prototype.embedWhite = function() {
this.options.canvas = 'w';
return this;
};
Sharp.prototype.embedBlack = function() {
this.options.canvas = 'b';
return this;
};
Sharp.prototype.max = function() {
this.options.canvas = 'm';
return this;
};
Sharp.prototype.sharpen = function(sharpen) {
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
return this;
};
Sharp.prototype.progressive = function(progressive) {
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this;
};
Sharp.prototype.sequentialRead = function(sequentialRead) {
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
return this;
};
Sharp.prototype.quality = function(quality) {
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
this.options.quality = quality;
} else {
throw 'Invalid quality (1 to 100) ' + quality;
}
return this;
};
Sharp.prototype.compressionLevel = function(compressionLevel) {
if (!Number.isNaN(compressionLevel) && compressionLevel >= -1 && compressionLevel <= 9) {
this.options.compressionLevel = compressionLevel;
} else {
throw 'Invalid compressionLevel (-1 to 9) ' + compressionLevel;
}
return this;
};
Sharp.prototype.resize = function(width, height) {
if (!width) {
this.options.width = -1;
} else {
if (!Number.isNaN(width)) {
this.options.width = width;
} else {
throw 'Invalid width ' + width;
}
}
if (!height) {
this.options.height = -1;
} else {
if (!Number.isNaN(height)) {
this.options.height = height;
} else {
throw 'Invalid height ' + height;
}
}
return this;
};
Sharp.prototype.write = function(output, callback) {
if (!output || output.length === 0) {
callback('Invalid output');
} else {
if (this.options.inFile === output) {
callback('Cannot use same file for input and output');
} else {
this._sharp(output, callback);
}
}
return this;
};
Sharp.prototype.toBuffer = function(callback) {
return this._sharp('__input', callback);
};
Sharp.prototype.jpeg = function(callback) {
return this._sharp('__jpeg', callback);
};
Sharp.prototype.png = function(callback) {
return this._sharp('__png', callback);
};
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",
"version": "0.3.0",
"version": "0.4.2",
"author": "Lovell Fuller <npm@lovell.info>",
"contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>"
@@ -31,13 +31,13 @@
"buffer"
],
"dependencies": {
"nan": "^0.8.0"
"nan": "^1.1.0"
},
"devDependencies": {
"imagemagick": "^0.1.3",
"imagemagick-native": "^0.2.9",
"gm": "^1.14.2",
"async": "^0.6.2",
"imagemagick-native": "^1.0.0",
"gm": "^1.16.0",
"async": "^0.9.0",
"benchmark": "^1.0.0"
},
"license": "Apache 2.0",

View File

@@ -20,13 +20,16 @@ struct resize_baton {
int width;
int height;
bool crop;
bool max;
VipsExtend extend;
bool sharpen;
bool progessive;
bool progressive;
VipsAccess access_method;
int quality;
int compressionLevel;
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 {
@@ -133,17 +136,25 @@ class ResizeWorker : public NanAsyncWorker {
double factor;
if (baton->width > 0 && baton->height > 0) {
// Fixed width and height
double xfactor = (double)(in->Xsize) / (double)(baton->width);
double yfactor = (double)(in->Ysize) / (double)(baton->height);
double xfactor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
double yfactor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
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) {
// Fixed width, auto height
factor = (double)(in->Xsize) / (double)(baton->width);
baton->height = floor((double)(in->Ysize) / factor);
factor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
baton->height = floor(static_cast<double>(in->Ysize) / factor);
} else if (baton->height > 0) {
// Fixed height, auto width
factor = (double)(in->Ysize) / (double)(baton->height);
baton->width = floor((double)(in->Xsize) / factor);
factor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
baton->width = floor(static_cast<double>(in->Xsize) / factor);
} else {
// Identity transform
factor = 1;
@@ -154,7 +165,7 @@ class ResizeWorker : public NanAsyncWorker {
if (shrink < 1) {
shrink = 1;
}
double residual = shrink / (double)factor;
double residual = static_cast<double>(shrink) / factor;
// Try to use libjpeg shrink-on-load
int shrink_on_load = 1;
@@ -176,7 +187,7 @@ class ResizeWorker : public NanAsyncWorker {
factor = std::max(factor, 1.0);
shrink = floor(factor);
residual = shrink / factor;
// Reload input using shrink-on-load
// Reload input using shrink-on-load
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)) {
return resize_error(baton, in);
@@ -197,6 +208,14 @@ class ResizeWorker : public NanAsyncWorker {
if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) {
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 || baton->max) {
residual = std::max(residualx, residualy);
} else {
residual = std::min(residualx, residualy);
}
} else {
vips_copy(shrunk_on_load, &shrunk, NULL);
}
@@ -216,8 +235,8 @@ class ResizeWorker : public NanAsyncWorker {
// Crop/embed
VipsImage *canvased = vips_image_new();
if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
if (baton->crop) {
// Crop
if (baton->crop || baton->max) {
// Crop/max
int width = std::min(affined->Xsize, baton->width);
int height = std::min(affined->Ysize, baton->height);
int left = (affined->Xsize - width + 1) / 2;
@@ -241,14 +260,16 @@ class ResizeWorker : public NanAsyncWorker {
// Mild sharpen
VipsImage *sharpened = vips_image_new();
if (baton->sharpen) {
INTMASK* sharpen = im_create_imaskv("sharpen", 3, 3,
-1, -1, -1,
-1, 32, -1,
-1, -1, -1);
sharpen->scale = 24;
if (im_conv(canvased, sharpened, sharpen)) {
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
-1.0, -1.0, -1.0,
-1.0, 32.0, -1.0,
-1.0, -1.0, -1.0);
vips_image_set_double(sharpen, "scale", 24);
if (vips_conv(canvased, &sharpened, sharpen, NULL)) {
g_object_unref(sharpen);
return resize_error(baton, canvased);
}
g_object_unref(sharpen);
} else {
vips_copy(canvased, &sharpened, NULL);
}
@@ -257,37 +278,37 @@ class ResizeWorker : public NanAsyncWorker {
// Output
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
// 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);
}
} else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
// 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);
}
} else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
// 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);
}
} else if (is_jpeg(baton->file_out)) {
// 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);
}
} else if (is_png(baton->file_out)) {
// 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);
}
} else if (is_webp(baton->file_out)) {
// 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);
}
} else if (is_tiff(baton->file_out)) {
// 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);
}
} else {
@@ -300,10 +321,10 @@ class ResizeWorker : public NanAsyncWorker {
void HandleOKCallback () {
NanScope();
Handle<Value> argv[2] = { Null(), Null() };
Handle<Value> argv[2] = { NanNull(), NanNull() };
if (!baton->err.empty()) {
// 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) {
// Buffer
argv[1] = NanNewBufferHandle((char *)baton->buffer_out, baton->buffer_out_len);
@@ -319,7 +340,7 @@ class ResizeWorker : public NanAsyncWorker {
NAN_METHOD(resize) {
NanScope();
resize_baton *baton = new resize_baton;
baton->file_in = *String::Utf8Value(args[0]->ToString());
if (args[1]->IsObject()) {
@@ -331,20 +352,22 @@ NAN_METHOD(resize) {
baton->width = args[3]->Int32Value();
baton->height = args[4]->Int32Value();
Local<String> canvas = args[5]->ToString();
if (canvas->Equals(String::NewSymbol("c"))) {
if (canvas->Equals(NanNew<String>("c"))) {
baton->crop = true;
} else if (canvas->Equals(String::NewSymbol("w"))) {
baton->crop = false;
} else if (canvas->Equals(NanNew<String>("w"))) {
baton->extend = VIPS_EXTEND_WHITE;
} else if (canvas->Equals(String::NewSymbol("b"))) {
baton->crop = false;
} else if (canvas->Equals(NanNew<String>("b"))) {
baton->extend = VIPS_EXTEND_BLACK;
} else if (canvas->Equals(NanNew<String>("m"))) {
baton->max = true;
}
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->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));
NanReturnUndefined();
@@ -352,17 +375,17 @@ NAN_METHOD(resize) {
NAN_METHOD(cache) {
NanScope();
// Set cache limit
if (args[0]->IsInt32()) {
vips_cache_set_max_mem(args[0]->Int32Value() * 1048576);
}
// Get cache statistics
Local<Object> cache = Object::New();
cache->Set(String::NewSymbol("current"), Number::New(vips_tracked_get_mem() / 1048576));
cache->Set(String::NewSymbol("high"), Number::New(vips_tracked_get_mem_highwater() / 1048576));
cache->Set(String::NewSymbol("limit"), Number::New(vips_cache_get_max_mem() / 1048576));
Local<Object> cache = NanNew<Object>();
cache->Set(NanNew<String>("current"), NanNew<Number>(vips_tracked_get_mem() / 1048576));
cache->Set(NanNew<String>("high"), NanNew<Number>(vips_tracked_get_mem_highwater() / 1048576));
cache->Set(NanNew<String>("limit"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
NanReturnValue(cache);
}

View File

@@ -1,73 +1,156 @@
var sharp = require("../index");
var path = require("path");
var imagemagick = require("imagemagick");
var assert = require("assert");
var async = require("async");
var fixturesPath = path.join(__dirname, "fixtures");
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
var outputJpg = path.join(fixturesPath, "output.jpg");
async.series([
// Resize with exact crop
function(done) {
sharp(inputJpg).resize(320, 240).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(240, features.height);
done();
});
});
},
// Resize to fixed width
function(done) {
sharp(inputJpg).resize(320).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 fixed height
function(done) {
sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
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;
imagemagick.identify(outputJpg, function(err, features) {
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;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(3000, features.width);
assert.strictEqual(2449, features.height);
done();
});
});
}
]);
var sharp = require("../index");
var path = require("path");
var imagemagick = require("imagemagick");
var assert = require("assert");
var async = require("async");
var fixturesPath = path.join(__dirname, "fixtures");
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
var outputJpg = path.join(fixturesPath, "output.jpg");
var inputTiff = path.join(fixturesPath, "G31D.TIF"); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
var outputTiff = path.join(fixturesPath, "output.tiff");
async.series([
// Resize with exact crop
function(done) {
sharp(inputJpg).resize(320, 240).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(240, features.height);
done();
});
});
},
// Resize to fixed width
function(done) {
sharp(inputJpg).resize(320).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 fixed height
function(done) {
sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
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;
imagemagick.identify(outputJpg, function(err, features) {
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;
imagemagick.identify(outputJpg, function(err, features) {
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();
});
});
},
// Attempt to output to input, should fail
function(done) {
sharp(inputJpg).write(inputJpg, function(err) {
assert(!!err);
done();
});
}
]);