mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 13:46:19 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ccc2bca97 | ||
|
|
46b701c85c | ||
|
|
7319533969 | ||
|
|
9a05684302 | ||
|
|
906311d403 | ||
|
|
4de9a2435f | ||
|
|
a94dd2b354 |
@@ -5,7 +5,7 @@ node_js:
|
||||
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
|
||||
- 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 libexif-dev libxml2-dev swig graphicsmagick libmagick++-dev
|
||||
- git clone https://github.com/jcupitt/libvips.git
|
||||
- cd libvips
|
||||
- git checkout 7.38
|
||||
|
||||
81
README.md
81
README.md
@@ -9,7 +9,7 @@
|
||||
|
||||
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||
|
||||
The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available. Everything remains non-blocking thanks to _libuv_.
|
||||
The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available. Everything remains non-blocking thanks to _libuv_ and Promises/A+ are supported.
|
||||
|
||||
This module supports reading and writing images of JPEG, PNG and WebP to and from both Buffer objects and the filesystem. It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present.
|
||||
|
||||
@@ -28,7 +28,7 @@ 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.
|
||||
_libvips_ can take advantage of [liborc](http://code.entropywave.com/orc/) if present. Warning: versions of _liborc_ prior to 0.4.19 suffer [memory leaks](https://github.com/lovell/sharp/issues/21#issuecomment-42367306) and version 0.4.19 suffers [buffer overflows](https://github.com/lovell/sharp/issues/21#issuecomment-44813498).
|
||||
|
||||
### Install libvips on Mac OS
|
||||
|
||||
@@ -48,7 +48,7 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
|
||||
|
||||
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
|
||||
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 libexif-dev libxml2-dev swig
|
||||
git clone https://github.com/jcupitt/libvips.git
|
||||
cd libvips
|
||||
git checkout 7.38
|
||||
@@ -60,7 +60,7 @@ Compiling from source is recommended:
|
||||
|
||||
#### 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:
|
||||
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 commands first:
|
||||
|
||||
sudo add-apt-repository ppa:lyrasis/precise-backports
|
||||
sudo apt-get update
|
||||
@@ -75,7 +75,7 @@ var sharp = require('sharp');
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp('input.jpg').resize(300, 200).write('output.jpg', function(err) {
|
||||
sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
@@ -85,28 +85,22 @@ sharp('input.jpg').resize(300, 200).write('output.jpg', function(err) {
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, outputBuffer) {
|
||||
sharp('input.jpg').rotate().resize(null, 200).progressive().toBuffer(function(err, outputBuffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// outputBuffer contains progressive JPEG image data, 200 pixels high
|
||||
// outputBuffer contains 200px high progressive JPEG image data, auto-rotated using EXIF Orientation tag
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp('input.png').resize(300).sharpen().quality(90).webp(function(err, outputBuffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// outputBuffer contains 300 pixels wide, sharpened, 90% quality WebP image data
|
||||
sharp('input.png').rotate(180).resize(300).sharpen().quality(90).webp().then(function(outputBuffer) {
|
||||
// outputBuffer contains 300px wide, upside down, sharpened, 90% quality WebP image data
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp(inputBuffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
sharp(inputBuffer).resize(200, 300).embedWhite().toFile('output.tiff').then(function() {
|
||||
// output.tiff is a 200 pixels wide and 300 pixels high image containing a scaled
|
||||
// version, embedded on a white canvas, of the image data in buffer
|
||||
});
|
||||
@@ -123,10 +117,7 @@ sharp('input.gif').resize(200, 300).embedBlack().webp(function(err, outputBuffer
|
||||
```
|
||||
|
||||
```javascript
|
||||
sharp(inputBuffer).resize(200, 200).max().jpeg(function(err, outputBuffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
sharp(inputBuffer).resize(200, 200).max().jpeg().then(function(outputBuffer) {
|
||||
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
|
||||
// than 200 pixels regardless of the inputBuffer image dimensions
|
||||
});
|
||||
@@ -143,7 +134,7 @@ Constructor to which further methods are chained. `input` can be one of:
|
||||
|
||||
### resize(width, [height])
|
||||
|
||||
Scale to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
||||
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
||||
|
||||
`width` is the Number of pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
|
||||
@@ -167,6 +158,20 @@ Embed the resized image on a white background of the exact size specified.
|
||||
|
||||
Embed the resized image on a black background of the exact size specified.
|
||||
|
||||
### rotate([angle])
|
||||
|
||||
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag. Mirroring is not supported.
|
||||
|
||||
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
|
||||
|
||||
Use this method without `angle` to determine the angle from EXIF data.
|
||||
|
||||
### withoutEnlargement()
|
||||
|
||||
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
|
||||
|
||||
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
||||
|
||||
### sharpen()
|
||||
|
||||
Perform a mild sharpen of the resultant image. This typically reduces performance by 30%.
|
||||
@@ -191,35 +196,45 @@ An advanced setting for the _zlib_ compression level of the lossless PNG output
|
||||
|
||||
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.
|
||||
|
||||
### write(filename, callback)
|
||||
### toFile(filename, [callback])
|
||||
|
||||
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP and TIFF supported.
|
||||
|
||||
`callback` is called with a single argument `(err)` containing an error message, if any.
|
||||
`callback`, if present, is called with a single argument `(err)` containing an error message, if any.
|
||||
|
||||
### jpeg(callback)
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
### toBuffer([callback])
|
||||
|
||||
Write image data to a Buffer, the format of which will match the input image. JPEG, PNG and WebP are supported.
|
||||
|
||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
### jpeg([callback])
|
||||
|
||||
Write JPEG image data to a Buffer.
|
||||
|
||||
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant JPEG image data.
|
||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant JPEG image data.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
### png(callback)
|
||||
|
||||
Write PNG image data to a Buffer.
|
||||
|
||||
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant PNG image data.
|
||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant PNG image data.
|
||||
|
||||
### webp(callback)
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
### webp([callback])
|
||||
|
||||
Write WebP image data to a Buffer.
|
||||
|
||||
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant WebP image data.
|
||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant WebP image data.
|
||||
|
||||
### toBuffer(callback)
|
||||
|
||||
Write image data to a Buffer, the format of which will match the input image. JPEG, PNG and WebP are supported.
|
||||
|
||||
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data.
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
### sharp.cache([limit])
|
||||
|
||||
|
||||
83
index.js
83
index.js
@@ -1,6 +1,7 @@
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var sharp = require('./build/Release/sharp');
|
||||
|
||||
var Sharp = function(input) {
|
||||
@@ -11,6 +12,8 @@ var Sharp = function(input) {
|
||||
width: -1,
|
||||
height: -1,
|
||||
canvas: 'c',
|
||||
angle: 0,
|
||||
withoutEnlargement: false,
|
||||
sharpen: false,
|
||||
progressive: false,
|
||||
sequentialRead: false,
|
||||
@@ -19,9 +22,13 @@ var Sharp = function(input) {
|
||||
output: '__jpeg'
|
||||
};
|
||||
if (typeof input === 'string') {
|
||||
this.options.inFile = input;
|
||||
this.options.fileIn = input;
|
||||
} else if (typeof input ==='object' && input instanceof Buffer) {
|
||||
this.options.inBuffer = input;
|
||||
if (input.length > 0) {
|
||||
this.options.bufferIn = input;
|
||||
} else {
|
||||
throw 'Buffer is empty';
|
||||
}
|
||||
} else {
|
||||
throw 'Unsupported input ' + typeof input;
|
||||
}
|
||||
@@ -49,6 +56,30 @@ Sharp.prototype.max = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Rotate output image by 0, 90, 180 or 270 degrees
|
||||
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
|
||||
*/
|
||||
Sharp.prototype.rotate = function(angle) {
|
||||
if (typeof angle === 'undefined') {
|
||||
this.options.angle = -1;
|
||||
} else if (!Number.isNaN(angle) && [0, 90, 180, 270].indexOf(angle) !== -1) {
|
||||
this.options.angle = angle;
|
||||
} else {
|
||||
throw 'Unsupport angle (0, 90, 180, 270) ' + angle;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
||||
This is equivalent to GraphicsMagick's ">" geometry option:
|
||||
"change the dimensions of the image only if its width or height exceeds the geometry specification"
|
||||
*/
|
||||
Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
|
||||
this.options.withoutEnlargement = (typeof withoutEnlargement === 'boolean') ? withoutEnlargement : true;
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.sharpen = function(sharpen) {
|
||||
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
|
||||
@@ -105,19 +136,25 @@ Sharp.prototype.resize = function(width, height) {
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.write = function(output, callback) {
|
||||
/*
|
||||
Write output image data to a file
|
||||
*/
|
||||
Sharp.prototype.toFile = function(output, callback) {
|
||||
if (!output || output.length === 0) {
|
||||
callback('Invalid output');
|
||||
} else {
|
||||
if (this.options.inFile === output) {
|
||||
if (this.options.fileIn === output) {
|
||||
callback('Cannot use same file for input and output');
|
||||
} else {
|
||||
this._sharp(output, callback);
|
||||
return this._sharp(output, callback);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
// Deprecated to make way for future stream support - remove in v0.6.0
|
||||
Sharp.prototype.write = Sharp.prototype.toFile;
|
||||
|
||||
Sharp.prototype.toBuffer = function(callback) {
|
||||
return this._sharp('__input', callback);
|
||||
};
|
||||
@@ -134,22 +171,28 @@ Sharp.prototype.webp = function(callback) {
|
||||
return this._sharp('__webp', callback);
|
||||
};
|
||||
|
||||
/*
|
||||
Invoke the C++ image processing pipeline
|
||||
Supports callback and promise variants
|
||||
*/
|
||||
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;
|
||||
if (typeof callback === 'function') {
|
||||
// I like callbacks
|
||||
sharp.resize(this.options, output, callback);
|
||||
return this;
|
||||
} else {
|
||||
// I like promises
|
||||
var options = this.options;
|
||||
return new Promise(function(resolve, reject) {
|
||||
sharp.resize(options, output, function(err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.cache = function(limit) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"version": "0.4.2",
|
||||
"version": "0.5.0",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"contributors": [
|
||||
"Pierre Inglebert <pierre.inglebert@gmail.com>"
|
||||
@@ -31,7 +31,8 @@
|
||||
"buffer"
|
||||
],
|
||||
"dependencies": {
|
||||
"nan": "^1.1.0"
|
||||
"nan": "^1.1.2",
|
||||
"bluebird": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"imagemagick": "^0.1.3",
|
||||
|
||||
198
src/sharp.cc
198
src/sharp.cc
@@ -24,12 +24,21 @@ struct resize_baton {
|
||||
VipsExtend extend;
|
||||
bool sharpen;
|
||||
bool progressive;
|
||||
bool without_enlargement;
|
||||
VipsAccess access_method;
|
||||
int quality;
|
||||
int compressionLevel;
|
||||
int angle;
|
||||
std::string err;
|
||||
|
||||
resize_baton(): buffer_in_len(0), buffer_out_len(0), crop(false), max(false), sharpen(false), progressive(false) {}
|
||||
resize_baton():
|
||||
buffer_in_len(0),
|
||||
buffer_out_len(0),
|
||||
crop(false),
|
||||
max(false),
|
||||
sharpen(false),
|
||||
progressive(false),
|
||||
without_enlargement(false) {}
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
@@ -40,31 +49,31 @@ typedef enum {
|
||||
MAGICK
|
||||
} ImageType;
|
||||
|
||||
unsigned char MARKER_JPEG[] = {0xff, 0xd8};
|
||||
unsigned char MARKER_PNG[] = {0x89, 0x50};
|
||||
unsigned char MARKER_WEBP[] = {0x52, 0x49};
|
||||
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
|
||||
unsigned char const MARKER_PNG[] = {0x89, 0x50};
|
||||
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
|
||||
|
||||
bool ends_with(std::string const &str, std::string const &end) {
|
||||
static bool ends_with(std::string const &str, std::string const &end) {
|
||||
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
|
||||
}
|
||||
|
||||
bool is_jpeg(std::string const &str) {
|
||||
static bool is_jpeg(std::string const &str) {
|
||||
return ends_with(str, ".jpg") || ends_with(str, ".jpeg") || ends_with(str, ".JPG") || ends_with(str, ".JPEG");
|
||||
}
|
||||
|
||||
bool is_png(std::string const &str) {
|
||||
static bool is_png(std::string const &str) {
|
||||
return ends_with(str, ".png") || ends_with(str, ".PNG");
|
||||
}
|
||||
|
||||
bool is_webp(std::string const &str) {
|
||||
static bool is_webp(std::string const &str) {
|
||||
return ends_with(str, ".webp") || ends_with(str, ".WEBP");
|
||||
}
|
||||
|
||||
bool is_tiff(std::string const &str) {
|
||||
static bool is_tiff(std::string const &str) {
|
||||
return ends_with(str, ".tif") || ends_with(str, ".tiff") || ends_with(str, ".TIF") || ends_with(str, ".TIFF");
|
||||
}
|
||||
|
||||
void resize_error(resize_baton *baton, VipsImage *unref) {
|
||||
static void resize_error(resize_baton *baton, VipsImage *unref) {
|
||||
(baton->err).append(vips_error_buffer());
|
||||
vips_error_clear();
|
||||
g_object_unref(unref);
|
||||
@@ -72,6 +81,38 @@ void resize_error(resize_baton *baton, VipsImage *unref) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the angle of rotation for the output image.
|
||||
In order of priority:
|
||||
1. Use explicitly requested angle (supports 90, 180, 270)
|
||||
2. Use input image EXIF Orientation header (does not support mirroring)
|
||||
3. Otherwise default to zero, i.e. no rotation
|
||||
*/
|
||||
static VipsAngle calc_rotation(int const angle, VipsImage const *input) {
|
||||
VipsAngle rotate = VIPS_ANGLE_0;
|
||||
if (angle == -1) {
|
||||
const char *exif;
|
||||
if (!vips_image_get_string(input, "exif-ifd0-Orientation", &exif)) {
|
||||
if (exif[0] == 0x36) { // "6"
|
||||
rotate = VIPS_ANGLE_90;
|
||||
} else if (exif[0] == 0x33) { // "3"
|
||||
rotate = VIPS_ANGLE_180;
|
||||
} else if (exif[0] == 0x38) { // "8"
|
||||
rotate = VIPS_ANGLE_270;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (angle == 90) {
|
||||
rotate = VIPS_ANGLE_90;
|
||||
} else if (angle == 180) {
|
||||
rotate = VIPS_ANGLE_180;
|
||||
} else if (angle == 270) {
|
||||
rotate = VIPS_ANGLE_270;
|
||||
}
|
||||
}
|
||||
return rotate;
|
||||
}
|
||||
|
||||
class ResizeWorker : public NanAsyncWorker {
|
||||
public:
|
||||
ResizeWorker(NanCallback *callback, resize_baton *baton)
|
||||
@@ -132,34 +173,47 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get input image width and height
|
||||
int inputWidth = in->Xsize;
|
||||
int inputHeight = in->Ysize;
|
||||
|
||||
// Calculate angle of rotation, to be carried out later
|
||||
VipsAngle rotation = calc_rotation(baton->angle, in);
|
||||
if (rotation == VIPS_ANGLE_90 || rotation == VIPS_ANGLE_270) {
|
||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
||||
int swap = inputWidth;
|
||||
inputWidth = inputHeight;
|
||||
inputHeight = swap;
|
||||
}
|
||||
|
||||
// Scaling calculations
|
||||
double factor;
|
||||
if (baton->width > 0 && baton->height > 0) {
|
||||
// Fixed width and 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);
|
||||
double xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
||||
double yfactor = static_cast<double>(inputHeight) / 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);
|
||||
baton->height = round(static_cast<double>(inputHeight) / xfactor);
|
||||
} else {
|
||||
baton->width = round(static_cast<double>(in->Xsize) / yfactor);
|
||||
baton->width = round(static_cast<double>(inputWidth) / yfactor);
|
||||
}
|
||||
}
|
||||
} else if (baton->width > 0) {
|
||||
// Fixed width, auto height
|
||||
factor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
|
||||
baton->height = floor(static_cast<double>(in->Ysize) / factor);
|
||||
factor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
||||
baton->height = floor(static_cast<double>(inputHeight) / factor);
|
||||
} else if (baton->height > 0) {
|
||||
// Fixed height, auto width
|
||||
factor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
|
||||
baton->width = floor(static_cast<double>(in->Xsize) / factor);
|
||||
factor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
||||
baton->width = floor(static_cast<double>(inputWidth) / factor);
|
||||
} else {
|
||||
// Identity transform
|
||||
factor = 1;
|
||||
baton->width = in->Xsize;
|
||||
baton->height = in->Ysize;
|
||||
baton->width = inputWidth;
|
||||
baton->height = inputHeight;
|
||||
}
|
||||
int shrink = floor(factor);
|
||||
if (shrink < 1) {
|
||||
@@ -167,6 +221,17 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
double residual = static_cast<double>(shrink) / factor;
|
||||
|
||||
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
||||
if (baton->without_enlargement) {
|
||||
if (inputWidth < baton->width || inputHeight < baton->height) {
|
||||
factor = 1;
|
||||
shrink = 1;
|
||||
residual = 0;
|
||||
baton->width = inputWidth;
|
||||
baton->height = inputHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to use libjpeg shrink-on-load
|
||||
int shrink_on_load = 1;
|
||||
if (inputImageType == JPEG) {
|
||||
@@ -186,7 +251,7 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
// Recalculate integral shrink and double residual
|
||||
factor = std::max(factor, 1.0);
|
||||
shrink = floor(factor);
|
||||
residual = shrink / factor;
|
||||
residual = static_cast<double>(shrink) / factor;
|
||||
// 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)) {
|
||||
@@ -209,8 +274,16 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
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);
|
||||
double shrunkWidth = shrunk->Xsize;
|
||||
double shrunkHeight = shrunk->Ysize;
|
||||
if (rotation == VIPS_ANGLE_90 || rotation == VIPS_ANGLE_270) {
|
||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
||||
int swap = shrunkWidth;
|
||||
shrunkWidth = shrunkHeight;
|
||||
shrunkHeight = swap;
|
||||
}
|
||||
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth);
|
||||
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight);
|
||||
if (baton->crop || baton->max) {
|
||||
residual = std::max(residualx, residualy);
|
||||
} else {
|
||||
@@ -232,30 +305,41 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
g_object_unref(shrunk);
|
||||
|
||||
// Rotate
|
||||
VipsImage *rotated = vips_image_new();
|
||||
if (rotation != VIPS_ANGLE_0) {
|
||||
if (vips_rot(affined, &rotated, rotation, NULL)) {
|
||||
return resize_error(baton, affined);
|
||||
}
|
||||
} else {
|
||||
vips_copy(affined, &rotated, NULL);
|
||||
}
|
||||
g_object_unref(affined);
|
||||
|
||||
// Crop/embed
|
||||
VipsImage *canvased = vips_image_new();
|
||||
if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
|
||||
if (rotated->Xsize != baton->width || rotated->Ysize != baton->height) {
|
||||
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;
|
||||
int top = (affined->Ysize - height + 1) / 2;
|
||||
if (vips_extract_area(affined, &canvased, left, top, width, height, NULL)) {
|
||||
return resize_error(baton, affined);
|
||||
int width = std::min(rotated->Xsize, baton->width);
|
||||
int height = std::min(rotated->Ysize, baton->height);
|
||||
int left = (rotated->Xsize - width + 1) / 2;
|
||||
int top = (rotated->Ysize - height + 1) / 2;
|
||||
if (vips_extract_area(rotated, &canvased, left, top, width, height, NULL)) {
|
||||
return resize_error(baton, rotated);
|
||||
}
|
||||
} else {
|
||||
// Embed
|
||||
int left = (baton->width - affined->Xsize) / 2;
|
||||
int top = (baton->height - affined->Ysize) / 2;
|
||||
if (vips_embed(affined, &canvased, left, top, baton->width, baton->height, "extend", baton->extend, NULL)) {
|
||||
return resize_error(baton, affined);
|
||||
int left = (baton->width - rotated->Xsize) / 2;
|
||||
int top = (baton->height - rotated->Ysize) / 2;
|
||||
if (vips_embed(rotated, &canvased, left, top, baton->width, baton->height, "extend", baton->extend, NULL)) {
|
||||
return resize_error(baton, rotated);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vips_copy(affined, &canvased, NULL);
|
||||
vips_copy(rotated, &canvased, NULL);
|
||||
}
|
||||
g_object_unref(affined);
|
||||
g_object_unref(rotated);
|
||||
|
||||
// Mild sharpen
|
||||
VipsImage *sharpened = vips_image_new();
|
||||
@@ -338,20 +422,29 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
resize_baton* baton;
|
||||
};
|
||||
|
||||
/*
|
||||
resize(options, output, callback)
|
||||
*/
|
||||
NAN_METHOD(resize) {
|
||||
NanScope();
|
||||
|
||||
// V8 objects are converted to non-V8 types held in the baton struct
|
||||
resize_baton *baton = new resize_baton;
|
||||
baton->file_in = *String::Utf8Value(args[0]->ToString());
|
||||
if (args[1]->IsObject()) {
|
||||
Local<Object> buffer = args[1]->ToObject();
|
||||
Local<Object> options = args[0]->ToObject();
|
||||
|
||||
// Input filename
|
||||
baton->file_in = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
||||
// Input Buffer object
|
||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
||||
baton->buffer_in_len = Buffer::Length(buffer);
|
||||
baton->buffer_in = Buffer::Data(buffer);
|
||||
}
|
||||
baton->file_out = *String::Utf8Value(args[2]->ToString());
|
||||
baton->width = args[3]->Int32Value();
|
||||
baton->height = args[4]->Int32Value();
|
||||
Local<String> canvas = args[5]->ToString();
|
||||
// Output image dimensions
|
||||
baton->width = options->Get(NanNew<String>("width"))->Int32Value();
|
||||
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
|
||||
// Canvas options
|
||||
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
|
||||
if (canvas->Equals(NanNew<String>("c"))) {
|
||||
baton->crop = true;
|
||||
} else if (canvas->Equals(NanNew<String>("w"))) {
|
||||
@@ -361,14 +454,19 @@ NAN_METHOD(resize) {
|
||||
} else if (canvas->Equals(NanNew<String>("m"))) {
|
||||
baton->max = true;
|
||||
}
|
||||
baton->sharpen = args[6]->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[11].As<v8::Function>());
|
||||
// Other options
|
||||
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
||||
baton->without_enlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
|
||||
baton->access_method = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
|
||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
||||
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
||||
// Output filename or __format for Buffer
|
||||
baton->file_out = *String::Utf8Value(args[1]->ToString());
|
||||
|
||||
// Join queue for worker thread
|
||||
NanCallback *callback = new NanCallback(args[2].As<v8::Function>());
|
||||
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
||||
NanReturnUndefined();
|
||||
}
|
||||
|
||||
BIN
tests/fixtures/Landscape_8.jpg
vendored
Normal file
BIN
tests/fixtures/Landscape_8.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
@@ -111,7 +111,7 @@ async.series({
|
||||
}).add("sharp-buffer-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpgBuffer).resize(width, height).write(outputJpg, function(err) {
|
||||
sharp(inputJpgBuffer).resize(width, height).toFile(outputJpg, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -134,7 +134,7 @@ async.series({
|
||||
}).add("sharp-file-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).write(outputJpg, function(err) {
|
||||
sharp(inputJpg).resize(width, height).toFile(outputJpg, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -178,6 +178,18 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add("sharp-file-buffer-rotate", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).rotate(90).resize(width, height).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add("sharp-file-buffer-sequentialRead", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
@@ -251,7 +263,7 @@ async.series({
|
||||
}).add("sharp-buffer-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputPngBuffer).resize(width, height).write(outputPng, function(err) {
|
||||
sharp(inputPngBuffer).resize(width, height).toFile(outputPng, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -274,7 +286,7 @@ async.series({
|
||||
}).add("sharp-file-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputPng).resize(width, height).write(outputPng, function(err) {
|
||||
sharp(inputPng).resize(width, height).toFile(outputPng, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -341,7 +353,7 @@ async.series({
|
||||
(new Benchmark.Suite("webp")).add("sharp-buffer-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputWebpBuffer).resize(width, height).write(outputWebp, function(err) {
|
||||
sharp(inputWebpBuffer).resize(width, height).toFile(outputWebp, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -364,7 +376,7 @@ async.series({
|
||||
}).add("sharp-file-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputWebp).resize(width, height).write(outputWebp, function(err) {
|
||||
sharp(inputWebp).resize(width, height).toFile(outputWebp, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -418,7 +430,7 @@ async.series({
|
||||
(new Benchmark.Suite("tiff")).add("sharp-file-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputTiff).resize(width, height).write(outputTiff, function(err) {
|
||||
sharp(inputTiff).resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -429,7 +441,7 @@ async.series({
|
||||
}).add("sharp-file-file-sharpen", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputTiff).resize(width, height).sharpen().write(outputTiff, function(err) {
|
||||
sharp(inputTiff).resize(width, height).sharpen().toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -440,7 +452,7 @@ async.series({
|
||||
}).add("sharp-file-file-sequentialRead", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputTiff).sequentialRead().resize(width, height).write(outputTiff, function(err) {
|
||||
sharp(inputTiff).sequentialRead().resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -458,7 +470,7 @@ async.series({
|
||||
(new Benchmark.Suite("gif")).add("sharp-file-file", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputGif).resize(width, height).write(outputTiff, function(err) {
|
||||
sharp(inputGif).resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -469,7 +481,7 @@ async.series({
|
||||
}).add("sharp-file-file-sharpen", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputGif).resize(width, height).sharpen().write(outputTiff, function(err) {
|
||||
sharp(inputGif).resize(width, height).sharpen().toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -480,7 +492,7 @@ async.series({
|
||||
}).add("sharp-file-file-sequentialRead", {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputGif).sequentialRead().resize(width, height).write(outputTiff, function(err) {
|
||||
sharp(inputGif).sequentialRead().resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
|
||||
132
tests/unit.js
132
tests/unit.js
@@ -1,3 +1,7 @@
|
||||
/*jslint node: true */
|
||||
/*jslint es5: true */
|
||||
'use strict';
|
||||
|
||||
var sharp = require("../index");
|
||||
var path = require("path");
|
||||
var imagemagick = require("imagemagick");
|
||||
@@ -12,10 +16,12 @@ 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");
|
||||
|
||||
var inputJpgWithExif = path.join(fixturesPath, "Landscape_8.jpg"); // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
||||
|
||||
async.series([
|
||||
// Resize with exact crop
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).write(outputJpg, function(err) {
|
||||
sharp(inputJpg).resize(320, 240).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -27,7 +33,7 @@ async.series([
|
||||
},
|
||||
// Resize to fixed width
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320).write(outputJpg, function(err) {
|
||||
sharp(inputJpg).resize(320).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -39,7 +45,7 @@ async.series([
|
||||
},
|
||||
// Resize to fixed height
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) {
|
||||
sharp(inputJpg).resize(null, 320).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -51,7 +57,7 @@ async.series([
|
||||
},
|
||||
// Identity transform
|
||||
function(done) {
|
||||
sharp(inputJpg).write(outputJpg, function(err) {
|
||||
sharp(inputJpg).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -63,7 +69,7 @@ async.series([
|
||||
},
|
||||
// Upscale
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(3000).write(outputJpg, function(err) {
|
||||
sharp(inputJpg).resize(3000).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -89,7 +95,7 @@ async.series([
|
||||
},
|
||||
// TIFF with dimensions known to cause rounding errors
|
||||
function(done) {
|
||||
sharp(inputTiff).resize(240, 320).embedBlack().write(outputJpg, function(err) {
|
||||
sharp(inputTiff).resize(240, 320).embedBlack().toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -100,7 +106,7 @@ async.series([
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputTiff).resize(240, 320).write(outputJpg, function(err) {
|
||||
sharp(inputTiff).resize(240, 320).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -112,7 +118,7 @@ async.series([
|
||||
},
|
||||
// Resize to max width or height considering ratio (landscape)
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 320).max().write(outputJpg, function(err) {
|
||||
sharp(inputJpg).resize(320, 320).max().toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -124,7 +130,7 @@ async.series([
|
||||
},
|
||||
// Resize to max width or height considering ratio (portrait)
|
||||
function(done) {
|
||||
sharp(inputTiff).resize(320, 320).max().write(outputJpg, function(err) {
|
||||
sharp(inputTiff).resize(320, 320).max().toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -136,7 +142,7 @@ async.series([
|
||||
},
|
||||
// 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) {
|
||||
sharp(inputJpg).resize(320).max().toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
@@ -148,9 +154,113 @@ async.series([
|
||||
},
|
||||
// Attempt to output to input, should fail
|
||||
function(done) {
|
||||
sharp(inputJpg).write(inputJpg, function(err) {
|
||||
sharp(inputJpg).toFile(inputJpg, function(err) {
|
||||
assert(!!err);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Rotate by 90 degrees, respecting output input size
|
||||
function(done) {
|
||||
sharp(inputJpg).rotate(90).resize(320, 240).toFile(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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Input image has Orientation EXIF tag but do not rotate output
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).resize(320).toFile(outputJpg, function(err) {
|
||||
if (err) throw err;
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, features.width);
|
||||
assert.strictEqual(426, features.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).rotate().resize(320).toFile(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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Attempt to auto-rotate using image that has no EXIF
|
||||
function(done) {
|
||||
sharp(inputJpg).rotate().resize(320).toFile(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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Rotate to an invalid angle, should fail
|
||||
function(done) {
|
||||
var fail = false;
|
||||
try {
|
||||
sharp(inputJpg).rotate(1);
|
||||
fail = true;
|
||||
} catch (e) {}
|
||||
assert(!fail);
|
||||
done();
|
||||
},
|
||||
// Do not enlarge the output if the input width is already less than the output width
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(2800).withoutEnlargement().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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Do not enlarge the output if the input height is already less than the output height
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(null, 2300).withoutEnlargement().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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Promises/A+
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).toFile(outputJpg).then(function() {
|
||||
imagemagick.identify(outputJpg, function(err, features) {
|
||||
assert.strictEqual(320, features.width);
|
||||
assert.strictEqual(240, features.height);
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
// Empty Buffer, should fail
|
||||
function(done) {
|
||||
var fail = false;
|
||||
try {
|
||||
sharp(new Buffer(0));
|
||||
fail = true;
|
||||
} catch (e) {}
|
||||
assert(!fail);
|
||||
done();
|
||||
}
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user