mirror of
https://github.com/lovell/sharp.git
synced 2026-02-04 13:46:19 +01:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca2f009f4 | ||
|
|
a900c28f7c | ||
|
|
77bbbb9715 | ||
|
|
88753a6333 | ||
|
|
bcd82f4893 | ||
|
|
749dc61f85 | ||
|
|
c7ccf6801d | ||
|
|
1565522ecc | ||
|
|
a44df2f533 | ||
|
|
317510746f | ||
|
|
ef54e327b7 | ||
|
|
d8d0158774 | ||
|
|
4d75f27a25 | ||
|
|
f89e9d726d | ||
|
|
5194b37460 | ||
|
|
55ea432711 | ||
|
|
ab7408c96f | ||
|
|
1f7e80e581 | ||
|
|
0e91ca90d6 | ||
|
|
8f41fed9c2 | ||
|
|
96dd40cee1 | ||
|
|
62767d072b | ||
|
|
33880ce19e | ||
|
|
988176846d | ||
|
|
657d436a0f | ||
|
|
e5549e3063 | ||
|
|
0b2fb967b8 | ||
|
|
f57478c1aa | ||
|
|
e5a5e2ca7e | ||
|
|
797d503a99 | ||
|
|
512a281986 | ||
|
|
37c5ca7166 | ||
|
|
cda700ef73 | ||
|
|
d32901da8d | ||
|
|
83ebe12061 | ||
|
|
fe34548bad | ||
|
|
855945bef2 | ||
|
|
8421e3aa5f | ||
|
|
c93f79daa7 | ||
|
|
35c53f78c8 | ||
|
|
c158d51f8b | ||
|
|
8e9a8dfede | ||
|
|
67dc694cfb | ||
|
|
74704a132c | ||
|
|
b86674f91f | ||
|
|
5dab3c8482 | ||
|
|
a190ae6b08 | ||
|
|
464fb1726d | ||
|
|
065ce6454b | ||
|
|
850c2ecdd6 | ||
|
|
926c5603aa | ||
|
|
d3225fa193 | ||
|
|
f026a835fd | ||
|
|
47241db789 | ||
|
|
34a9970bd9 | ||
|
|
57203f841a | ||
|
|
bd20bd1881 | ||
|
|
60f1fda7ee | ||
|
|
ea1013f6ec | ||
|
|
247b607afd | ||
|
|
a56102a209 | ||
|
|
940b6f505f | ||
|
|
e1b5574c4a |
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"strict": true,
|
||||
"node": true,
|
||||
"maxparams": 4,
|
||||
"maxcomplexity": 13,
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"it": true
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
- "0.12"
|
||||
- iojs
|
||||
before_install:
|
||||
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||
after_success:
|
||||
|
||||
84
CONTRIBUTING.md
Executable file
84
CONTRIBUTING.md
Executable file
@@ -0,0 +1,84 @@
|
||||
# Contributing to sharp
|
||||
|
||||
Hello, thank you for your interest in helping!
|
||||
|
||||
## Submit a new bug report
|
||||
|
||||
Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem.
|
||||
|
||||
New bugs are assigned a `triage` label whilst under investigation.
|
||||
|
||||
If you're having problems with `npm install sharp`, please include the output of the following commands, perhaps as a [gist](https://gist.github.com/):
|
||||
|
||||
```sh
|
||||
vips -v
|
||||
pkg-config --print-provides vips
|
||||
npm install --verbose sharp
|
||||
```
|
||||
|
||||
## Submit a new feature request
|
||||
|
||||
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists, it's probably fastest to add a comment to it about your requirement.
|
||||
|
||||
Implementation is usually straightforward if _libvips_ [already supports](http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/ch03.html) the feature you need.
|
||||
|
||||
## Submit a Pull Request to fix a bug
|
||||
|
||||
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
|
||||
|
||||
Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
|
||||
|
||||
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
|
||||
|
||||
## Submit a Pull Request with a new feature
|
||||
|
||||
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature. A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
|
||||
|
||||
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
|
||||
|
||||
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
||||
|
||||
| Release | WIP branch |
|
||||
| ------: | :--------- |
|
||||
| v0.9.0 | intake |
|
||||
| v0.10.0 | judgement |
|
||||
| v0.11.0 | knife |
|
||||
|
||||
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||
|
||||
### Add a new public method
|
||||
|
||||
The API tries to be as fluent as possible. Image processing concepts follow the naming conventions from _libvips_ and, to a lesser extent, _ImageMagick_.
|
||||
|
||||
Most methods have optional parameters and assume sensible defaults. Methods with mandatory parameters often have names like `doSomethingWith(X)`.
|
||||
|
||||
Please ensure backwards compatibility where possible. Methods to modify previously default behaviour often have names like `withoutOptionY()` or `withExtraZ()`.
|
||||
|
||||
Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change.
|
||||
|
||||
### Remove an existing public method
|
||||
|
||||
A method to be removed should be deprecated in the next major version then removed in the following major version.
|
||||
|
||||
By way of example, the [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0.
|
||||
|
||||
## Run the tests
|
||||
|
||||
### Functional tests and static code analysis
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
### Memory leak tests
|
||||
|
||||
Requires _valgrind_.
|
||||
|
||||
```sh
|
||||
cd sharp/test/leak
|
||||
./leak.sh
|
||||
```
|
||||
|
||||
## Finally
|
||||
|
||||
Please feel free to ask any questions via a [new issue](https://github.com/lovell/sharp/issues/new) or contact me by [e-mail](https://github.com/lovell/sharp/blob/master/package.json#L4).
|
||||
157
README.md
157
README.md
@@ -3,6 +3,7 @@
|
||||
* [Installation](https://github.com/lovell/sharp#installation)
|
||||
* [Usage examples](https://github.com/lovell/sharp#usage-examples)
|
||||
* [API](https://github.com/lovell/sharp#api)
|
||||
* [Contributing](https://github.com/lovell/sharp#contributing)
|
||||
* [Testing](https://github.com/lovell/sharp#testing)
|
||||
* [Performance](https://github.com/lovell/sharp#performance)
|
||||
* [Thanks](https://github.com/lovell/sharp#thanks)
|
||||
@@ -11,7 +12,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.
|
||||
|
||||
This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem.
|
||||
It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present.
|
||||
It also supports reading images of many other types from the filesystem via libmagick or libgraphicsmagick if present.
|
||||
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||
|
||||
Only small regions of uncompressed image data are held in memory and processed at a time, taking full advantage of multiple CPU cores and L1/L2/L3 cache. Resizing an image is typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings.
|
||||
@@ -30,8 +31,9 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* Node.js v0.10+
|
||||
* Node.js v0.10+ or io.js
|
||||
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
|
||||
* C++11 compatible compiler such as gcc 4.6+ or clang 3.0+
|
||||
|
||||
To install the most suitable version of libvips on the following Operating Systems:
|
||||
|
||||
@@ -45,6 +47,7 @@ To install the most suitable version of libvips on the following Operating Syste
|
||||
* Red Hat Linux
|
||||
* RHEL/Centos/Scientific 6, 7
|
||||
* Fedora 21, 22
|
||||
* Amazon Linux 2014.09
|
||||
|
||||
run the following as a user with `sudo` access:
|
||||
|
||||
@@ -80,9 +83,11 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
|
||||
|
||||
docker pull marcbachmann/libvips
|
||||
|
||||
### gulp.js
|
||||
### Build tools
|
||||
|
||||
[Eugeny Vlasenko](https://github.com/mahnunchik) maintains [gulp-responsive](https://www.npmjs.org/package/gulp-responsive) and [Mohammad Prabowo](https://github.com/rizalp) maintains [gulp-sharp](https://www.npmjs.org/package/gulp-sharp).
|
||||
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
|
||||
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
|
||||
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
|
||||
|
||||
## Usage examples
|
||||
|
||||
@@ -109,7 +114,7 @@ readableStream.pipe(transformer).pipe(writableStream);
|
||||
```javascript
|
||||
var image = sharp(inputJpg);
|
||||
image.metadata(function(err, metadata) {
|
||||
image.resize(metadata.width / 2).webp().toBuffer(function(err, outputBuffer, info) {
|
||||
image.resize(Math.floor(metadata.width / 2)).webp().toBuffer(function(err, outputBuffer, info) {
|
||||
// outputBuffer contains a WebP image half the width and height of the original JPEG
|
||||
});
|
||||
});
|
||||
@@ -183,7 +188,7 @@ sharp(inputBuffer)
|
||||
.toFile('output.tiff')
|
||||
.then(function() {
|
||||
// output.tiff is a 200 pixels wide and 300 pixels high image
|
||||
// containing a bicubic scaled version, embedded on a white canvas,
|
||||
// containing a nohalo scaled version, embedded on a white canvas,
|
||||
// of the image data in inputBuffer
|
||||
});
|
||||
```
|
||||
@@ -193,7 +198,7 @@ sharp('input.gif')
|
||||
.resize(200, 300)
|
||||
.background({r: 0, g: 0, b: 0, a: 0})
|
||||
.embed()
|
||||
.webp()
|
||||
.toFormat(sharp.format.webp)
|
||||
.toBuffer(function(err, outputBuffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
@@ -207,30 +212,66 @@ sharp('input.gif')
|
||||
sharp(inputBuffer)
|
||||
.resize(200, 200)
|
||||
.max()
|
||||
.jpeg()
|
||||
.toFormat('jpeg')
|
||||
.toBuffer().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
|
||||
});
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Runtime discovery of available formats
|
||||
console.dir(sharp.format);
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Attributes
|
||||
|
||||
#### format
|
||||
|
||||
An Object containing nested boolean values
|
||||
representing the available input and output formats/methods,
|
||||
for example:
|
||||
|
||||
```javascript
|
||||
{ jpeg: { id: 'jpeg',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: true, stream: true } },
|
||||
png: { id: 'png',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: true, stream: true } },
|
||||
webp: { id: 'webp',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: true, stream: true } },
|
||||
tiff: { id: 'tiff',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: true, buffer: false, stream: false } },
|
||||
magick: { id: 'magick',
|
||||
input: { file: true, buffer: true, stream: true },
|
||||
output: { file: false, buffer: false, stream: false } },
|
||||
raw: { id: 'raw',
|
||||
input: { file: false, buffer: false, stream: false },
|
||||
output: { file: false, buffer: true, stream: true } } }
|
||||
```
|
||||
|
||||
### Input methods
|
||||
|
||||
#### sharp([input])
|
||||
|
||||
Constructor to which further methods are chained. `input`, if present, can be one of:
|
||||
|
||||
* Buffer containing JPEG, PNG, WebP or TIFF image data, or
|
||||
* Buffer containing JPEG, PNG, WebP, GIF* or TIFF image data, or
|
||||
* String containing the filename of an image, with most major formats supported.
|
||||
|
||||
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||
|
||||
JPEG, PNG, WebP or TIFF format image data can be streamed into the object when `input` is not provided.
|
||||
JPEG, PNG, WebP, GIF* or TIFF format image data can be streamed into the object when `input` is not provided.
|
||||
|
||||
JPEG, PNG or WebP format image data can be streamed out from this object.
|
||||
|
||||
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
|
||||
|
||||
#### metadata([callback])
|
||||
|
||||
Fast access to image metadata without decoding any compressed image data.
|
||||
@@ -252,15 +293,21 @@ A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
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.
|
||||
|
||||
#### limitInputPixels(pixels)
|
||||
|
||||
Do not process input images where the number of pixels (width * height) exceeds this limit.
|
||||
|
||||
`pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF).
|
||||
|
||||
### Image transformation options
|
||||
|
||||
#### resize(width, [height])
|
||||
|
||||
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.
|
||||
`width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
|
||||
|
||||
`height` is the Number of pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
|
||||
#### extract(top, left, width, height)
|
||||
|
||||
@@ -282,7 +329,19 @@ Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The
|
||||
|
||||
#### max()
|
||||
|
||||
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified.
|
||||
Preserving aspect ratio,
|
||||
resize the image to be as large as possible
|
||||
while ensuring its dimensions are less than or equal to
|
||||
the `width` and `height` specified.
|
||||
|
||||
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
||||
|
||||
#### min()
|
||||
|
||||
Preserving aspect ratio,
|
||||
resize the image to be as small as possible
|
||||
while ensuring its dimensions are greater than or equal to
|
||||
the `width` and `height` specified.
|
||||
|
||||
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
||||
|
||||
@@ -314,6 +373,8 @@ Rotate the output image by either an explicit angle or auto-orient based on the
|
||||
|
||||
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
|
||||
|
||||
Method order is important when both rotating and extracting regions, for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
|
||||
|
||||
#### flip()
|
||||
|
||||
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
||||
@@ -391,6 +452,25 @@ Use PNG format for the output image.
|
||||
|
||||
Use WebP format for the output image.
|
||||
|
||||
#### raw()
|
||||
|
||||
_Requires libvips 7.42.0+_
|
||||
|
||||
Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output.
|
||||
|
||||
The number of channels depends on the input image and selected options.
|
||||
|
||||
* 1 channel for images converted to `greyscale()`, with each byte representing one pixel.
|
||||
* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]).
|
||||
* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\].
|
||||
|
||||
#### toFormat(format)
|
||||
|
||||
Convenience method for the above output format methods, where `format` is either:
|
||||
|
||||
* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or
|
||||
* a String containing `jpeg`, `png`, `webp` or `raw`.
|
||||
|
||||
#### quality(quality)
|
||||
|
||||
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
|
||||
@@ -403,10 +483,19 @@ Use progressive (interlace) scan for JPEG and PNG output. This typically reduces
|
||||
|
||||
#### withMetadata()
|
||||
|
||||
Include all metadata (ICC, EXIF, XMP) from the input image in the output image.
|
||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. This will also convert to and add the latest web-friendly v2 sRGB ICC profile.
|
||||
|
||||
The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||
|
||||
#### withoutChromaSubsampling()
|
||||
|
||||
Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4).
|
||||
|
||||
This can improve colour representation at higher quality settings (90+),
|
||||
but usually increases output file size and typically reduces performance by 25%.
|
||||
|
||||
The default behaviour is to use chroma subsampling (4:2:0).
|
||||
|
||||
#### compressionLevel(compressionLevel)
|
||||
|
||||
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
||||
@@ -428,7 +517,7 @@ An advanced setting to disable adaptive row filtering for the lossless PNG outpu
|
||||
`callback`, if present, is called with two arguments `(err, info)` where:
|
||||
|
||||
* `err` contains an error message, if any.
|
||||
* `info` contains the output image `format`, `width` and `height`.
|
||||
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
@@ -440,7 +529,7 @@ Write image data to a Buffer, the format of which will match the input image by
|
||||
|
||||
* `err` is an error message, if any.
|
||||
* `buffer` is the output image data.
|
||||
* `info` contains the output image `format`, `width` and `height`.
|
||||
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
@@ -463,7 +552,7 @@ sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
|
||||
|
||||
#### sharp.concurrency([threads])
|
||||
|
||||
`threads`, if provided, is the Number of threads _libvips'_ should create for image processing. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
||||
`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
||||
|
||||
This method always returns the current concurrency.
|
||||
|
||||
@@ -473,6 +562,8 @@ sharp.concurrency(2); // 2
|
||||
sharp.concurrency(0); // 4
|
||||
```
|
||||
|
||||
The maximum number of images that can be processed in parallel is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||
|
||||
#### sharp.counters()
|
||||
|
||||
Provides access to internal task counters.
|
||||
@@ -484,6 +575,10 @@ Provides access to internal task counters.
|
||||
var counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md) covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
## Testing
|
||||
|
||||
### Functional tests
|
||||
@@ -500,29 +595,6 @@ var counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||
|
||||
[](https://snap-ci.com/lovell/sharp/branch/master)
|
||||
|
||||
#### It worked on my machine
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
### Memory leak tests
|
||||
|
||||
```
|
||||
cd sharp/test/leak
|
||||
./leak.sh
|
||||
```
|
||||
|
||||
Requires _valgrind_:
|
||||
|
||||
```
|
||||
brew install valgrind
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get install -qq valgrind
|
||||
```
|
||||
|
||||
### Benchmark tests
|
||||
|
||||
```
|
||||
@@ -539,7 +611,7 @@ brew install graphicsmagick
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get install -qq imagemagick graphicsmagick libmagick++-dev
|
||||
sudo apt-get install -qq imagemagick graphicsmagick libmagickcore-dev
|
||||
```
|
||||
|
||||
```
|
||||
@@ -604,12 +676,13 @@ This module would never have been possible without the help and code contributio
|
||||
* [Amit Pitaru](https://github.com/apitaru)
|
||||
* [Brandon Aaron](https://github.com/brandonaaron)
|
||||
* [Andreas Lind](https://github.com/papandreou)
|
||||
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
|
||||
|
||||
Thank you!
|
||||
|
||||
## Licence
|
||||
|
||||
Copyright 2013, 2014 Lovell Fuller and contributors.
|
||||
Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
129
index.js
129
index.js
@@ -11,6 +11,12 @@ var BluebirdPromise = require('bluebird');
|
||||
var sharp = require('./build/Release/sharp');
|
||||
var libvipsVersion = sharp.libvipsVersion();
|
||||
|
||||
var maximum = {
|
||||
width: 0x3FFF,
|
||||
height: 0x3FFF,
|
||||
pixels: Math.pow(0x3FFF, 2)
|
||||
};
|
||||
|
||||
var Sharp = function(input) {
|
||||
if (!(this instanceof Sharp)) {
|
||||
return new Sharp(input);
|
||||
@@ -18,8 +24,10 @@ var Sharp = function(input) {
|
||||
stream.Duplex.call(this);
|
||||
this.options = {
|
||||
// input options
|
||||
bufferIn: null,
|
||||
streamIn: false,
|
||||
sequentialRead: false,
|
||||
limitInputPixels: maximum.pixels,
|
||||
// ICC profiles
|
||||
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
||||
// resize options
|
||||
@@ -33,9 +41,10 @@ var Sharp = function(input) {
|
||||
heightPost: -1,
|
||||
width: -1,
|
||||
height: -1,
|
||||
canvas: 'c',
|
||||
canvas: 'crop',
|
||||
gravity: 0,
|
||||
angle: 0,
|
||||
rotateBeforePreExtract: false,
|
||||
flip: false,
|
||||
flop: false,
|
||||
withoutEnlargement: false,
|
||||
@@ -55,6 +64,7 @@ var Sharp = function(input) {
|
||||
quality: 80,
|
||||
compressionLevel: 6,
|
||||
withoutAdaptiveFiltering: false,
|
||||
withoutChromaSubsampling: false,
|
||||
streamOut: false,
|
||||
withMetadata: false
|
||||
};
|
||||
@@ -73,11 +83,13 @@ var Sharp = function(input) {
|
||||
(input[0] === 0x52 && input[1] === 0x49) ||
|
||||
// TIFF
|
||||
(input[0] === 0x4D && input[1] === 0x4D && input[2] === 0x00 && (input[3] === 0x2A || input[3] === 0x2B)) ||
|
||||
(input[0] === 0x49 && input[1] === 0x49 && (input[2] === 0x2A || input[2] === 0x2B) && input[3] === 0x00)
|
||||
(input[0] === 0x49 && input[1] === 0x49 && (input[2] === 0x2A || input[2] === 0x2B) && input[3] === 0x00) ||
|
||||
// GIF
|
||||
(input[0] === 0x47 && input[1] === 0x49 && input[2] === 0x46 && input[3] === 0x38 && (input[4] === 0x37 || input[4] === 0x39) && input[5] === 0x61)
|
||||
) {
|
||||
this.options.bufferIn = input;
|
||||
} else {
|
||||
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP and TIFF are currently supported.');
|
||||
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP, TIFF and GIF are currently supported.');
|
||||
}
|
||||
} else {
|
||||
// input=stream
|
||||
@@ -88,6 +100,11 @@ var Sharp = function(input) {
|
||||
module.exports = Sharp;
|
||||
util.inherits(Sharp, stream.Duplex);
|
||||
|
||||
/*
|
||||
Supported image formats
|
||||
*/
|
||||
module.exports.format = sharp.format();
|
||||
|
||||
/*
|
||||
Handle incoming chunk on Writable Stream
|
||||
*/
|
||||
@@ -95,16 +112,16 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||
/*jslint unused: false */
|
||||
if (this.options.streamIn) {
|
||||
if (typeof chunk === 'object' && chunk instanceof Buffer) {
|
||||
if (typeof this.options.bufferIn === 'undefined') {
|
||||
// Create new Buffer
|
||||
this.options.bufferIn = new Buffer(chunk.length);
|
||||
chunk.copy(this.options.bufferIn);
|
||||
} else {
|
||||
if (this.options.bufferIn instanceof Buffer) {
|
||||
// Append to existing Buffer
|
||||
this.options.bufferIn = Buffer.concat(
|
||||
[this.options.bufferIn, chunk],
|
||||
this.options.bufferIn.length + chunk.length
|
||||
);
|
||||
} else {
|
||||
// Create new Buffer
|
||||
this.options.bufferIn = new Buffer(chunk.length);
|
||||
chunk.copy(this.options.bufferIn);
|
||||
}
|
||||
callback();
|
||||
} else {
|
||||
@@ -119,7 +136,7 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
|
||||
|
||||
Sharp.prototype.crop = function(gravity) {
|
||||
this.options.canvas = 'c';
|
||||
this.options.canvas = 'crop';
|
||||
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
||||
this.options.gravity = gravity;
|
||||
} else {
|
||||
@@ -133,8 +150,16 @@ Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
|
||||
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
||||
var values = arguments;
|
||||
['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) {
|
||||
this.options[name + suffix] = values[index];
|
||||
if (typeof values[index] === 'number' && !Number.isNaN(values[index]) && (values[index] % 1 === 0) && values[index] >= 0) {
|
||||
this.options[name + suffix] = values[index];
|
||||
} else {
|
||||
throw new Error('Non-integer value for ' + name + ' of ' + values[index]);
|
||||
}
|
||||
}.bind(this));
|
||||
// Ensure existing rotation occurs before pre-resize extraction
|
||||
if (suffix === 'Pre' && this.options.angle !== 0) {
|
||||
this.options.rotateBeforePreExtract = true;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -151,12 +176,17 @@ Sharp.prototype.background = function(rgba) {
|
||||
};
|
||||
|
||||
Sharp.prototype.embed = function() {
|
||||
this.options.canvas = 'e';
|
||||
this.options.canvas = 'embed';
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.max = function() {
|
||||
this.options.canvas = 'm';
|
||||
this.options.canvas = 'max';
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.min = function() {
|
||||
this.options.canvas = 'min';
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -340,10 +370,10 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||
};
|
||||
|
||||
/*
|
||||
Disable the use of adaptive row filtering for PNG output - requires libvips 7.41.0+
|
||||
Disable the use of adaptive row filtering for PNG output - requires libvips 7.42.0+
|
||||
*/
|
||||
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
|
||||
if (semver.gte(libvipsVersion, '7.41.0')) {
|
||||
if (semver.gte(libvipsVersion, '7.42.0')) {
|
||||
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
|
||||
} else {
|
||||
console.error('withoutAdaptiveFiltering requires libvips 7.41.0+');
|
||||
@@ -351,6 +381,14 @@ Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Disable the use of chroma subsampling for JPEG output
|
||||
*/
|
||||
Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
|
||||
this.options.withoutChromaSubsampling = (typeof withoutChromaSubsampling === 'boolean') ? withoutChromaSubsampling : true;
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.withMetadata = function(withMetadata) {
|
||||
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
||||
return this;
|
||||
@@ -360,24 +398,37 @@ Sharp.prototype.resize = function(width, height) {
|
||||
if (!width) {
|
||||
this.options.width = -1;
|
||||
} else {
|
||||
if (typeof width === 'number' && !Number.isNaN(width)) {
|
||||
if (typeof width === 'number' && !Number.isNaN(width) && width % 1 === 0 && width > 0 && width <= maximum.width) {
|
||||
this.options.width = width;
|
||||
} else {
|
||||
throw new Error('Invalid width ' + width);
|
||||
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
|
||||
}
|
||||
}
|
||||
if (!height) {
|
||||
this.options.height = -1;
|
||||
} else {
|
||||
if (typeof height === 'number' && !Number.isNaN(height)) {
|
||||
if (typeof height === 'number' && !Number.isNaN(height) && height % 1 === 0 && height > 0 && height <= maximum.height) {
|
||||
this.options.height = height;
|
||||
} else {
|
||||
throw new Error('Invalid height ' + height);
|
||||
throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Limit the total number of pixels for input images
|
||||
Assumes the image dimensions contained in the file header can be trusted
|
||||
*/
|
||||
Sharp.prototype.limitInputPixels = function(limit) {
|
||||
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit > 0 && limit <= maximum.pixels) {
|
||||
this.options.limitInputPixels = limit;
|
||||
} else {
|
||||
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Write output image data to a file
|
||||
*/
|
||||
@@ -405,25 +456,67 @@ Sharp.prototype.toFile = function(output, callback) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Write output to a Buffer
|
||||
*/
|
||||
Sharp.prototype.toBuffer = function(callback) {
|
||||
return this._sharp(callback);
|
||||
};
|
||||
|
||||
/*
|
||||
Force JPEG output
|
||||
*/
|
||||
Sharp.prototype.jpeg = function() {
|
||||
this.options.output = '__jpeg';
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Force PNG output
|
||||
*/
|
||||
Sharp.prototype.png = function() {
|
||||
this.options.output = '__png';
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Force WebP output
|
||||
*/
|
||||
Sharp.prototype.webp = function() {
|
||||
this.options.output = '__webp';
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Force raw, uint8 output
|
||||
*/
|
||||
Sharp.prototype.raw = function() {
|
||||
var supportsRawOutput = module.exports.format.raw.output;
|
||||
if (supportsRawOutput.file || supportsRawOutput.buffer || supportsRawOutput.stream) {
|
||||
this.options.output = '__raw';
|
||||
} else {
|
||||
console.error('Raw output requires libvips 7.42.0+');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Force output to a given format
|
||||
@param format is either the id as a String or an Object with an 'id' attribute
|
||||
*/
|
||||
Sharp.prototype.toFormat = function(format) {
|
||||
var id = format;
|
||||
if (typeof format === 'object') {
|
||||
id = format.id;
|
||||
}
|
||||
if (typeof id === 'string' && typeof module.exports.format[id] === 'object' && typeof this[id] === 'function') {
|
||||
this[id]();
|
||||
} else {
|
||||
throw new Error('Unsupported format ' + format);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Used by a Writable Stream to notify that it is ready for data
|
||||
*/
|
||||
|
||||
23
package.json
Executable file → Normal file
23
package.json
Executable file → Normal file
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"version": "0.8.1",
|
||||
"version": "0.9.3",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"contributors": [
|
||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||
@@ -11,11 +11,13 @@
|
||||
"Julian Walker <julian@fiftythree.com>",
|
||||
"Amit Pitaru <pitaru.amit@gmail.com>",
|
||||
"Brandon Aaron <hello.brandon@aaron.sh>",
|
||||
"Andreas Lind <andreas@one.com>"
|
||||
"Andreas Lind <andreas@one.com>",
|
||||
"Maurus Cuelenaere <mcuelenaere@gmail.com>",
|
||||
"Linus Unnebäck <linus@folkdatorn.se>"
|
||||
],
|
||||
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
|
||||
"scripts": {
|
||||
"test": "node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=10000 ./test/unit/*.js"
|
||||
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=10000 ./test/unit/*.js"
|
||||
},
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -34,16 +36,17 @@
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"bluebird": "^2.3.11",
|
||||
"color": "^0.7.1",
|
||||
"nan": "^1.4.1",
|
||||
"semver": "^4.1.0"
|
||||
"bluebird": "^2.9.12",
|
||||
"color": "^0.7.3",
|
||||
"nan": "^1.6.2",
|
||||
"semver": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.0.1",
|
||||
"mocha": "^2.1.0",
|
||||
"mocha-jshint": "^0.0.9",
|
||||
"istanbul": "^0.3.2",
|
||||
"coveralls": "^2.11.2"
|
||||
"istanbul": "^0.3.6",
|
||||
"coveralls": "^2.11.2",
|
||||
"node-cpplint": "^0.4.0"
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
"engines": {
|
||||
|
||||
@@ -10,24 +10,25 @@
|
||||
# * Red Hat Linux
|
||||
# * RHEL/Centos/Scientific 6, 7
|
||||
# * Fedora 21, 22
|
||||
# * Amazon Linux 2014.09
|
||||
|
||||
vips_version_minimum=7.40.0
|
||||
vips_version_latest_major=7.42
|
||||
vips_version_latest_minor=0
|
||||
vips_version_latest_major_minor=7.42
|
||||
vips_version_latest_patch=3
|
||||
|
||||
install_libvips_from_source() {
|
||||
echo "Compiling libvips $vips_version_latest_major.$vips_version_latest_minor from source"
|
||||
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major/vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
||||
tar zvxf vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
||||
cd vips-$vips_version_latest_major.$vips_version_latest_minor
|
||||
./configure --enable-debug=no --enable-docs=no --enable-cxx=yes --without-python --without-orc --without-fftw --without-gsf $1
|
||||
echo "Compiling libvips $vips_version_latest_major_minor.$vips_version_latest_patch from source"
|
||||
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
|
||||
./configure --enable-debug=no --enable-docs=no --enable-cxx=yes --without-python --without-orc --without-fftw $1
|
||||
make
|
||||
make install
|
||||
cd ..
|
||||
rm -rf vips-$vips_version_latest_major.$vips_version_latest_minor
|
||||
rm vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
||||
rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch
|
||||
rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||
ldconfig
|
||||
echo "Installed libvips $vips_version_latest_major.$vips_version_latest_minor"
|
||||
echo "Installed libvips $vips_version_latest_major_minor.$vips_version_latest_patch"
|
||||
}
|
||||
|
||||
sorry() {
|
||||
@@ -89,12 +90,12 @@ case $(uname -s) in
|
||||
jessie|vivid)
|
||||
# Debian 8, Ubuntu 15
|
||||
echo "Installing libvips via apt-get"
|
||||
apt-get install -y libvips-dev
|
||||
apt-get install -y libvips-dev libgsf-1-dev
|
||||
;;
|
||||
trusty|utopic|qiana|rebecca)
|
||||
# Ubuntu 14, Mint 17
|
||||
echo "Installing libvips dependencies via apt-get"
|
||||
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickwand-dev curl
|
||||
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
|
||||
install_libvips_from_source
|
||||
;;
|
||||
precise|wheezy|maya)
|
||||
@@ -102,7 +103,7 @@ case $(uname -s) in
|
||||
echo "Installing libvips dependencies via apt-get"
|
||||
add-apt-repository -y ppa:lyrasis/precise-backports
|
||||
apt-get update
|
||||
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickwand-dev curl
|
||||
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
|
||||
install_libvips_from_source
|
||||
;;
|
||||
*)
|
||||
@@ -119,14 +120,14 @@ case $(uname -s) in
|
||||
# RHEL/CentOS 7
|
||||
echo "Installing libvips dependencies via yum"
|
||||
yum groupinstall -y "Development Tools"
|
||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||
install_libvips_from_source "--prefix=/usr"
|
||||
;;
|
||||
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
|
||||
# RHEL/CentOS 6
|
||||
echo "Installing libvips dependencies via yum"
|
||||
yum groupinstall -y "Development Tools"
|
||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel curl
|
||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel curl
|
||||
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
|
||||
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
|
||||
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
|
||||
@@ -136,13 +137,26 @@ case $(uname -s) in
|
||||
"Fedora release 21 "*|"Fedora release 22 "*)
|
||||
# Fedora 21, 22
|
||||
echo "Installing libvips via yum"
|
||||
yum install vips-devel
|
||||
yum install -y vips-devel
|
||||
;;
|
||||
*)
|
||||
# Unsupported RHEL-based OS
|
||||
sorry "$RELEASE"
|
||||
;;
|
||||
esac
|
||||
elif [ -f /etc/system-release ]; then
|
||||
# Probably Amazon Linux
|
||||
RELEASE=$(cat /etc/system-release)
|
||||
case $RELEASE in
|
||||
"Amazon Linux AMI release 2014.09")
|
||||
# Amazon Linux
|
||||
echo "Detected '$RELEASE'"
|
||||
echo "Installing libvips dependencies via yum"
|
||||
yum groupinstall -y "Development Tools"
|
||||
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||
install_libvips_from_source "--prefix=/usr"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Unsupported OS
|
||||
sorry "$(uname -a)"
|
||||
|
||||
@@ -43,6 +43,15 @@ namespace sharp {
|
||||
);
|
||||
}
|
||||
|
||||
static bool buffer_is_gif(char *buffer, size_t len) {
|
||||
return (
|
||||
len >= 6 && (
|
||||
(buffer[0] == 'G' && buffer[1] == 'I' && buffer[2] == 'F' &&
|
||||
buffer[3] == '8' && (buffer[4] == '7' || buffer[4] == '9') && buffer[5] == 'a')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Determine image format of a buffer.
|
||||
*/
|
||||
@@ -57,6 +66,8 @@ namespace sharp {
|
||||
imageType = ImageType::WEBP;
|
||||
} else if (buffer_is_tiff(static_cast<char*>(buffer), length)) {
|
||||
imageType = ImageType::TIFF;
|
||||
} else if (buffer_is_gif(static_cast<char*>(buffer), length)) {
|
||||
imageType = ImageType::MAGICK;
|
||||
}
|
||||
}
|
||||
return imageType;
|
||||
@@ -75,6 +86,10 @@ namespace sharp {
|
||||
vips_webpload_buffer(buffer, length, &image, "access", access, NULL);
|
||||
} else if (imageType == ImageType::TIFF) {
|
||||
vips_tiffload_buffer(buffer, length, &image, "access", access, NULL);
|
||||
#if (VIPS_MAJOR_VERSION >= 8)
|
||||
} else if (imageType == ImageType::MAGICK) {
|
||||
vips_magickload_buffer(buffer, length, &image, "access", access, NULL);
|
||||
#endif
|
||||
}
|
||||
return image;
|
||||
}
|
||||
@@ -162,4 +177,4 @@ namespace sharp {
|
||||
return window_size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace sharp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SHARP_COMMON_H
|
||||
#define SHARP_COMMON_H
|
||||
#ifndef SRC_COMMON_H_
|
||||
#define SRC_COMMON_H_
|
||||
|
||||
namespace sharp {
|
||||
|
||||
@@ -66,6 +66,6 @@ namespace sharp {
|
||||
*/
|
||||
int InterpolatorWindowSize(char const *name);
|
||||
|
||||
} // namespace
|
||||
} // namespace sharp
|
||||
|
||||
#endif
|
||||
#endif // SRC_COMMON_H_
|
||||
|
||||
@@ -6,8 +6,23 @@
|
||||
#include "common.h"
|
||||
#include "metadata.h"
|
||||
|
||||
using namespace v8;
|
||||
using namespace sharp;
|
||||
using v8::Handle;
|
||||
using v8::Local;
|
||||
using v8::Value;
|
||||
using v8::Object;
|
||||
using v8::Number;
|
||||
using v8::String;
|
||||
using v8::Boolean;
|
||||
using v8::Function;
|
||||
using v8::Exception;
|
||||
|
||||
using sharp::ImageType;
|
||||
using sharp::DetermineImageType;
|
||||
using sharp::InitImage;
|
||||
using sharp::HasProfile;
|
||||
using sharp::HasAlpha;
|
||||
using sharp::ExifOrientation;
|
||||
using sharp::counterQueue;
|
||||
|
||||
struct MetadataBaton {
|
||||
// Input
|
||||
@@ -47,6 +62,10 @@ class MetadataWorker : public NanAsyncWorker {
|
||||
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
image = InitImage(imageType, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
|
||||
if (image == NULL) {
|
||||
(baton->err).append("Input buffer has corrupt header");
|
||||
imageType = ImageType::UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Input buffer contains unsupported image format");
|
||||
}
|
||||
@@ -55,8 +74,12 @@ class MetadataWorker : public NanAsyncWorker {
|
||||
imageType = DetermineImageType(baton->fileIn.c_str());
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
image = InitImage(imageType, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
|
||||
if (image == NULL) {
|
||||
(baton->err).append("Input file has corrupt header");
|
||||
imageType = ImageType::UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("File is of an unsupported image format");
|
||||
(baton->err).append("Input file is of an unsupported image format");
|
||||
}
|
||||
}
|
||||
if (image != NULL && imageType != ImageType::UNKNOWN) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef SHARP_METADATA_H
|
||||
#define SHARP_METADATA_H
|
||||
#ifndef SRC_METADATA_H_
|
||||
#define SRC_METADATA_H_
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(metadata);
|
||||
|
||||
#endif
|
||||
#endif // SRC_METADATA_H_
|
||||
|
||||
273
src/resize.cc
273
src/resize.cc
@@ -10,31 +10,55 @@
|
||||
#include "common.h"
|
||||
#include "resize.h"
|
||||
|
||||
using namespace v8;
|
||||
using namespace sharp;
|
||||
using v8::Handle;
|
||||
using v8::Local;
|
||||
using v8::Value;
|
||||
using v8::Object;
|
||||
using v8::Integer;
|
||||
using v8::Uint32;
|
||||
using v8::String;
|
||||
using v8::Array;
|
||||
using v8::Function;
|
||||
using v8::Exception;
|
||||
|
||||
using sharp::ImageType;
|
||||
using sharp::DetermineImageType;
|
||||
using sharp::InitImage;
|
||||
using sharp::InterpolatorWindowSize;
|
||||
using sharp::HasProfile;
|
||||
using sharp::HasAlpha;
|
||||
using sharp::ExifOrientation;
|
||||
using sharp::IsJpeg;
|
||||
using sharp::IsPng;
|
||||
using sharp::IsWebp;
|
||||
using sharp::IsTiff;
|
||||
using sharp::counterProcess;
|
||||
using sharp::counterQueue;
|
||||
|
||||
enum class Canvas {
|
||||
CROP,
|
||||
EMBED,
|
||||
MAX,
|
||||
EMBED
|
||||
MIN
|
||||
};
|
||||
|
||||
enum class Angle {
|
||||
D0,
|
||||
D90,
|
||||
D180,
|
||||
D270,
|
||||
DLAST
|
||||
D0,
|
||||
D90,
|
||||
D180,
|
||||
D270,
|
||||
DLAST
|
||||
};
|
||||
|
||||
struct ResizeBaton {
|
||||
std::string fileIn;
|
||||
void* bufferIn;
|
||||
char *bufferIn;
|
||||
size_t bufferInLength;
|
||||
std::string iccProfilePath;
|
||||
int limitInputPixels;
|
||||
std::string output;
|
||||
std::string outputFormat;
|
||||
void* bufferOut;
|
||||
void *bufferOut;
|
||||
size_t bufferOutLength;
|
||||
int topOffsetPre;
|
||||
int leftOffsetPre;
|
||||
@@ -58,6 +82,7 @@ struct ResizeBaton {
|
||||
double gamma;
|
||||
bool greyscale;
|
||||
int angle;
|
||||
bool rotateBeforePreExtract;
|
||||
bool flip;
|
||||
bool flop;
|
||||
bool progressive;
|
||||
@@ -66,11 +91,13 @@ struct ResizeBaton {
|
||||
int quality;
|
||||
int compressionLevel;
|
||||
bool withoutAdaptiveFiltering;
|
||||
bool withoutChromaSubsampling;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
|
||||
ResizeBaton():
|
||||
bufferInLength(0),
|
||||
limitInputPixels(0),
|
||||
outputFormat(""),
|
||||
bufferOutLength(0),
|
||||
topOffsetPre(-1),
|
||||
@@ -92,6 +119,7 @@ struct ResizeBaton {
|
||||
quality(80),
|
||||
compressionLevel(6),
|
||||
withoutAdaptiveFiltering(false),
|
||||
withoutChromaSubsampling(false),
|
||||
withMetadata(false) {
|
||||
background[0] = 0.0;
|
||||
background[1] = 0.0;
|
||||
@@ -100,6 +128,16 @@ struct ResizeBaton {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Delete input char[] buffer and notify V8 of memory deallocation
|
||||
Used as the callback function for the "postclose" signal
|
||||
*/
|
||||
static void DeleteBuffer(VipsObject *object, char *buffer) {
|
||||
if (buffer != NULL) {
|
||||
delete[] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
class ResizeWorker : public NanAsyncWorker {
|
||||
|
||||
public:
|
||||
@@ -110,39 +148,82 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
libuv worker
|
||||
*/
|
||||
void Execute() {
|
||||
|
||||
// Decrement queued task counter
|
||||
g_atomic_int_dec_and_test(&counterQueue);
|
||||
// Increment processing task counter
|
||||
g_atomic_int_inc(&counterProcess);
|
||||
|
||||
// Latest v2 sRGB ICC profile
|
||||
std::string srgbProfile = baton->iccProfilePath + "sRGB_IEC61966-2-1_black_scaled.icc";
|
||||
|
||||
// Hang image references from this hook object
|
||||
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
|
||||
|
||||
// Input
|
||||
ImageType inputImageType = ImageType::UNKNOWN;
|
||||
VipsImage *image;
|
||||
VipsImage *image = NULL;
|
||||
if (baton->bufferInLength > 1) {
|
||||
// From buffer
|
||||
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||
if (inputImageType != ImageType::UNKNOWN) {
|
||||
image = InitImage(inputImageType, baton->bufferIn, baton->bufferInLength, baton->accessMethod);
|
||||
if (image != NULL) {
|
||||
// Listen for "postclose" signal to delete input buffer
|
||||
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
|
||||
} else {
|
||||
// Could not read header data
|
||||
(baton->err).append("Input buffer has corrupt header");
|
||||
inputImageType = ImageType::UNKNOWN;
|
||||
DeleteBuffer(NULL, baton->bufferIn);
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("Input buffer contains unsupported image format");
|
||||
DeleteBuffer(NULL, baton->bufferIn);
|
||||
}
|
||||
} else {
|
||||
// From file
|
||||
inputImageType = DetermineImageType(baton->fileIn.c_str());
|
||||
if (inputImageType != ImageType::UNKNOWN) {
|
||||
image = InitImage(inputImageType, baton->fileIn.c_str(), baton->accessMethod);
|
||||
if (image == NULL) {
|
||||
(baton->err).append("Input file has corrupt header");
|
||||
inputImageType = ImageType::UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
(baton->err).append("File is of an unsupported image format");
|
||||
(baton->err).append("Input file is of an unsupported image format");
|
||||
}
|
||||
}
|
||||
if (inputImageType == ImageType::UNKNOWN) {
|
||||
if (image == NULL || inputImageType == ImageType::UNKNOWN) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, image);
|
||||
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
if (image->Xsize * image->Ysize > baton->limitInputPixels) {
|
||||
(baton->err).append("Input image exceeds pixel limit");
|
||||
return Error(baton, hook);
|
||||
}
|
||||
|
||||
// Calculate angle of rotation
|
||||
Angle rotation;
|
||||
bool flip;
|
||||
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
|
||||
if (flip && !baton->flip) {
|
||||
// Add flip operation due to EXIF mirroring
|
||||
baton->flip = TRUE;
|
||||
}
|
||||
|
||||
// Rotate pre-extract
|
||||
if (baton->rotateBeforePreExtract && rotation != Angle::D0) {
|
||||
VipsImage *rotated;
|
||||
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, rotated);
|
||||
image = rotated;
|
||||
}
|
||||
|
||||
// Pre extraction
|
||||
if (baton->topOffsetPre != -1) {
|
||||
VipsImage *extractedPre;
|
||||
@@ -153,42 +234,48 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
image = extractedPre;
|
||||
}
|
||||
|
||||
// Get input image width and height
|
||||
// Get pre-resize image width and height
|
||||
int inputWidth = image->Xsize;
|
||||
int inputHeight = image->Ysize;
|
||||
|
||||
// Calculate angle of rotation, to be carried out later
|
||||
Angle rotation;
|
||||
bool flip;
|
||||
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
|
||||
if (rotation == Angle::D90 || rotation == Angle::D270) {
|
||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
||||
int swap = inputWidth;
|
||||
inputWidth = inputHeight;
|
||||
inputHeight = swap;
|
||||
}
|
||||
if (flip && !baton->flip) {
|
||||
// Add flip operation due to EXIF mirroring
|
||||
baton->flip = TRUE;
|
||||
}
|
||||
|
||||
// Get window size of interpolator, used for determining shrink vs affine
|
||||
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.c_str());
|
||||
|
||||
// Scaling calculations
|
||||
double factor;
|
||||
double factor = 1.0;
|
||||
if (baton->width > 0 && baton->height > 0) {
|
||||
// Fixed width and 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->canvas == Canvas::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->canvas == Canvas::MAX) {
|
||||
if (xfactor > yfactor) {
|
||||
baton->height = round(static_cast<double>(inputHeight) / xfactor);
|
||||
} else {
|
||||
baton->width = round(static_cast<double>(inputWidth) / yfactor);
|
||||
}
|
||||
switch (baton->canvas) {
|
||||
case Canvas::CROP:
|
||||
factor = std::min(xfactor, yfactor);
|
||||
break;
|
||||
case Canvas::EMBED:
|
||||
factor = std::max(xfactor, yfactor);
|
||||
break;
|
||||
case Canvas::MAX:
|
||||
factor = std::max(xfactor, yfactor);
|
||||
if (xfactor > yfactor) {
|
||||
baton->height = round(static_cast<double>(inputHeight) / xfactor);
|
||||
} else {
|
||||
baton->width = round(static_cast<double>(inputWidth) / yfactor);
|
||||
}
|
||||
break;
|
||||
case Canvas::MIN:
|
||||
factor = std::min(xfactor, yfactor);
|
||||
if (xfactor < yfactor) {
|
||||
baton->height = round(static_cast<double>(inputHeight) / xfactor);
|
||||
} else {
|
||||
baton->width = round(static_cast<double>(inputWidth) / yfactor);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (baton->width > 0) {
|
||||
// Fixed width, auto height
|
||||
@@ -200,7 +287,6 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
baton->width = floor(static_cast<double>(inputWidth) / factor);
|
||||
} else {
|
||||
// Identity transform
|
||||
factor = 1;
|
||||
baton->width = inputWidth;
|
||||
baton->height = inputHeight;
|
||||
}
|
||||
@@ -272,16 +358,14 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
// Ensure we're using a device-independent colour space
|
||||
if (HasProfile(image)) {
|
||||
// Convert to sRGB using embedded profile
|
||||
std::string srgbProfile = baton->iccProfilePath + "sRGB_IEC61966-2-1_black_scaled.icc";
|
||||
VipsImage *transformed;
|
||||
if (vips_icc_transform(image, &transformed, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
||||
return Error(baton, hook);
|
||||
if (!vips_icc_transform(image, &transformed, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
||||
// Embedded profile can fail, so only update references on success
|
||||
vips_object_local(hook, transformed);
|
||||
image = transformed;
|
||||
}
|
||||
vips_object_local(hook, transformed);
|
||||
image = transformed;
|
||||
} else if (image->Type == VIPS_INTERPRETATION_CMYK) {
|
||||
// Convert to sRGB using default "USWebCoatedSWOP" CMYK profile
|
||||
std::string srgbProfile = baton->iccProfilePath + "sRGB_IEC61966-2-1_black_scaled.icc";
|
||||
std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc";
|
||||
VipsImage *transformed;
|
||||
if (vips_icc_transform(image, &transformed, srgbProfile.c_str(), "input_profile", cmykProfile.c_str(), NULL)) {
|
||||
@@ -304,7 +388,7 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
if (vips_flatten(image, &flattened, "background", background, NULL)) {
|
||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
||||
return Error(baton, hook);
|
||||
};
|
||||
}
|
||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
||||
vips_object_local(hook, flattened);
|
||||
image = flattened;
|
||||
@@ -391,7 +475,7 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
|
||||
// Rotate
|
||||
if (rotation != Angle::D0) {
|
||||
if (!baton->rotateBeforePreExtract && rotation != Angle::D0) {
|
||||
VipsImage *rotated;
|
||||
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
|
||||
return Error(baton, hook);
|
||||
@@ -480,7 +564,7 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
vips_object_local(hook, embedded);
|
||||
image = embedded;
|
||||
} else {
|
||||
// Crop/max
|
||||
// Crop/max/min
|
||||
int left;
|
||||
int top;
|
||||
std::tie(left, top) = CalculateCrop(image->Xsize, image->Ysize, baton->width, baton->height, baton->gravity);
|
||||
@@ -498,7 +582,9 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
// Post extraction
|
||||
if (baton->topOffsetPost != -1) {
|
||||
VipsImage *extractedPost;
|
||||
if (vips_extract_area(image, &extractedPost, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL)) {
|
||||
if (vips_extract_area(image, &extractedPost,
|
||||
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL
|
||||
)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, extractedPost);
|
||||
@@ -528,7 +614,6 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
vips_object_local(hook, gaussian);
|
||||
// Apply Gaussian function
|
||||
VipsImage *blurred;
|
||||
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
@@ -571,25 +656,27 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
image = gammaDecoded;
|
||||
}
|
||||
|
||||
// Convert colour space to either sRGB or RGB-with-profile, if not already
|
||||
// Convert image to sRGB, if not already
|
||||
if (image->Type != VIPS_INTERPRETATION_sRGB) {
|
||||
// Switch intrepretation to sRGB
|
||||
VipsImage *rgb;
|
||||
if (baton->withMetadata && HasProfile(image)) {
|
||||
// Convert to device-dependent RGB using embedded profile of input
|
||||
if (vips_icc_export(image, &rgb, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
} else {
|
||||
// Convert to device-independent sRGB
|
||||
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, rgb);
|
||||
image = rgb;
|
||||
// Tranform colours from embedded profile to sRGB profile
|
||||
if (baton->withMetadata && HasProfile(image)) {
|
||||
VipsImage *profiled;
|
||||
if (vips_icc_transform(image, &profiled, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, profiled);
|
||||
image = profiled;
|
||||
}
|
||||
}
|
||||
|
||||
#if !(VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5)
|
||||
#if !(VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5))
|
||||
// Generate image tile cache when interlace output is required - no longer required as of libvips 7.40.5+
|
||||
if (baton->progressive) {
|
||||
VipsImage *cached;
|
||||
@@ -605,12 +692,13 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
|
||||
// Write JPEG to buffer
|
||||
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
|
||||
"interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "jpeg";
|
||||
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) {
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42)
|
||||
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
|
||||
// Select PNG row filter
|
||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
// Write PNG to buffer
|
||||
@@ -633,6 +721,35 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "webp";
|
||||
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
|
||||
} else if (baton->output == "__raw") {
|
||||
// Write raw, uncompressed image data to buffer
|
||||
if (baton->greyscale) {
|
||||
// Extract first band for greyscale image
|
||||
VipsImage *grey;
|
||||
if (vips_extract_band(image, &grey, 1, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, grey);
|
||||
image = grey;
|
||||
}
|
||||
if (image->BandFmt != VIPS_FORMAT_UCHAR) {
|
||||
// Cast pixels to uint8 (unsigned char)
|
||||
VipsImage *uchar;
|
||||
if (vips_cast(image, &uchar, VIPS_FORMAT_UCHAR, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, uchar);
|
||||
image = uchar;
|
||||
}
|
||||
// Get raw image data
|
||||
baton->bufferOut = vips_image_write_to_memory(image, &baton->bufferOutLength);
|
||||
if (baton->bufferOut == NULL) {
|
||||
(baton->err).append("Could not allocate enough memory for raw output");
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "raw";
|
||||
#endif
|
||||
} else {
|
||||
bool outputJpeg = IsJpeg(baton->output);
|
||||
bool outputPng = IsPng(baton->output);
|
||||
@@ -642,12 +759,13 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
||||
// Write JPEG to file
|
||||
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
|
||||
"interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "jpeg";
|
||||
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) {
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
|
||||
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
|
||||
// Select PNG row filter
|
||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
// Write PNG to file
|
||||
@@ -710,16 +828,22 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
// Info Object
|
||||
Local<Object> info = NanNew<Object>();
|
||||
info->Set(NanNew<String>("format"), NanNew<String>(baton->outputFormat));
|
||||
info->Set(NanNew<String>("width"), NanNew<Number>(width));
|
||||
info->Set(NanNew<String>("height"), NanNew<Number>(height));
|
||||
info->Set(NanNew<String>("width"), NanNew<Integer>(width));
|
||||
info->Set(NanNew<String>("height"), NanNew<Integer>(height));
|
||||
|
||||
if (baton->bufferOutLength > 0) {
|
||||
// Buffer
|
||||
// Copy data to new Buffer
|
||||
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
||||
// bufferOut was allocated via g_malloc
|
||||
g_free(baton->bufferOut);
|
||||
// Add buffer size to info
|
||||
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
||||
argv[2] = info;
|
||||
} else {
|
||||
// File
|
||||
// Add file size to info
|
||||
struct stat st;
|
||||
g_stat(baton->output.c_str(), &st);
|
||||
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(st.st_size)));
|
||||
argv[1] = info;
|
||||
}
|
||||
}
|
||||
@@ -830,11 +954,16 @@ NAN_METHOD(resize) {
|
||||
// Input Buffer object
|
||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
||||
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
|
||||
baton->bufferInLength = node::Buffer::Length(buffer);
|
||||
baton->bufferIn = node::Buffer::Data(buffer);
|
||||
baton->bufferIn = new char[baton->bufferInLength];
|
||||
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
|
||||
options->Set(NanNew<String>("bufferIn"), NanNull());
|
||||
}
|
||||
// ICC profile to use when input CMYK image has no embedded profile
|
||||
baton->iccProfilePath = *String::Utf8Value(options->Get(NanNew<String>("iccProfilePath"))->ToString());
|
||||
// Limit input images to a given number of pixels, where pixels = width * height
|
||||
baton->limitInputPixels = options->Get(NanNew<String>("limitInputPixels"))->Int32Value();
|
||||
// Extract image options
|
||||
baton->topOffsetPre = options->Get(NanNew<String>("topOffsetPre"))->Int32Value();
|
||||
baton->leftOffsetPre = options->Get(NanNew<String>("leftOffsetPre"))->Int32Value();
|
||||
@@ -849,12 +978,14 @@ NAN_METHOD(resize) {
|
||||
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
|
||||
// Canvas option
|
||||
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
|
||||
if (canvas->Equals(NanNew<String>("c"))) {
|
||||
if (canvas->Equals(NanNew<String>("crop"))) {
|
||||
baton->canvas = Canvas::CROP;
|
||||
} else if (canvas->Equals(NanNew<String>("m"))) {
|
||||
baton->canvas = Canvas::MAX;
|
||||
} else if (canvas->Equals(NanNew<String>("e"))) {
|
||||
} else if (canvas->Equals(NanNew<String>("embed"))) {
|
||||
baton->canvas = Canvas::EMBED;
|
||||
} else if (canvas->Equals(NanNew<String>("max"))) {
|
||||
baton->canvas = Canvas::MAX;
|
||||
} else if (canvas->Equals(NanNew<String>("min"))) {
|
||||
baton->canvas = Canvas::MIN;
|
||||
}
|
||||
// Background colour
|
||||
Local<Array> background = Local<Array>::Cast(options->Get(NanNew<String>("background")));
|
||||
@@ -874,6 +1005,7 @@ NAN_METHOD(resize) {
|
||||
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
||||
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
|
||||
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
||||
baton->rotateBeforePreExtract = options->Get(NanNew<String>("rotateBeforePreExtract"))->BooleanValue();
|
||||
baton->flip = options->Get(NanNew<String>("flip"))->BooleanValue();
|
||||
baton->flop = options->Get(NanNew<String>("flop"))->BooleanValue();
|
||||
// Output options
|
||||
@@ -881,12 +1013,13 @@ NAN_METHOD(resize) {
|
||||
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
|
||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
||||
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
||||
baton->withoutChromaSubsampling = options->Get(NanNew<String>("withoutChromaSubsampling"))->BooleanValue();
|
||||
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
||||
// Output filename or __format for Buffer
|
||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
||||
|
||||
// Join queue for worker thread
|
||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
||||
NanCallback *callback = new NanCallback(args[1].As<Function>());
|
||||
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
||||
|
||||
// Increment queued task counter
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef SHARP_RESIZE_H
|
||||
#define SHARP_RESIZE_H
|
||||
#ifndef SRC_RESIZE_H_
|
||||
#define SRC_RESIZE_H_
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(resize);
|
||||
|
||||
#endif
|
||||
#endif // SRC_RESIZE_H_
|
||||
|
||||
16
src/sharp.cc
16
src/sharp.cc
@@ -8,25 +8,14 @@
|
||||
#include "resize.h"
|
||||
#include "utilities.h"
|
||||
|
||||
using namespace v8;
|
||||
|
||||
static void at_exit(void* arg) {
|
||||
NanScope();
|
||||
vips_shutdown();
|
||||
}
|
||||
|
||||
extern "C" void init(Handle<Object> target) {
|
||||
extern "C" void init(v8::Handle<v8::Object> target) {
|
||||
NanScope();
|
||||
vips_init("sharp");
|
||||
node::AtExit(at_exit);
|
||||
|
||||
// Set libvips operation cache limits
|
||||
vips_cache_set_max_mem(100 * 1048576); // 100 MB
|
||||
vips_cache_set_max_mem(100 * 1024 * 1024); // 100 MB
|
||||
vips_cache_set_max(500); // 500 operations
|
||||
|
||||
// Notify the V8 garbage collector of max cache size
|
||||
NanAdjustExternalMemory(vips_cache_get_max_mem());
|
||||
|
||||
// Methods available to JavaScript
|
||||
NODE_SET_METHOD(target, "metadata", metadata);
|
||||
NODE_SET_METHOD(target, "resize", resize);
|
||||
@@ -34,6 +23,7 @@ extern "C" void init(Handle<Object> target) {
|
||||
NODE_SET_METHOD(target, "concurrency", concurrency);
|
||||
NODE_SET_METHOD(target, "counters", counters);
|
||||
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
|
||||
NODE_SET_METHOD(target, "format", format);
|
||||
}
|
||||
|
||||
NODE_MODULE(sharp, init)
|
||||
|
||||
@@ -6,8 +6,14 @@
|
||||
#include "common.h"
|
||||
#include "utilities.h"
|
||||
|
||||
using namespace v8;
|
||||
using namespace sharp;
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
using v8::Number;
|
||||
using v8::String;
|
||||
using v8::Boolean;
|
||||
|
||||
using sharp::counterQueue;
|
||||
using sharp::counterProcess;
|
||||
|
||||
/*
|
||||
Get and set cache memory and item limits
|
||||
@@ -70,6 +76,69 @@ NAN_METHOD(counters) {
|
||||
NAN_METHOD(libvipsVersion) {
|
||||
NanScope();
|
||||
char version[9];
|
||||
snprintf(version, 9, "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||
snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||
NanReturnValue(NanNew<String>(version));
|
||||
}
|
||||
|
||||
/*
|
||||
Get available input/output file/buffer/stream formats
|
||||
*/
|
||||
NAN_METHOD(format) {
|
||||
NanScope();
|
||||
|
||||
// Attribute names
|
||||
Local<String> attrId = NanNew<String>("id");
|
||||
Local<String> attrInput = NanNew<String>("input");
|
||||
Local<String> attrOutput = NanNew<String>("output");
|
||||
Local<String> attrFile = NanNew<String>("file");
|
||||
Local<String> attrBuffer = NanNew<String>("buffer");
|
||||
Local<String> attrStream = NanNew<String>("stream");
|
||||
|
||||
// Which load/save operations are available for each compressed format?
|
||||
Local<Object> format = NanNew<Object>();
|
||||
for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) {
|
||||
// Input
|
||||
Local<Object> input = NanNew<Object>();
|
||||
input->Set(attrFile, NanNew<Boolean>(
|
||||
vips_type_find("VipsOperation", (f + "load").c_str())));
|
||||
input->Set(attrBuffer, NanNew<Boolean>(
|
||||
vips_type_find("VipsOperation", (f + "load_buffer").c_str())));
|
||||
input->Set(attrStream, input->Get(attrBuffer));
|
||||
// Output
|
||||
Local<Object> output = NanNew<Object>();
|
||||
output->Set(attrFile, NanNew<Boolean>(
|
||||
vips_type_find("VipsOperation", (f + "save").c_str())));
|
||||
output->Set(attrBuffer, NanNew<Boolean>(
|
||||
vips_type_find("VipsOperation", (f + "save_buffer").c_str())));
|
||||
output->Set(attrStream, output->Get(attrBuffer));
|
||||
// Other attributes
|
||||
Local<Object> container = NanNew<Object>();
|
||||
Local<String> formatId = NanNew<String>(f);
|
||||
container->Set(attrId, formatId);
|
||||
container->Set(attrInput, input);
|
||||
container->Set(attrOutput, output);
|
||||
// Add to set of formats
|
||||
format->Set(formatId, container);
|
||||
}
|
||||
|
||||
// Raw, uncompressed data
|
||||
Local<Object> raw = NanNew<Object>();
|
||||
raw->Set(attrId, NanNew<String>("raw"));
|
||||
format->Set(NanNew<String>("raw"), raw);
|
||||
// No support for raw input yet, so always false
|
||||
Local<Boolean> unsupported = NanNew<Boolean>(false);
|
||||
Local<Object> rawInput = NanNew<Object>();
|
||||
rawInput->Set(attrFile, unsupported);
|
||||
rawInput->Set(attrBuffer, unsupported);
|
||||
rawInput->Set(attrStream, unsupported);
|
||||
raw->Set(attrInput, rawInput);
|
||||
// Raw output via Buffer/Stream is available in libvips >= 7.42.0
|
||||
Local<Boolean> supportsRawOutput = NanNew<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
|
||||
Local<Object> rawOutput = NanNew<Object>();
|
||||
rawOutput->Set(attrFile, unsupported);
|
||||
rawOutput->Set(attrBuffer, supportsRawOutput);
|
||||
rawOutput->Set(attrStream, supportsRawOutput);
|
||||
raw->Set(attrOutput, rawOutput);
|
||||
|
||||
NanReturnValue(format);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SHARP_UTILITIES_H
|
||||
#define SHARP_UTILITIES_H
|
||||
#ifndef SRC_UTILITIES_H_
|
||||
#define SRC_UTILITIES_H_
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
@@ -7,5 +7,6 @@ NAN_METHOD(cache);
|
||||
NAN_METHOD(concurrency);
|
||||
NAN_METHOD(counters);
|
||||
NAN_METHOD(libvipsVersion);
|
||||
NAN_METHOD(format);
|
||||
|
||||
#endif
|
||||
#endif // SRC_UTILITIES_H_
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"imagemagick": "^0.1.3",
|
||||
"imagemagick-native": "^1.6.0",
|
||||
"imagemagick-native": "^1.7.0",
|
||||
"gm": "^1.17.0",
|
||||
"async": "^0.9.0",
|
||||
"semver": "^4.1.0",
|
||||
"semver": "^4.3.0",
|
||||
"benchmark": "^1.0.0"
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
|
||||
@@ -347,6 +347,18 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-without-chroma-subsampling', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpgBuffer).resize(width, height).withoutChromaSubsampling().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-rotate', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
|
||||
@@ -11,8 +11,11 @@ var fixtures = require('../fixtures');
|
||||
var min = 320;
|
||||
var max = 960;
|
||||
|
||||
// Nearest equivalent to bilinear
|
||||
var magickFilter = 'Triangle';
|
||||
|
||||
var randomDimension = function() {
|
||||
return Math.random() * (max - min) + min;
|
||||
return Math.ceil(Math.random() * (max - min) + min);
|
||||
};
|
||||
|
||||
new Benchmark.Suite('random').add('imagemagick', {
|
||||
@@ -23,7 +26,9 @@ new Benchmark.Suite('random').add('imagemagick', {
|
||||
dstPath: fixtures.outputJpg,
|
||||
quality: 0.8,
|
||||
width: randomDimension(),
|
||||
height: randomDimension()
|
||||
height: randomDimension(),
|
||||
format: 'jpg',
|
||||
filter: magickFilter
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
@@ -35,14 +40,18 @@ new Benchmark.Suite('random').add('imagemagick', {
|
||||
}).add('gm', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
gm(fixtures.inputJpg).resize(randomDimension(), randomDimension()).quality(80).toBuffer(function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
gm(fixtures.inputJpg)
|
||||
.resize(randomDimension(), randomDimension())
|
||||
.filter(magickFilter)
|
||||
.quality(80)
|
||||
.toBuffer(function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp', {
|
||||
defer: true,
|
||||
|
||||
17
test/fixtures/Wikimedia-logo.svg
vendored
Normal file
17
test/fixtures/Wikimedia-logo.svg
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
||||
id="Wikimedia logo"
|
||||
viewBox="-599 -599 1198 1198" width="1024" height="1024">
|
||||
<defs>
|
||||
<clipPath id="mask">
|
||||
<path d="M 47.5,-87.5 v 425 h -95 v -425 l -552,-552 v 1250 h 1199 v -1250 z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#mask)">
|
||||
<circle id="green parts" fill="#396" r="336.5"/>
|
||||
<circle id="blue arc" fill="none" stroke="#069" r="480.25" stroke-width="135.5" />
|
||||
</g>
|
||||
<circle fill="#900" cy="-379.5" r="184.5" id="red circle"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 692 B |
BIN
test/fixtures/corrupt-header.jpg
vendored
Normal file
BIN
test/fixtures/corrupt-header.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
test/fixtures/free-gearhead-pack.psd
vendored
Normal file
BIN
test/fixtures/free-gearhead-pack.psd
vendored
Normal file
Binary file not shown.
3
test/fixtures/index.js
vendored
3
test/fixtures/index.js
vendored
@@ -14,6 +14,7 @@ module.exports = {
|
||||
inputJpgWithGammaHoliness: getPath('gamma_dalai_lama_gray.jpg'), // http://www.4p8.com/eric.brasseur/gamma.html
|
||||
inputJpgWithCmykProfile: getPath('Channel_digital_image_CMYK_color.jpg'), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg
|
||||
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
|
||||
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
|
||||
|
||||
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||
inputPngWithTransparency: getPath('blackbug.png'), // public domain
|
||||
@@ -21,6 +22,8 @@ module.exports = {
|
||||
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
||||
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
||||
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||
inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg
|
||||
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack
|
||||
|
||||
outputJpg: getPath('output.jpg'),
|
||||
outputPng: getPath('output.png'),
|
||||
|
||||
46
test/unit/cpplint.js
Executable file
46
test/unit/cpplint.js
Executable file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var assert = require('assert');
|
||||
|
||||
var cpplint = require('node-cpplint/lib/');
|
||||
|
||||
describe('cpplint', function() {
|
||||
|
||||
// List C++ source files
|
||||
fs.readdirSync(path.join(__dirname, '..', '..', 'src')).forEach(function (source) {
|
||||
var file = path.join('src', source);
|
||||
it(file, function(done) {
|
||||
// Lint each source file
|
||||
cpplint({
|
||||
files: [file],
|
||||
linelength: 140,
|
||||
filters: {
|
||||
legal: {
|
||||
copyright: false
|
||||
},
|
||||
build: {
|
||||
include: false,
|
||||
include_order: false
|
||||
},
|
||||
whitespace: {
|
||||
blank_line: false,
|
||||
comments: false,
|
||||
parens: false
|
||||
}
|
||||
}
|
||||
}, function(err, report) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var expected = {};
|
||||
expected[file] = [];
|
||||
assert.deepEqual(expected, report);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -92,4 +92,86 @@ describe('Partial image extraction', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Extract then rotate', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.extract(10, 10, 100, 100)
|
||||
.rotate(90)
|
||||
.toFile(fixtures.path('output.extract.extract-then-rotate.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Rotate then extract', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(90)
|
||||
.extract(10, 10, 100, 100)
|
||||
.toFile(fixtures.path('output.extract.rotate-then-extract.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function() {
|
||||
|
||||
it('Undefined', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).extract();
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('String top', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Non-integral left', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Negative width - negative', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).extract(10, 10, -10, 10);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Null height', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).extract(10, 10, 10, null);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
313
test/unit/io.js
313
test/unit/io.js
@@ -18,6 +18,7 @@ describe('Input/output', function() {
|
||||
sharp(fixtures.outputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -35,6 +36,7 @@ describe('Input/output', function() {
|
||||
sharp(fixtures.outputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -49,6 +51,7 @@ describe('Input/output', function() {
|
||||
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||
var pipeline = sharp().resize(320, 240).toFile(fixtures.outputJpg, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -63,6 +66,7 @@ describe('Input/output', function() {
|
||||
var pipeline = sharp().resize(320, 240).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -90,6 +94,7 @@ describe('Input/output', function() {
|
||||
sharp(fixtures.outputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -130,13 +135,15 @@ describe('Input/output', function() {
|
||||
readableButNotAnImage.pipe(writable);
|
||||
});
|
||||
|
||||
it('Sequential read', function(done) {
|
||||
it('Sequential read, force JPEG', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.sequentialRead()
|
||||
.resize(320, 240)
|
||||
.toFormat(sharp.format.jpeg)
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -144,13 +151,15 @@ describe('Input/output', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Not sequential read', function(done) {
|
||||
it('Not sequential read, force JPEG', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.sequentialRead(false)
|
||||
.resize(320, 240)
|
||||
.toFormat('jpeg')
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -221,6 +230,7 @@ describe('Input/output', function() {
|
||||
sharp(data).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -262,31 +272,74 @@ describe('Input/output', function() {
|
||||
.resize(320, 240)
|
||||
.png()
|
||||
.progressive(false)
|
||||
.toBuffer(function(err, nonProgressive, info) {
|
||||
.toBuffer(function(err, nonProgressiveData, nonProgressiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, nonProgressive.length > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
sharp(nonProgressive)
|
||||
assert.strictEqual(true, nonProgressiveData.length > 0);
|
||||
assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
|
||||
assert.strictEqual('png', nonProgressiveInfo.format);
|
||||
assert.strictEqual(320, nonProgressiveInfo.width);
|
||||
assert.strictEqual(240, nonProgressiveInfo.height);
|
||||
sharp(nonProgressiveData)
|
||||
.progressive()
|
||||
.toBuffer(function(err, progressive, info) {
|
||||
.toBuffer(function(err, progressiveData, progressiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, progressive.length > 0);
|
||||
assert.strictEqual(true, progressive.length > nonProgressive.length);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
assert.strictEqual(true, progressiveData.length > 0);
|
||||
assert.strictEqual(progressiveData.length, progressiveInfo.size);
|
||||
assert.strictEqual(true, progressiveData.length > nonProgressiveData.length);
|
||||
assert.strictEqual('png', progressiveInfo.format);
|
||||
assert.strictEqual(320, progressiveInfo.width);
|
||||
assert.strictEqual(240, progressiveInfo.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('WebP output', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.toFormat(sharp.format.webp)
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid output format', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().toFormat('zoinks');
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(!isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('File input with corrupt header fails gracefully', function(done) {
|
||||
sharp(fixtures.inputJpgWithCorruptHeader)
|
||||
.toBuffer(function(err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Buffer input with corrupt header fails gracefully', function(done) {
|
||||
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
||||
.toBuffer(function(err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Output filename without extension uses input format', function() {
|
||||
|
||||
it('JPEG', function(done) {
|
||||
sharp(fixtures.inputJpg).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
@@ -298,6 +351,7 @@ describe('Input/output', function() {
|
||||
it('PNG', function(done) {
|
||||
sharp(fixtures.inputPng).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
@@ -309,6 +363,7 @@ describe('Input/output', function() {
|
||||
it('Transparent PNG', function(done) {
|
||||
sharp(fixtures.inputPngWithTransparency).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
@@ -319,6 +374,7 @@ describe('Input/output', function() {
|
||||
it('WebP', function(done) {
|
||||
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
@@ -330,6 +386,7 @@ describe('Input/output', function() {
|
||||
it('TIFF', function(done) {
|
||||
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
@@ -369,29 +426,31 @@ describe('Input/output', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
||||
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.41.0]', function(done) {
|
||||
if (semver.gte(sharp.libvipsVersion(), '7.42.0')) {
|
||||
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function(done) {
|
||||
// First generate with adaptive filtering
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.withoutAdaptiveFiltering(false)
|
||||
.toBuffer(function(err, dataAdaptive, info) {
|
||||
.toBuffer(function(err, adaptiveData, adaptiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, dataAdaptive.length > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
assert.strictEqual(true, adaptiveData.length > 0);
|
||||
assert.strictEqual(adaptiveData.length, adaptiveInfo.size);
|
||||
assert.strictEqual('png', adaptiveInfo.format);
|
||||
assert.strictEqual(320, adaptiveInfo.width);
|
||||
assert.strictEqual(240, adaptiveInfo.height);
|
||||
// Then generate without
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.withoutAdaptiveFiltering()
|
||||
.toBuffer(function(err, dataWithoutAdaptive, info) {
|
||||
.toBuffer(function(err, withoutAdaptiveData, withoutAdaptiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, dataWithoutAdaptive.length > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
assert.strictEqual(true, dataWithoutAdaptive.length < dataAdaptive.length);
|
||||
assert.strictEqual(true, withoutAdaptiveData.length > 0);
|
||||
assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size);
|
||||
assert.strictEqual('png', withoutAdaptiveInfo.format);
|
||||
assert.strictEqual(320, withoutAdaptiveInfo.width);
|
||||
assert.strictEqual(240, withoutAdaptiveInfo.height);
|
||||
assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -400,8 +459,72 @@ describe('Input/output', function() {
|
||||
|
||||
});
|
||||
|
||||
if (semver.gte(sharp.libvipsVersion(), '7.40.0')) {
|
||||
it('Load TIFF from Buffer [libvips ' + sharp.libvipsVersion() + '>=7.40.0]', function(done) {
|
||||
it('Without chroma subsampling generates larger file', function(done) {
|
||||
// First generate with chroma subsampling (default)
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.withoutChromaSubsampling(false)
|
||||
.toBuffer(function(err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withChromaSubsamplingData.length > 0);
|
||||
assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
|
||||
assert.strictEqual('jpeg', withChromaSubsamplingInfo.format);
|
||||
assert.strictEqual(320, withChromaSubsamplingInfo.width);
|
||||
assert.strictEqual(240, withChromaSubsamplingInfo.height);
|
||||
// Then generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.withoutChromaSubsampling()
|
||||
.toBuffer(function(err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
|
||||
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
|
||||
assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format);
|
||||
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
|
||||
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
|
||||
assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (sharp.format.magick.input.file) {
|
||||
it('Convert SVG, if supported, to PNG', function(done) {
|
||||
sharp(fixtures.inputSvg)
|
||||
.resize(100, 100)
|
||||
.toFormat('png')
|
||||
.toFile(fixtures.path('output.svg.png'), function(err, info) {
|
||||
if (err) {
|
||||
assert.strictEqual('Input file is of an unsupported image format', err.message);
|
||||
} else {
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sharp.format.magick.input.file) {
|
||||
it('Convert PSD to PNG', function(done) {
|
||||
sharp(fixtures.inputPsd)
|
||||
.resize(320, 240)
|
||||
.toFormat(sharp.format.png)
|
||||
.toFile(fixtures.path('output.psd.png'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sharp.format.tiff.input.buffer) {
|
||||
it('Load TIFF from Buffer', function(done) {
|
||||
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
|
||||
sharp(inputTiffBuffer)
|
||||
.resize(320, 240)
|
||||
@@ -409,6 +532,7 @@ describe('Input/output', function() {
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
@@ -417,4 +541,135 @@ describe('Input/output', function() {
|
||||
});
|
||||
}
|
||||
|
||||
if (sharp.format.magick.input.buffer) {
|
||||
it('Load GIF from Buffer', function(done) {
|
||||
var inputGifBuffer = fs.readFileSync(fixtures.inputGif);
|
||||
sharp(inputGifBuffer)
|
||||
.resize(320, 240)
|
||||
.jpeg()
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sharp.format.raw.output.buffer) {
|
||||
describe('Ouput raw, uncompressed image data', function() {
|
||||
it('1 channel greyscale image', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.greyscale()
|
||||
.resize(32, 24)
|
||||
.raw()
|
||||
.toBuffer(function(err, data, info) {
|
||||
assert.strictEqual(32 * 24 * 1, info.size);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('raw', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('3 channel colour image without transparency', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(32, 24)
|
||||
.toFormat('raw')
|
||||
.toBuffer(function(err, data, info) {
|
||||
assert.strictEqual(32 * 24 * 3, info.size);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('raw', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('4 channel colour image with transparency', function(done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.resize(32, 24)
|
||||
.toFormat(sharp.format.raw)
|
||||
.toBuffer(function(err, data, info) {
|
||||
assert.strictEqual(32 * 24 * 4, info.size);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('raw', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('Limit pixel count of input image', function() {
|
||||
|
||||
it('Invalid fails - negative', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().limitInputPixels(-1);
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(!isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid fails - float', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().limitInputPixels(12.3);
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(!isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid fails - huge', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().limitInputPixels(Math.pow(0x3FFF, 2) + 1);
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(!isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid fails - string', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().limitInputPixels('fail');
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(!isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Same size as input works', function(done) {
|
||||
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
sharp(fixtures.inputJpg)
|
||||
.limitInputPixels(metadata.width * metadata.height)
|
||||
.toBuffer(function(err) {
|
||||
assert.strictEqual(true, !err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Smaller than input fails', function(done) {
|
||||
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
sharp(fixtures.inputJpg)
|
||||
.limitInputPixels(metadata.width * metadata.height - 1)
|
||||
.toBuffer(function(err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -122,6 +122,15 @@ describe('Image metadata', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Non-existent file in, Promise out', function(done) {
|
||||
sharp('fail').metadata().then(function(metadata) {
|
||||
throw new Error('Non-existent file');
|
||||
}, function (err) {
|
||||
assert.ok(!!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Stream in, Promise out', function(done) {
|
||||
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||
var pipeline = sharp();
|
||||
@@ -167,7 +176,7 @@ describe('Image metadata', function() {
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasProfile);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
image.resize(metadata.width / 2).toBuffer(function(err, data, info) {
|
||||
image.resize(Math.floor(metadata.width / 2)).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(1362, info.width);
|
||||
@@ -207,4 +216,20 @@ describe('Image metadata', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('File input with corrupt header fails gracefully', function(done) {
|
||||
sharp(fixtures.inputJpgWithCorruptHeader)
|
||||
.metadata(function(err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Buffer input with corrupt header fails gracefully', function(done) {
|
||||
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
||||
.metadata(function(err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('Resize dimensions', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid width', function(done) {
|
||||
it('Invalid width - NaN', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize('spoons', 240);
|
||||
@@ -75,7 +75,7 @@ describe('Resize dimensions', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height', function(done) {
|
||||
it('Invalid height - NaN', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 'spoons');
|
||||
@@ -86,6 +86,50 @@ describe('Resize dimensions', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid width - float', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(1.5, 240);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height - float', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 1.5);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid width - too large', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(0x4000, 240);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height - too large', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 0x4000);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('TIFF embed known to cause rounding errors', function(done) {
|
||||
sharp(fixtures.inputTiff).resize(240, 320).embed().jpeg().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -141,6 +185,39 @@ describe('Resize dimensions', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Min width or height considering ratio (landscape)', function(done) {
|
||||
sharp(fixtures.inputJpg).resize(320, 320).min().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(392, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Min width or height considering ratio (portrait)', function(done) {
|
||||
sharp(fixtures.inputTiff).resize(320, 320).min().jpeg().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(422, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Provide only one dimension with min, should default to crop', function(done) {
|
||||
sharp(fixtures.inputJpg).resize(320).min().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Do not enlarge when input width is already less than output width', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(2800)
|
||||
|
||||
@@ -50,4 +50,25 @@ describe('Utilities', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Format', function() {
|
||||
it('Contains expected attributes', function() {
|
||||
assert.strictEqual('object', typeof sharp.format);
|
||||
Object.keys(sharp.format).forEach(function(format) {
|
||||
assert.strictEqual(true, 'id' in sharp.format[format]);
|
||||
assert.strictEqual(format, sharp.format[format].id);
|
||||
['input', 'output'].forEach(function(direction) {
|
||||
assert.strictEqual(true, direction in sharp.format[format]);
|
||||
assert.strictEqual('object', typeof sharp.format[format][direction]);
|
||||
assert.strictEqual(3, Object.keys(sharp.format[format][direction]).length);
|
||||
assert.strictEqual(true, 'file' in sharp.format[format][direction]);
|
||||
assert.strictEqual(true, 'buffer' in sharp.format[format][direction]);
|
||||
assert.strictEqual(true, 'stream' in sharp.format[format][direction]);
|
||||
assert.strictEqual('boolean', typeof sharp.format[format][direction].file);
|
||||
assert.strictEqual('boolean', typeof sharp.format[format][direction].buffer);
|
||||
assert.strictEqual('boolean', typeof sharp.format[format][direction].stream);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user