Compare commits

...

28 Commits

Author SHA1 Message Date
Lovell Fuller
c879df3b31 Release v0.18.0 2017-05-30 08:09:59 +01:00
Lovell Fuller
361ed98353 Remove previously-deprecated output format 'option' functions 2017-05-23 21:57:05 +01:00
Lovell Fuller
d45f8ef2d3 Document the cache-free nature of metadata extraction #796 2017-05-23 21:24:29 +01:00
Lovell Fuller
d6a63d11d7 Docs refresh 2017-05-22 21:49:37 +01:00
jingsam
4c6804eadc Add toFormat 'jpg' alias for 'jpeg' (#814) 2017-05-22 12:59:43 +01:00
Nicolas Coden
99810c0311 Add support for any rotation angle (#791)
Allow to provide any positive or negative multiple of 90 to `.rotate(...)`.
Negative angles and angles above 360 are converted to valid 0/90/180/270
rotations (0 rotations are still ignored).

Changes:
- [Node] Add `useExifOrientation` internal variable to know if the Exif
  orientation must be used instead of the provided angle. This allows to save a
  negative angle in the `angle` option, because the `-1` special case is not
  needed.

- [Node] Change check for planed-rotation in extract, to prepare a
  rotation before extraction: check with both `angle` and `useExifOrientation`
  options.
  I think this check contains a bit too much logics on rotation options. Maybe
  we could move this condition to a dedicated function.

- [C++] Separate `CalculateRotationAndFlip` into two generic functions:
  - `CalculateExifRotationAndFlip`: Calculate the angle of rotation and
    need-to-flip for the given Exif orientation.
  - `CalculateAngleRotation`: Calculate the rotation for the given angle.

  One or the other function is used to calculate the rotation, depending on
  wether the Exif orientation tag or the provided angle must be used.

- Add unit tests for `-3690`, `-450`, `-90`, `90`, `450`, `3690` and `-3780`,
  `-540`, `0`, `180`, `540`, `3780` rotations
- Add `320x240` fixture image for tests.

Unrelated changes (squashed):
- Add ncoden to the list of contributors
2017-05-22 11:08:33 +01:00
gmaliar
d15fb1ab1b Docs: add link to TailorBrands-maintained libvips Dockerfiles (#813) 2017-05-21 20:37:57 +01:00
Lovell Fuller
0a6d8b37ad Ensure double to int cast introduced in 4d1a169 is static 2017-05-21 19:05:56 +01:00
Lovell Fuller
f78ffdb9ce Upgrade to libvips v8.5.5 2017-05-21 18:31:06 +01:00
Lovell Fuller
b7b6fdbdf5 Update perf test contenders, add node-images 2017-05-13 20:08:53 +01:00
Lovell Fuller
e398b471e1 Prevent aliasing by using dynamic values for shrink(-on-load) 2017-05-13 18:46:39 +01:00
Lovell Fuller
48f69f3d88 Upgrade libpng to v1.6.29 2017-05-13 18:20:54 +01:00
Lovell Fuller
95850d75f6 Include pixel format depth when reading metadata 2017-05-07 09:29:38 +01:00
Lovell Fuller
c41d755441 Ctor single arg: allow plain object, reject null/undefined
Thank you @kub1x
2017-05-06 19:03:14 +01:00
Lovell Fuller
39a21787b7 Remove 'require' test as bufferutil now ships prebuilt 2017-05-06 15:49:50 +01:00
Lovell Fuller
36078f9903 Switch to the libvips crop strategy implementations 2017-05-06 14:46:28 +01:00
Lovell Fuller
2f534dc01c Base maximum output dimensions on limitation of format 2017-05-04 23:20:37 +01:00
Lovell Fuller
c8e59f08ec Add support for Buffer and Stream-based TIFF output 2017-05-04 16:40:49 +01:00
Lovell Fuller
19dd6a997f Doc refresh, thank you @cspotcode 2017-05-01 09:34:10 +01:00
Lovell Fuller
4d1a1694cd Improve perf/accuracy of nearest neighbour integral upsample 2017-04-30 20:54:48 +01:00
Lovell Fuller
52bea15ad7 Upgrade libvips dependency to v8.5.4, plus other bumps 2017-04-26 23:04:08 +01:00
Lovell Fuller
6592361c5a Ensure ARM64 pre-built binaries use correct C++11 ABI 2017-04-26 21:41:03 +01:00
Lovell Fuller
f3f83494f5 Credit contributor YvesBos 2017-04-26 21:40:30 +01:00
Lovell Fuller
1169afbe90 Avoid (un)premultiplication for overlay image without alpha channel
Add 'premultiplied' boolean attribute to output info, helps test
2017-04-26 21:37:43 +01:00
Lovell Fuller
301bfbd271 Expose libvips warnings via NODE_DEBUG env var 2017-04-26 21:37:43 +01:00
Lovell Fuller
46aec7eabc Upgrade libvips dependency and packaging to v8.5.1 2017-04-26 21:37:43 +01:00
YvesBos
4cd3b66761 Add support for squashing TIFF output to 1-bit (#783) 2017-04-26 17:47:29 +01:00
Jakub Podlaha
567e3dd258 Add gentoo support to glibc detection (#760) 2017-04-06 15:17:30 +01:00
63 changed files with 1133 additions and 909 deletions

View File

@@ -267,7 +267,7 @@
'vendor/lib/libstdc++-6.dll',
'vendor/lib/libtiff-5.dll',
'vendor/lib/libvips-42.dll',
'vendor/lib/libwebp-6.dll',
'vendor/lib/libwebp-7.dll',
'vendor/lib/libxml2-2.dll',
'vendor/lib/zlib1.dll'
]

View File

@@ -3,7 +3,6 @@
const fs = require('fs');
const os = require('os');
const path = require('path');
const zlib = require('zlib');
const caw = require('caw');
const got = require('got');
@@ -29,21 +28,22 @@ const isFile = function (file) {
};
const unpack = function (tarPath, done) {
const extractor = tar.Extract({ path: path.join(__dirname, 'vendor') });
if (done) {
extractor.on('end', done);
}
extractor.on('error', error);
fs.createReadStream(tarPath)
.on('error', error)
.pipe(zlib.Unzip())
.pipe(extractor);
const vendorPath = path.join(__dirname, 'vendor');
fs.mkdirSync(vendorPath);
tar
.extract({
file: tarPath,
cwd: vendorPath,
strict: true
})
.then(done)
.catch(error);
};
const platformId = function () {
const platformId = [platform];
if (arch === 'arm' || arch === 'armhf' || arch === 'arch64') {
const armVersion = (arch === 'arch64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6';
if (arch === 'arm' || arch === 'armhf' || arch === 'arm64') {
const armVersion = (arch === 'arm64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6';
platformId.push('armv' + armVersion);
} else {
platformId.push(arch);
@@ -73,7 +73,7 @@ module.exports.download_vips = function () {
// Ensure glibc >= 2.15
const lddVersion = process.env.LDD_VERSION;
if (lddVersion) {
if (/(glibc|gnu libc)/i.test(lddVersion)) {
if (/(glibc|gnu libc|gentoo)/i.test(lddVersion)) {
const glibcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : '';
if (glibcVersion && semver.lt(glibcVersion + '.0', '2.13.0')) {
error('glibc version ' + glibcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');

View File

@@ -37,7 +37,7 @@ An alpha channel may be present, and will be unchanged by the operation.
**Parameters**
- `greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -47,7 +47,7 @@ Alternative spelling of `greyscale`.
**Parameters**
- `grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**

View File

@@ -11,16 +11,18 @@ Overlay (composite) an image over the processed (resized, extracted etc.) image.
The overlay image must be the same size or smaller than the processed image.
If both `top` and `left` options are provided, they take precedence over `gravity`.
If the overlay image contains an alpha channel then composition with premultiplication will occur.
**Parameters**
- `overlay` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Buffer containing image data or String containing the path to an image file.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.gravity` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** gravity at which to place the overlay. (optional, default `'centre'`)
- `options.gravity` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** gravity at which to place the overlay. (optional, default `'centre'`)
- `options.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the top edge.
- `options.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the pixel offset from the left edge.
- `options.tile` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `options.cutout` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`)
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** integral number representing the DPI for vector overlay image. (optional, default `72`)
- `options.tile` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `options.cutout` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`)
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** integral number representing the DPI for vector overlay image. (optional, default `72`)
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes overlay when using raw pixel data.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**

View File

@@ -14,9 +14,9 @@
- `input` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** if present, is an Object with optional attributes.
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** integral number representing the DPI for vector images. (optional, default `72`)
- `options.density` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** integral number representing the DPI for vector images. (optional, default `72`)
- `options.raw` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
- `options.raw.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?**
@@ -53,7 +53,7 @@ readableStream.pipe(transformer).pipe(writableStream);
```javascript
// Create a blank 300x200 PNG image of semi-transluent red pixels
sharp(null, {
sharp({
create: {
width: 300,
height: 200,
@@ -77,7 +77,7 @@ An Object containing nested boolean values representing the available input and
**Examples**
```javascript
console.log(sharp.format());
console.log(sharp.format);
```
Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**

View File

@@ -28,14 +28,15 @@ Returns **Sharp**
## metadata
Fast access to image metadata without decoding any compressed image data.
Fast access to (uncached) image metadata without decoding any compressed image data.
A Promises/A+ promise is returned when `callback` is not provided.
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
- `width`: Number of pixels wide
- `height`: Number of pixels high
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
- `density`: Number of pixels per inch (DPI), if present
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
@@ -88,6 +89,6 @@ This will reduce memory usage and can improve performance on some systems.
**Parameters**
- `sequentialRead` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `sequentialRead` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**

View File

@@ -24,7 +24,10 @@
Rotate the output image by either an explicit angle
or auto-orient based on the EXIF `Orientation` tag.
Use this method without angle to determine the angle from EXIF data.
If an angle is provided, it is converted to a valid 90/180/270deg rotation.
For example, `-450` will produce a 270deg rotation.
If no angle is provided, it is determined from the EXIF data.
Mirroring is supported and may infer the use of a flip operation.
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
@@ -34,7 +37,7 @@ for example `rotate(x).extract(y)` will produce a different result to `extract(y
**Parameters**
- `angle` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 0, 90, 180 or 270. (optional, default `auto`)
- `angle` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** angle of rotation, must be a multiple of 90. (optional, default `auto`)
**Examples**
@@ -101,7 +104,7 @@ The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
**Parameters**
- `flip` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `flip` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -112,7 +115,7 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
**Parameters**
- `flop` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `flop` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -126,8 +129,8 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av
**Parameters**
- `sigma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- `flat` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- `flat` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
- `jagged` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -184,7 +187,7 @@ Merge alpha transparency channel, if any, with `background`.
**Parameters**
- `flatten` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `flatten` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -194,7 +197,7 @@ Trim "boring" pixels from all edges that contain values within a percentage simi
**Parameters**
- `tolerance` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1 and 99 representing the percentage similarity. (optional, default `10`)
- `tolerance` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** value between 1 and 99 representing the percentage similarity. (optional, default `10`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -211,7 +214,7 @@ when applying a gamma correction.
**Parameters**
- `gamma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** value between 1.0 and 3.0. (optional, default `2.2`)
- `gamma` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** value between 1.0 and 3.0. (optional, default `2.2`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -224,7 +227,7 @@ Produce the "negative" of the image.
**Parameters**
- `negate` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `negate` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -234,7 +237,7 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
**Parameters**
- `normalise` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `normalise` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -244,7 +247,7 @@ Alternative spelling of normalise.
**Parameters**
- `normalize` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `normalize` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**
@@ -258,8 +261,8 @@ Convolve the image with the specified kernel.
- `kernel.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels.
- `kernel.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels.
- `kernel.kernel` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Array of length `width*height` containing the kernel values.
- `kernel.scale` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the offset of the kernel in pixels. (optional, default `0`)
- `kernel.scale` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the offset of the kernel in pixels. (optional, default `0`)
**Examples**
@@ -287,10 +290,10 @@ Any pixel value greather than or equal to the threshold value will be set to 255
**Parameters**
- `threshold` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `threshold` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling for greyscale. (optional, default `true`)
- `options.greyscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** alternative spelling for greyscale. (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters

View File

@@ -27,7 +27,8 @@ A Promises/A+ promise is returned when `callback` is not provided.
- `fileOut` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the path to write the image data to.
- `callback` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters
@@ -37,14 +38,15 @@ Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe
## toBuffer
Write output to a Buffer.
JPEG, PNG, WebP, and RAW output are supported.
JPEG, PNG, WebP, TIFF and RAW output are supported.
By default, the format will match the input image, except GIF and SVG input which become PNG output.
`callback`, if present, gets three arguments `(err, data, info)` where:
- `err` is an error, if any.
- `data` is the output image data.
- `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`.
- `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used).
A Promise is returned when `callback` is not provided.
**Parameters**
@@ -78,14 +80,14 @@ Use these JPEG options for output image.
**Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** apply trellis quantisation, requires mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** apply overshoot deringing, requires mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling of optimiseScans (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force JPEG output, otherwise attempt to use input format (optional, default `true`)
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** apply trellis quantisation, requires mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** apply overshoot deringing, requires mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** alternative spelling of optimiseScans (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force JPEG output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -99,10 +101,10 @@ Use these PNG options for output image.
**Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** zlib compression level (optional, default `6`)
- `options.adaptiveFiltering` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use adaptive row filtering (optional, default `true`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force PNG output, otherwise attempt to use input format (optional, default `true`)
- `options.progressive` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** zlib compression level (optional, default `6`)
- `options.adaptiveFiltering` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use adaptive row filtering (optional, default `true`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force PNG output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -116,11 +118,11 @@ Use these WebP options for output image.
**Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use near_lossless compression mode (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force WebP output, otherwise attempt to use input format (optional, default `true`)
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use near_lossless compression mode (optional, default `false`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force WebP output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -134,10 +136,11 @@ Use these TIFF options for output image.
**Parameters**
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** compression options: lzw, deflate, jpeg (optional, default `'jpeg'`)
- `options.predictor` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** compression predictor options: none, horizontal, float (optional, default `'none'`)
- `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** compression options: lzw, deflate, jpeg (optional, default `'jpeg'`)
- `options.predictor` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** compression predictor options: none, horizontal, float (optional, default `'none'`)
- `options.squash` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** squash 8-bit images down to 1 bit (optional, default `false`)
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
@@ -173,10 +176,10 @@ Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed arc
**Parameters**
- `tile` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `tile.size` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `tile.overlap` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.container` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
- `tile.size` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `tile.overlap` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.container` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
**Examples**

View File

@@ -33,13 +33,13 @@ Possible enlargement interpolators are:
**Parameters**
- `width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** 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` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
- `width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?**
- `options.kernel` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.interpolator` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** the interpolator to use for image enlargement. (optional, default `'bicubic'`)
- `options.centreSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use \*magick centre sampling convention instead of corner sampling. (optional, default `false`)
- `options.centerSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** alternative spelling of centreSampling. (optional, default `false`)
- `options.kernel` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.interpolator` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the interpolator to use for image enlargement. (optional, default `'bicubic'`)
- `options.centreSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** use \*magick centre sampling convention instead of corner sampling. (optional, default `false`)
- `options.centerSampling` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** alternative spelling of centreSampling. (optional, default `false`)
**Examples**
@@ -78,7 +78,7 @@ then repeatedly ranks edge regions, discarding the edge with the lowest score ba
**Parameters**
- `crop` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. (optional, default `'centre'`)
- `crop` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. (optional, default `'centre'`)
**Examples**
@@ -172,6 +172,6 @@ This is equivalent to GraphicsMagick's `>` geometry option:
**Parameters**
- `withoutEnlargement` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `true`)
- `withoutEnlargement` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`)
Returns **Sharp**

View File

@@ -17,9 +17,9 @@ useful for determining how much working memory is required for a particular task
**Parameters**
- `options` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching.
- `options.memory` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** is the maximum memory in MB to use for this cache (optional, default `50`)
- `options.files` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** is the maximum number of files to hold open (optional, default `20`)
- `options.items` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** is the maximum number of operations to cache (optional, default `100`)
- `options.memory` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is the maximum memory in MB to use for this cache (optional, default `50`)
- `options.files` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is the maximum number of files to hold open (optional, default `20`)
- `options.items` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** is the maximum number of operations to cache (optional, default `100`)
**Examples**
@@ -89,7 +89,7 @@ Versions of liborc prior to 0.4.25 are known to segfault under heavy load.
**Parameters**
- `simd` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** (optional, default `false`)
- `simd` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `false`)
**Examples**

View File

@@ -1,5 +1,67 @@
# Changelog
### v0.18 - "*ridge*"
Requires libvips v8.5.5.
#### v0.18.0 - 30<sup>th</sup> May 2017
* Remove the previously-deprecated output format "option" functions:
quality, progressive, compressionLevel, withoutAdaptiveFiltering,
withoutChromaSubsampling, trellisQuantisation, trellisQuantization,
overshootDeringing, optimiseScans and optimizeScans.
* Ensure maximum output dimensions are based on the format to be used.
[#176](https://github.com/lovell/sharp/issues/176)
[@stephanebachelier](https://github.com/stephanebachelier)
* Avoid costly (un)premultiply when using overlayWith without alpha channel.
[#573](https://github.com/lovell/sharp/issues/573)
[@strarsis](https://github.com/strarsis)
* Include pixel depth (e.g. "uchar") when reading metadata.
[#577](https://github.com/lovell/sharp/issues/577)
[@moedusa](https://github.com/moedusa)
* Add support for Buffer and Stream-based TIFF output.
[#587](https://github.com/lovell/sharp/issues/587)
[@strarsis](https://github.com/strarsis)
* Expose warnings from libvips via NODE_DEBUG=sharp environment variable.
[#607](https://github.com/lovell/sharp/issues/607)
[@puzrin](https://github.com/puzrin)
* Switch to the libvips implementation of "attention" and "entropy" crop strategies.
[#727](https://github.com/lovell/sharp/issues/727)
* Improve performance and accuracy of nearest neighbour integral upsampling.
[#752](https://github.com/lovell/sharp/issues/752)
[@MrIbby](https://github.com/MrIbby)
* Constructor single argument API: allow plain object, reject null/undefined.
[#768](https://github.com/lovell/sharp/issues/768)
[@kub1x](https://github.com/kub1x)
* Ensure ARM64 pre-built binaries use correct C++11 ABI version.
[#772](https://github.com/lovell/sharp/issues/772)
[@ajiratech2](https://github.com/ajiratech2)
* Prevent aliasing by using dynamic values for shrink(-on-load).
[#781](https://github.com/lovell/sharp/issues/781)
[@kleisauke](https://github.com/kleisauke)
* Expose libvips' "squash" parameter to enable 1-bit TIFF output.
[#783](https://github.com/lovell/sharp/pull/783)
[@YvesBos](https://github.com/YvesBos)
* Add support for rotation using any multiple of +/-90 degrees.
[#791](https://github.com/lovell/sharp/pull/791)
[@ncoden](https://github.com/ncoden)
* Add "jpg" alias to toFormat as shortened form of "jpeg".
[#814](https://github.com/lovell/sharp/pull/814)
[@jingsam](https://github.com/jingsam)
### v0.17 - "*quill*"
Requires libvips v8.4.2.

View File

@@ -22,7 +22,7 @@ the installation of any external runtime dependencies.
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.
Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
@@ -99,6 +99,7 @@ the help and code contributions of the following people:
* [Jérémy Lal](https://github.com/kapouer)
* [Alice Monday](https://github.com/alice0meta)
* [Kristo Jorgenson](https://github.com/kristojorg)
* [Yves Bos](https://github.com/YvesBos)
Thank you!

View File

@@ -20,7 +20,7 @@ yarn add sharp
[![Linux Build Status](https://circleci.com/gh/lovell/sharp.svg?style=svg&circle-token=6cb6d1d287a51af83722b19ed8885377fbc85e5c)](https://circleci.com/gh/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.5MB.
This involves an automated HTTPS download of approximately 7MB.
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
@@ -57,7 +57,7 @@ via `sharp.cache(false)` to avoid a stack overflow.
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.3MB.
This involves an automated HTTPS download of approximately 7MB.
To use your own version of libvips instead of the provided binaries, make sure it is
at least the version listed under `config.libvips` in the `package.json` file and
@@ -68,7 +68,7 @@ that it can be located using `pkg-config --modversion vips-cpp`.
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 9MB.
This involves an automated HTTPS download of approximately 11MB.
Only 64-bit (x64) `node.exe` is supported.
@@ -84,7 +84,7 @@ cd /usr/ports/graphics/vips/ && make install clean
### Heroku
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 6.5MB.
This involves an automated HTTPS download of approximately 7MB.
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager.
@@ -105,6 +105,13 @@ docker pull marcbachmann/libvips
docker pull wjordan/libvips
```
[Tailor Brands](https://github.com/TailorBrands) maintain
[Debian-based Dockerfiles for libvips and nodejs](https://github.com/TailorBrands/docker-libvips).
```sh
docker pull tailor/docker-libvips
```
### AWS Lambda
In order to use sharp on AWS Lambda, you need to [create a deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html). Because sharp
@@ -199,6 +206,7 @@ Use of libraries under the terms of the LGPLv3 is via the
| Library | Used under the terms of |
|---------------|----------------------------------------------------------------------------------------------------------|
| cairo | Mozilla Public License 2.0 |
| expat | MIT Licence |
| fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) |
| freetype | [freetype Licence](http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
| giflib | MIT Licence |

View File

@@ -8,6 +8,8 @@ const is = require('./is');
* The overlay image must be the same size or smaller than the processed image.
* If both `top` and `left` options are provided, they take precedence over `gravity`.
*
* If the overlay image contains an alpha channel then composition with premultiplication will occur.
*
* @example
* sharp('input.png')
* .rotate(180)
@@ -66,10 +68,7 @@ function overlayWith (overlay, options) {
}
}
if (is.defined(options.left) || is.defined(options.top)) {
if (
is.integer(options.left) && is.inRange(options.left, 0, this.constructor.maximum.width) &&
is.integer(options.top) && is.inRange(options.top, 0, this.constructor.maximum.height)
) {
if (is.integer(options.left) && options.left >= 0 && is.integer(options.top) && options.top >= 0) {
this.options.overlayXOffset = options.left;
this.options.overlayYOffset = options.top;
} else {

View File

@@ -5,6 +5,7 @@ const util = require('util');
const stream = require('stream');
const events = require('events');
const semver = require('semver');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
// Versioning
@@ -24,12 +25,15 @@ let versions = {
} catch (err) {}
})();
// Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp');
/**
* @class Sharp
*
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
*
* JPEG, PNG or WebP format image data can be streamed out from this object.
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event.
*
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
@@ -56,7 +60,7 @@ let versions = {
*
* @example
* // Create a blank 300x200 PNG image of semi-transluent red pixels
* sharp(null, {
* sharp({
* create: {
* width: 300,
* height: 200,
@@ -71,7 +75,7 @@ let versions = {
* @param {(Buffer|String)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when null or undefined.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Number} [options.density=72] - integral number representing the DPI for vector images.
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
@@ -87,6 +91,9 @@ let versions = {
* @throws {Error} Invalid parameters
*/
const Sharp = function (input, options) {
if (arguments.length === 1 && !is.defined(input)) {
throw new Error('Invalid input');
}
if (!(this instanceof Sharp)) {
return new Sharp(input, options);
}
@@ -94,7 +101,7 @@ const Sharp = function (input, options) {
this.options = {
// input options
sequentialRead: false,
limitInputPixels: maximum.pixels,
limitInputPixels: Math.pow(0x3FFF, 2),
// ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options
@@ -110,6 +117,7 @@ const Sharp = function (input, options) {
height: -1,
canvas: 'crop',
crop: 0,
useExifOrientation: false,
angle: 0,
rotateBeforePreExtract: false,
flip: false,
@@ -171,8 +179,11 @@ const Sharp = function (input, options) {
tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'none',
tiffSquash: false,
tileSize: 256,
tileOverlap: 0,
// Function to notify of libvips warnings
debuglog: debuglog,
// Function to notify of queue length changes
queueListener: function (queueLength) {
queue.emit('change', queueLength);
@@ -183,18 +194,6 @@ const Sharp = function (input, options) {
};
util.inherits(Sharp, stream.Duplex);
/**
* Pixel limits.
* @member
* @private
*/
const maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
Sharp.maximum = maximum;
/**
* An EventEmitter that emits a `change` event when a task is either:
* - queued, waiting for _libuv_ to provide a worker thread
@@ -211,7 +210,7 @@ Sharp.queue = queue;
/**
* An Object containing nested boolean values representing the available input and output formats/methods.
* @example
* console.log(sharp.format());
* console.log(sharp.format);
* @returns {Object}
*/
Sharp.format = sharp.format();

View File

@@ -1,6 +1,5 @@
'use strict';
const util = require('util');
const color = require('color');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
@@ -17,6 +16,9 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.buffer(input)) {
// Buffer
inputDescriptor.buffer = input;
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream
inputDescriptor.buffer = [];
@@ -36,8 +38,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
if (is.defined(inputOptions.raw)) {
if (
is.object(inputOptions.raw) &&
is.integer(inputOptions.raw.width) && is.inRange(inputOptions.raw.width, 1, this.constructor.maximum.width) &&
is.integer(inputOptions.raw.height) && is.inRange(inputOptions.raw.height, 1, this.constructor.maximum.height) &&
is.integer(inputOptions.raw.width) && inputOptions.raw.width > 0 &&
is.integer(inputOptions.raw.height) && inputOptions.raw.height > 0 &&
is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4)
) {
inputDescriptor.rawWidth = inputOptions.raw.width;
@@ -51,8 +53,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
if (is.defined(inputOptions.create)) {
if (
is.object(inputOptions.create) &&
is.integer(inputOptions.create.width) && is.inRange(inputOptions.create.width, 1, this.constructor.maximum.width) &&
is.integer(inputOptions.create.height) && is.inRange(inputOptions.create.height, 1, this.constructor.maximum.height) &&
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
is.defined(inputOptions.create.background)
) {
@@ -143,7 +145,7 @@ function clone () {
const that = this;
// Clone existing options
const clone = this.constructor.call();
util._extend(clone.options, this.options);
clone.options = Object.assign({}, this.options);
// Pass 'finish' event to clone for Stream-based input
this.on('finish', function () {
// Clone inherits input data
@@ -155,14 +157,15 @@ function clone () {
}
/**
* Fast access to image metadata without decoding any compressed image data.
* Fast access to (uncached) image metadata without decoding any compressed image data.
* A Promises/A+ promise is returned when `callback` is not provided.
*
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* - `width`: Number of pixels wide
* - `height`: Number of pixels high
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
* - `density`: Number of pixels per inch (DPI), if present
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
@@ -240,12 +243,12 @@ function limitInputPixels (limit) {
if (limit === false) {
limit = 0;
} else if (limit === true) {
limit = this.constructor.maximum.pixels;
limit = Math.pow(0x3FFF, 2);
}
if (is.integer(limit) && limit >= 0) {
this.options.limitInputPixels = limit;
} else {
throw new Error('Invalid pixel limit (0 to ' + this.constructor.maximum.pixels + ') ' + limit);
throw is.invalidParameterError('limitInputPixels', 'integer', limit);
}
return this;
}

View File

@@ -16,6 +16,14 @@ const object = function (val) {
return typeof val === 'object';
};
/**
* Is this value a plain object?
* @private
*/
const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]';
};
/**
* Is this value a function?
* @private
@@ -98,6 +106,7 @@ const invalidParameterError = function (name, expected, actual) {
module.exports = {
defined: defined,
object: object,
plainObject: plainObject,
fn: fn,
bool: bool,
buffer: buffer,

View File

@@ -6,7 +6,10 @@ const is = require('./is');
* Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag.
*
* Use this method without angle to determine the angle from EXIF data.
* If an angle is provided, it is converted to a valid 90/180/270deg rotation.
* For example, `-450` will produce a 270deg rotation.
*
* If no angle is provided, it is determined from the EXIF data.
* Mirroring is supported and may infer the use of a flip operation.
*
* The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
@@ -25,17 +28,17 @@ const is = require('./is');
* });
* readableStream.pipe(pipeline);
*
* @param {Number} [angle=auto] 0, 90, 180 or 270.
* @param {Number} [angle=auto] angle of rotation, must be a multiple of 90.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function rotate (angle) {
if (!is.defined(angle)) {
this.options.angle = -1;
} else if (is.integer(angle) && is.inArray(angle, [0, 90, 180, 270])) {
this.options.useExifOrientation = true;
} else if (is.integer(angle) && !(angle % 90)) {
this.options.angle = angle;
} else {
throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle);
throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + angle);
}
return this;
}
@@ -81,7 +84,7 @@ function extract (options) {
}
}, this);
// Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && this.options.angle !== 0) {
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true)) {
this.options.rotateBeforePreExtract = true;
}
return this;

View File

@@ -1,6 +1,5 @@
'use strict';
const util = require('util');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
@@ -15,7 +14,8 @@ const sharp = require('../build/Release/sharp.node');
*
* @param {String} fileOut - the path to write the image data to.
* @param {Function} [callback] - called on completion with two arguments `(err, info)`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used).
* @returns {Promise<Object>} - when no callback is provided
* @throws {Error} Invalid parameters
*/
@@ -45,13 +45,14 @@ function toFile (fileOut, callback) {
/**
* Write output to a Buffer.
* JPEG, PNG, WebP, and RAW output are supported.
* JPEG, PNG, WebP, TIFF and RAW output are supported.
* By default, the format will match the input image, except GIF and SVG input which become PNG output.
*
* `callback`, if present, gets three arguments `(err, data, info)` where:
* - `err` is an error, if any.
* - `data` is the output image data.
* - `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`.
* - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used).
* A Promise is returned when `callback` is not provided.
*
* @param {Object} [options]
@@ -213,6 +214,7 @@ function webp (options) {
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg
* @param {Boolean} [options.predictor='none'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -224,6 +226,13 @@ function tiff (options) {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
}
}
if (is.object(options) && is.defined(options.squash)) {
if (is.bool(options.squash)) {
this.options.tiffSquash = options.squash;
} else {
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
}
}
// compression
if (is.defined(options) && is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'none'])) {
@@ -264,6 +273,7 @@ function toFormat (format, options) {
if (is.object(format) && is.string(format.id)) {
format = format.id;
}
if (format === 'jpg') format = 'jpeg';
if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) {
throw new Error('Unsupported output format ' + format);
}
@@ -482,66 +492,6 @@ function _pipeline (callback) {
}
}
// Deprecated output options
/* istanbul ignore next */
const quality = util.deprecate(function (quality) {
const formatOut = this.options.formatOut;
const options = { quality: quality };
this.jpeg(options).webp(options).tiff(options);
this.options.formatOut = formatOut;
return this;
}, 'quality: use jpeg({ quality: ... }), webp({ quality: ... }) and/or tiff({ quality: ... }) instead');
/* istanbul ignore next */
const progressive = util.deprecate(function (progressive) {
const formatOut = this.options.formatOut;
const options = { progressive: (progressive !== false) };
this.jpeg(options).png(options);
this.options.formatOut = formatOut;
return this;
}, 'progressive: use jpeg({ progressive: ... }) and/or png({ progressive: ... }) instead');
/* istanbul ignore next */
const compressionLevel = util.deprecate(function (compressionLevel) {
const formatOut = this.options.formatOut;
this.png({ compressionLevel: compressionLevel });
this.options.formatOut = formatOut;
return this;
}, 'compressionLevel: use png({ compressionLevel: ... }) instead');
/* istanbul ignore next */
const withoutAdaptiveFiltering = util.deprecate(function (withoutAdaptiveFiltering) {
const formatOut = this.options.formatOut;
this.png({ adaptiveFiltering: (withoutAdaptiveFiltering === false) });
this.options.formatOut = formatOut;
return this;
}, 'withoutAdaptiveFiltering: use png({ adaptiveFiltering: ... }) instead');
/* istanbul ignore next */
const withoutChromaSubsampling = util.deprecate(function (withoutChromaSubsampling) {
const formatOut = this.options.formatOut;
this.jpeg({ chromaSubsampling: (withoutChromaSubsampling === false) ? '4:2:0' : '4:4:4' });
this.options.formatOut = formatOut;
return this;
}, 'withoutChromaSubsampling: use jpeg({ chromaSubsampling: "4:4:4" }) instead');
/* istanbul ignore next */
const trellisQuantisation = util.deprecate(function (trellisQuantisation) {
const formatOut = this.options.formatOut;
this.jpeg({ trellisQuantisation: (trellisQuantisation !== false) });
this.options.formatOut = formatOut;
return this;
}, 'trellisQuantisation: use jpeg({ trellisQuantisation: ... }) instead');
/* istanbul ignore next */
const overshootDeringing = util.deprecate(function (overshootDeringing) {
const formatOut = this.options.formatOut;
this.jpeg({ overshootDeringing: (overshootDeringing !== false) });
this.options.formatOut = formatOut;
return this;
}, 'overshootDeringing: use jpeg({ overshootDeringing: ... }) instead');
/* istanbul ignore next */
const optimiseScans = util.deprecate(function (optimiseScans) {
const formatOut = this.options.formatOut;
this.jpeg({ optimiseScans: (optimiseScans !== false) });
this.options.formatOut = formatOut;
return this;
}, 'optimiseScans: use jpeg({ optimiseScans: ... }) instead');
/**
* Decorate the Sharp prototype with output-related functions.
* @private
@@ -567,15 +517,4 @@ module.exports = function (Sharp) {
].forEach(function (f) {
Sharp.prototype[f.name] = f;
});
// Deprecated
Sharp.prototype.quality = quality;
Sharp.prototype.progressive = progressive;
Sharp.prototype.compressionLevel = compressionLevel;
Sharp.prototype.withoutAdaptiveFiltering = withoutAdaptiveFiltering;
Sharp.prototype.withoutChromaSubsampling = withoutChromaSubsampling;
Sharp.prototype.trellisQuantisation = trellisQuantisation;
Sharp.prototype.trellisQuantization = trellisQuantisation;
Sharp.prototype.overshootDeringing = overshootDeringing;
Sharp.prototype.optimiseScans = optimiseScans;
Sharp.prototype.optimizeScans = optimiseScans;
};

View File

@@ -91,8 +91,8 @@ const interpolator = {
* // of the image data in inputBuffer
* });
*
* @param {Number} [width] - 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.
* @param {Number} [height] - pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {Number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Object} [options]
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
* @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement.
@@ -103,19 +103,19 @@ const interpolator = {
*/
function resize (width, height, options) {
if (is.defined(width)) {
if (is.integer(width) && is.inRange(width, 1, this.constructor.maximum.width)) {
if (is.integer(width) && width > 0) {
this.options.width = width;
} else {
throw is.invalidParameterError('width', `integer between 1 and ${this.constructor.maximum.width}`, width);
throw is.invalidParameterError('width', 'positive integer', width);
}
} else {
this.options.width = -1;
}
if (is.defined(height)) {
if (is.integer(height) && is.inRange(height, 1, this.constructor.maximum.height)) {
if (is.integer(height) && height > 0) {
this.options.height = height;
} else {
throw is.invalidParameterError('height', `integer between 1 and ${this.constructor.maximum.height}`, height);
throw is.invalidParameterError('height', 'positive integer', height);
}
} else {
this.options.height = -1;

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.17.3",
"version": "0.18.0",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -34,11 +34,14 @@
"Jérémy Lal <kapouer@melix.org>",
"Rahul Nanwani <r.nanwani@gmail.com>",
"Alice Monday <alice0meta@gmail.com>",
"Kristo Jorgenson <kristo.jorgenson@gmail.com>"
"Kristo Jorgenson <kristo.jorgenson@gmail.com>",
"YvesBos <yves_bos@outlook.com>",
"Guy Maliar <guy@tailorbrands.com>",
"Nicolas Coden <nicolas@ncoden.fr>"
],
"scripts": {
"clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*",
"test": "semistandard && cc && cross-env VIPS_WARNING=0 nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test": "semistandard && cc && nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-leak": "./test/leak/leak.sh",
"test-packaging": "./packaging/test-linux-x64.sh",
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md lib/$m.js >docs/api-$m.md; done"
@@ -66,28 +69,26 @@
"dependencies": {
"caw": "^2.0.0",
"color": "^1.0.3",
"got": "^6.7.1",
"nan": "^2.5.1",
"got": "^7.0.0",
"nan": "^2.6.2",
"semver": "^5.3.0",
"tar": "^2.2.1"
"tar": "^3.1.3"
},
"devDependencies": {
"async": "^2.2.0",
"bufferutil": "^3.0.0",
"cc": "^1.0.0",
"cross-env": "^4.0.0",
"documentation": "^4.0.0-beta.18",
"async": "^2.4.1",
"cc": "^1.0.1",
"documentation": "^4.0.0-rc.1",
"exif-reader": "^1.0.2",
"icc": "^1.0.0",
"mocha": "^3.2.0",
"nyc": "^10.2.0",
"rimraf": "^2.5.4",
"semistandard": "^10.0.0",
"mocha": "^3.4.2",
"nyc": "^10.3.2",
"rimraf": "^2.6.1",
"semistandard": "^11.0.0",
"unzip": "^0.1.11"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.4.2"
"libvips": "8.5.5"
},
"engines": {
"node": ">=4"
@@ -100,6 +101,7 @@
"cc": {
"linelength": "120",
"filter": [
"build/c++11",
"build/include",
"runtime/indentation_namespace"
]

View File

@@ -23,6 +23,11 @@ if ! type docker >/dev/null; then
exit 1
fi
# Update base images
for baseimage in debian:wheezy debian:jessie debian:stretch socialdefect/raspbian-jessie-core; do
docker pull $baseimage
done
# Windows (x64)
if [ $PLATFORM = "all" ] || [ $PLATFORM = "win32-x64" ]; then
echo "Building win32-x64..."

View File

@@ -16,27 +16,28 @@ export CFLAGS="${FLAGS}"
export CXXFLAGS="${FLAGS}"
# Dependency version numbers
VERSION_ZLIB=1.2.10
VERSION_ZLIB=1.2.11
VERSION_FFI=3.2.1
VERSION_GLIB=2.50.1
VERSION_GLIB=2.53.1
VERSION_XML2=2.9.4
VERSION_GSF=1.14.40
VERSION_GSF=1.14.41
VERSION_EXIF=0.6.21
VERSION_LCMS2=2.8
VERSION_JPEG=1.5.1
VERSION_PNG16=1.6.28
VERSION_WEBP=0.5.1
VERSION_TIFF=4.0.6
VERSION_PNG16=1.6.29
VERSION_WEBP=0.6.0
VERSION_TIFF=4.0.7
VERSION_ORC=0.4.26
VERSION_GDKPIXBUF=2.36.0
VERSION_FREETYPE=2.7
VERSION_GDKPIXBUF=2.36.6
VERSION_FREETYPE=2.8
VERSION_EXPAT=2.2.0
VERSION_FONTCONFIG=2.12.1
VERSION_HARFBUZZ=1.3.2
VERSION_HARFBUZZ=1.4.6
VERSION_PIXMAN=0.34.0
VERSION_CAIRO=1.14.6
VERSION_PANGO=1.40.3
VERSION_CROCO=0.6.11
VERSION_SVG=2.40.16
VERSION_CAIRO=1.14.8
VERSION_PANGO=1.40.5
VERSION_CROCO=0.6.12
VERSION_SVG=2.40.17
VERSION_GIF=5.1.4
# Least out-of-sync Sourceforge mirror
@@ -56,11 +57,12 @@ cd ${DEPS}/ffi
make install-strip
mkdir ${DEPS}/glib
curl -Ls https://download.gnome.org/sources/glib/2.50/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
curl -Ls https://download.gnome.org/sources/glib/2.53/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
cd ${DEPS}/glib
echo glib_cv_stack_grows=no >>glib.cache
echo glib_cv_uscore=no >>glib.cache
./configure --cache-file=glib.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal --disable-libmount
./configure --cache-file=glib.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--with-pcre=internal --disable-libmount
make install-strip
mkdir ${DEPS}/xml2
@@ -87,6 +89,9 @@ make install-strip
mkdir ${DEPS}/lcms2
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
cd ${DEPS}/lcms2
# Apply patches for lcms2 vulnerabilities reported since v2.8
VERSION_LCMS2_GIT_MASTER_SHA=$(curl -Ls https://api.github.com/repos/mm2/Little-CMS/git/refs/heads/master | jq -r '.object.sha' | head -c7)
curl -Ls https://github.com/mm2/Little-CMS/compare/lcms2.8...master.patch | patch -p1 -t || true
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking
make install-strip
@@ -106,15 +111,16 @@ make install-strip
mkdir ${DEPS}/webp
curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
cd ${DEPS}/webp
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-neon
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-neon --enable-libwebpmux
make install-strip
mkdir ${DEPS}/tiff
curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
cd ${DEPS}/tiff
# Apply patches for various libtiff security vulnerabilities reported since v4.0.6
# Apply patches for libtiff vulnerabilities reported since v4.0.7
VERSION_TIFF_GIT_MASTER_SHA=$(curl -Ls https://api.github.com/repos/vadz/libtiff/git/refs/heads/master | jq -r '.object.sha' | head -c7)
curl -Ls https://github.com/vadz/libtiff/compare/Release-v4-0-6...master.patch | patch -p1 -t || true
curl -Ls https://github.com/vadz/libtiff/compare/Release-v4-0-7...master.patch | patch -p1 -t || true
if [ -n "${CHOST}" ]; then autoreconf -fiv; fi
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-mdi --disable-pixarlog --disable-cxx
make install-strip
@@ -130,8 +136,11 @@ rm -rf liborc-test-*
mkdir ${DEPS}/gdkpixbuf
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.36/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
cd ${DEPS}/gdkpixbuf
touch gdk-pixbuf/loaders.cache
LD_LIBRARY_PATH=${TARGET}/lib \
./configure --cache-file=gdkpixbuf.cache --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-introspection --disable-modules --disable-gio-sniffing --without-libtiff --without-gdiplus --with-included-loaders=png,jpeg
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-modules --disable-gio-sniffing \
--without-libtiff --without-gdiplus --with-included-loaders=png,jpeg
make install-strip
mkdir ${DEPS}/freetype
@@ -140,10 +149,17 @@ cd ${DEPS}/freetype
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static
make install
mkdir ${DEPS}/expat
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/expat/expat/${VERSION_EXPAT}/expat-${VERSION_EXPAT}.tar.bz2 | tar xjC ${DEPS}/expat --strip-components=1
cd ${DEPS}/expat
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static
make install
mkdir ${DEPS}/fontconfig
curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
cd ${DEPS}/fontconfig
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 --sysconfdir=/etc
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--with-expat-includes=${TARGET}/include --with-expat-lib=${TARGET}/lib --sysconfdir=/etc
make install-strip
mkdir ${DEPS}/harfbuzz
@@ -191,7 +207,7 @@ cd ${DEPS}/gif
make install-strip
mkdir ${DEPS}/vips
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.4/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
curl -Ls https://github.com/jcupitt/libvips/releases/download/v${VERSION_VIPS}/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
cd ${DEPS}/vips
./configure --host=${CHOST} --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw \
@@ -212,6 +228,7 @@ echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"expat\": \"${VERSION_EXPAT}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\
@@ -221,7 +238,7 @@ echo "{\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"lcms\": \"${VERSION_LCMS2}-${VERSION_LCMS2_GIT_MASTER_SHA}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\

View File

@@ -8,14 +8,11 @@ curl -L -O https://github.com/lovell/build-win64/releases/download/v${VERSION_VI
unzip vips-dev-w64-web-${VERSION_VIPS}.zip
# Clean and zip
cd /vips/vips-dev-8.4
cd /vips/vips-dev-8.5
rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
cp bin/*.dll lib/
cp -r lib64/* lib/
# Temp patch for __declspec ordering
curl -L -o include/vips/VImage8.h https://raw.githubusercontent.com/lovell/libvips/e1aef0445bf123d2de000bc7f2ef97b9f788eea0/cplusplus/include/vips/VImage8.h
echo "Creating tarball"
tar czf /packaging/libvips-${VERSION_VIPS}-${PLATFORM}.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll
echo "Shrinking tarball"

View File

@@ -9,10 +9,10 @@ RUN \
apt-get install -y curl && \
dpkg --add-architecture arm64 && \
apt-get update && \
apt-get install -y crossbuild-essential-arm64 autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq
apt-get install -y crossbuild-essential-arm64 autoconf libtool nasm gtk-doc-tools texinfo advancecomp libglib2.0-dev jq gettext intltool autopoint
# Compiler settings
ENV \
PLATFORM=linux-armv8 \
CHOST=aarch64-linux-gnu \
FLAGS="-march=armv8-a -Os"
FLAGS="-march=armv8-a -Os -D_GLIBCXX_USE_CXX11_ABI=0"

View File

@@ -6,15 +6,14 @@ if ! type docker >/dev/null; then
exit 1
fi
version_node=6.3.0
test="npm run clean; npm install --unsafe-perm; npm test"
# Debian 7, 8
# Ubuntu 14.04
for dist in wheezy jessie trusty; do
# Ubuntu 14.04, 16.04
for dist in debian:jessie debian:stretch ubuntu:trusty ubuntu:xenial; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
docker pull $dist
if docker run -i -t --rm -v $PWD:/v $dist >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
@@ -22,35 +21,16 @@ done
# Centos 7
echo "Testing centos7..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
docker pull centos:7
if docker run -i -t --rm -v $PWD:/v centos:7 >packaging/centos7.log 2>&1 sh -c "cd /v; ./packaging/test/centos.sh; $test";
then echo "centos7 OK"
else echo "centos7 fail" && cat packaging/$dist.log
else echo "centos7 fail" && cat packaging/centos7.log
fi
# Fedora 22
echo "Testing fedora22..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "fedora22 OK"
else echo "fedora22 fail" && cat packaging/$dist.log
fi
# openSUSE 13.2
echo "Testing opensuse..."
if docker run -i -t --rm -v $PWD:/v opensuse:13.2 >packaging/opensuse.log 2>&1 /bin/sh -c "cd /v; ./packaging/test/opensuse.sh; $test";
then echo "opensuse OK"
else echo "opensuse fail" && cat packaging/opensuse.log
fi
# Archlinux 2015.06.01
# Archlinux latest
echo "Testing archlinux..."
docker pull pritunl/archlinux:latest
if docker run -i -t --rm -v $PWD:/v pritunl/archlinux:latest >packaging/archlinux.log 2>&1 sh -c "cd /v; ./packaging/test/archlinux.sh; $test";
then echo "archlinux OK"
else echo "archlinux fail" && cat packaging/archlinux.log
fi
# Alpine
echo "Testing alpine..."
if docker run -i -t --rm -v $PWD:/v -e "SHARP_TEST_WITHOUT_CACHE=0" alpine:edge >packaging/alpine.log 2>&1 sh -c "cd /v; ./packaging/test/alpine.sh; $test";
then echo "alpine OK"
else echo "alpine fail" && cat packaging/alpine.log
fi

View File

@@ -1,7 +0,0 @@
#!/bin/sh
# Install build dependencies
apk add --update make gcc g++ python nodejs
# Install libvips from aports/testing
apk add --update --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing vips-dev

4
packaging/test/centos.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
curl -sL https://rpm.nodesource.com/setup_6.x | bash -
yum install -y gcc-c++ make nodejs

View File

@@ -1,5 +1,6 @@
#!/bin/sh
# Install pkg-config on Debian/Ubuntu
apt-get update
apt-get install -y pkg-config
apt-get install -y build-essential python pkg-config curl
curl -sL https://deb.nodesource.com/setup_6.x | bash -
apt-get install -y nodejs

View File

@@ -1,7 +0,0 @@
#!/bin/sh
# Install Node.js on openSUSE 13.2
zypper addrepo http://download.opensuse.org/repositories/devel:languages:nodejs/openSUSE_13.2/devel:languages:nodejs.repo
zypper --gpg-auto-import-keys refresh
zypper --non-interactive install gcc-c++ make nodejs-devel npm
npm install -g npm

View File

@@ -1,4 +1,4 @@
FROM debian:jessie
FROM debian:stretch
MAINTAINER Lovell Fuller <npm@lovell.info>
# Create Debian-based container suitable for post-processing Windows x64 binaries

View File

@@ -16,6 +16,8 @@
#include <string>
#include <string.h>
#include <vector>
#include <queue>
#include <mutex>
#include <node.h>
#include <node_buffer.h>
@@ -345,6 +347,25 @@ namespace sharp {
image.set(VIPS_META_RESOLUTION_UNIT, "in");
}
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) {
throw vips::VError("Processed image is too large for the JPEG format");
}
} else if (imageType == ImageType::PNG) {
if (image.width() > 2147483647 || image.height() > 2147483647) {
throw vips::VError("Processed image is too large for the PNG format");
}
} else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) {
throw vips::VError("Processed image is too large for the WebP format");
}
}
}
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
@@ -354,6 +375,33 @@ namespace sharp {
}
}
/*
Temporary buffer of warnings
*/
std::queue<std::string> vipsWarnings;
std::mutex vipsWarningsMutex;
/*
Called with warnings from the glib-registered "VIPS" domain
*/
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
vipsWarnings.emplace(message);
}
/*
Pop the oldest warning message from the queue
*/
std::string VipsWarningPop() {
std::string warning;
std::lock_guard<std::mutex> lock(vipsWarningsMutex);
if (!vipsWarnings.empty()) {
warning = vipsWarnings.front();
vipsWarnings.pop();
}
return warning;
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.

View File

@@ -25,8 +25,8 @@
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 4))
#error libvips version 8.4.x required - see sharp.dimens.io/page/install
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 5))
#error libvips version 8.5.x required - see sharp.dimens.io/page/install
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -184,11 +184,26 @@ namespace sharp {
*/
void SetDensity(VImage image, const int density);
/*
Check the proposed format supports the current dimensions.
*/
void AssertImageTypeDimensions(VImage image, ImageType const imageType);
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint);
/*
Called with warnings from the glib-registered "VIPS" domain
*/
void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore);
/*
Pop the oldest warning message from the queue
*/
std::string VipsWarningPop();
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.

View File

@@ -350,7 +350,7 @@ set_property( VipsObject *object, const char *name, const GValue *value )
if( vips_object_get_argument( object, name,
&pspec, &argument_class, &argument_instance ) ) {
vips_warn( NULL, "%s", vips_error_buffer() );
g_warning( "%s", vips_error_buffer() );
vips_error_clear();
return;
}
@@ -364,7 +364,7 @@ set_property( VipsObject *object, const char *name, const GValue *value )
if( (enum_value = vips_enum_from_nick( object_class->nickname,
pspec_type, g_value_get_string( value ) )) < 0 ) {
vips_warn( NULL, "%s", vips_error_buffer() );
g_warning( "%s", vips_error_buffer() );
vips_error_clear();
return;
}
@@ -770,19 +770,19 @@ operator+( VImage a, std::vector<double> b )
}
VImage &
operator+=( VImage a, const VImage b )
operator+=( VImage &a, const VImage b )
{
return( a = a + b );
}
VImage &
operator+=( VImage a, const double b )
operator+=( VImage &a, const double b )
{
return( a = a + b );
}
VImage &
operator+=( VImage a, std::vector<double> b )
operator+=( VImage &a, std::vector<double> b )
{
return( a = a + b );
}
@@ -818,19 +818,19 @@ operator-( VImage a, std::vector<double> b )
}
VImage &
operator-=( VImage a, const VImage b )
operator-=( VImage &a, const VImage b )
{
return( a = a - b );
}
VImage &
operator-=( VImage a, const double b )
operator-=( VImage &a, const double b )
{
return( a = a - b );
}
VImage &
operator-=( VImage a, std::vector<double> b )
operator-=( VImage &a, std::vector<double> b )
{
return( a = a - b );
}
@@ -872,19 +872,19 @@ operator*( VImage a, std::vector<double> b )
}
VImage &
operator*=( VImage a, const VImage b )
operator*=( VImage &a, const VImage b )
{
return( a = a * b );
}
VImage &
operator*=( VImage a, const double b )
operator*=( VImage &a, const double b )
{
return( a = a * b );
}
VImage &
operator*=( VImage a, std::vector<double> b )
operator*=( VImage &a, std::vector<double> b )
{
return( a = a * b );
}
@@ -920,19 +920,19 @@ operator/( VImage a, std::vector<double> b )
}
VImage &
operator/=( VImage a, const VImage b )
operator/=( VImage &a, const VImage b )
{
return( a = a / b );
}
VImage &
operator/=( VImage a, const double b )
operator/=( VImage &a, const double b )
{
return( a = a / b );
}
VImage &
operator/=( VImage a, std::vector<double> b )
operator/=( VImage &a, std::vector<double> b )
{
return( a = a / b );
}
@@ -956,19 +956,19 @@ operator%( VImage a, std::vector<double> b )
}
VImage &
operator%=( VImage a, const VImage b )
operator%=( VImage &a, const VImage b )
{
return( a = a % b );
}
VImage &
operator%=( VImage a, const double b )
operator%=( VImage &a, const double b )
{
return( a = a % b );
}
VImage &
operator%=( VImage a, std::vector<double> b )
operator%=( VImage &a, std::vector<double> b )
{
return( a = a % b );
}
@@ -982,29 +982,29 @@ operator<( VImage a, VImage b )
VImage
operator<( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_MORE ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
to_vector( a ) ) );
}
VImage
operator<( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_LESS ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
to_vector( b ) ) );
}
VImage
operator<( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_MORE ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
a ) );
}
VImage
operator<( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_LESS ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
b ) );
}
VImage
@@ -1016,29 +1016,29 @@ operator<=( VImage a, VImage b )
VImage
operator<=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
to_vector( a ) ) );
}
VImage
operator<=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
to_vector( b ) ) );
}
VImage
operator<=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
a ) );
}
VImage
operator<=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
b ) );
}
VImage
@@ -1050,29 +1050,29 @@ operator>( VImage a, VImage b )
VImage
operator>( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_LESS ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
to_vector( a ) ) );
}
VImage
operator>( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_MORE ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
to_vector( b ) ) );
}
VImage
operator>( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_LESS ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESS,
a ) );
}
VImage
operator>( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_MORE ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_MORE,
b ) );
}
VImage
@@ -1084,29 +1084,29 @@ operator>=( VImage a, VImage b )
VImage
operator>=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
to_vector( a ) ) );
}
VImage
operator>=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
to_vector( b ) ) );
}
VImage
operator>=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_LESSEQ,
a ) );
}
VImage
operator>=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_MOREEQ,
b ) );
}
VImage
@@ -1118,29 +1118,29 @@ operator==( VImage a, VImage b )
VImage
operator==( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_EQUAL ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
to_vector( a ) ) );
}
VImage
operator==( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_EQUAL ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
to_vector( b ) ) );
}
VImage
operator==( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_EQUAL ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
a ) );
}
VImage
operator==( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_EQUAL ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_EQUAL,
b ) );
}
VImage
@@ -1152,29 +1152,29 @@ operator!=( VImage a, VImage b )
VImage
operator!=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
to_vector( a ) ) );
}
VImage
operator!=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
to_vector( b ) ) );
}
VImage
operator!=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
return( b.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
a ) );
}
VImage
operator!=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
return( a.relational_const( VIPS_OPERATION_RELATIONAL_NOTEQ,
b ) );
}
VImage
@@ -1186,43 +1186,43 @@ operator&( VImage a, VImage b )
VImage
operator&( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_AND ) );
return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND,
to_vector( a ) ) );
}
VImage
operator&( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_AND ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND,
to_vector( b ) ) );
}
VImage
operator&( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_AND ) );
return( b.boolean_const( VIPS_OPERATION_BOOLEAN_AND, a ) );
}
VImage
operator&( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_AND ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_AND, b ) );
}
VImage &
operator&=( VImage a, const VImage b )
operator&=( VImage &a, const VImage b )
{
return( a = a & b );
}
VImage &
operator&=( VImage a, const double b )
operator&=( VImage &a, const double b )
{
return( a = a & b );
}
VImage &
operator&=( VImage a, std::vector<double> b )
operator&=( VImage &a, std::vector<double> b )
{
return( a = a & b );
}
@@ -1236,43 +1236,45 @@ operator|( VImage a, VImage b )
VImage
operator|( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_OR ) );
return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
to_vector( a ) ) );
}
VImage
operator|( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_OR ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
to_vector( b ) ) );
}
VImage
operator|( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_OR ) );
return( b.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
a ) );
}
VImage
operator|( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_OR ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_OR,
b ) );
}
VImage &
operator|=( VImage a, const VImage b )
operator|=( VImage &a, const VImage b )
{
return( a = a | b );
}
VImage &
operator|=( VImage a, const double b )
operator|=( VImage &a, const double b )
{
return( a = a | b );
}
VImage &
operator|=( VImage a, std::vector<double> b )
operator|=( VImage &a, std::vector<double> b )
{
return( a = a | b );
}
@@ -1286,43 +1288,45 @@ operator^( VImage a, VImage b )
VImage
operator^( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_EOR ) );
return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
to_vector( a ) ) );
}
VImage
operator^( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_EOR ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
to_vector( b ) ) );
}
VImage
operator^( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_EOR ) );
return( b.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
a ) );
}
VImage
operator^( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_EOR ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_EOR,
b ) );
}
VImage &
operator^=( VImage a, const VImage b )
operator^=( VImage &a, const VImage b )
{
return( a = a ^ b );
}
VImage &
operator^=( VImage a, const double b )
operator^=( VImage &a, const double b )
{
return( a = a ^ b );
}
VImage &
operator^=( VImage a, std::vector<double> b )
operator^=( VImage &a, std::vector<double> b )
{
return( a = a ^ b );
}
@@ -1336,30 +1340,31 @@ operator<<( VImage a, VImage b )
VImage
operator<<( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_LSHIFT ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT,
to_vector( b ) ) );
}
VImage
operator<<( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_LSHIFT,
b ) );
}
VImage &
operator<<=( VImage a, const VImage b )
operator<<=( VImage &a, const VImage b )
{
return( a = a << b );
}
VImage &
operator<<=( VImage a, const double b )
operator<<=( VImage &a, const double b )
{
return( a = a << b );
}
VImage &
operator<<=( VImage a, std::vector<double> b )
operator<<=( VImage &a, std::vector<double> b )
{
return( a = a << b );
}
@@ -1373,30 +1378,31 @@ operator>>( VImage a, VImage b )
VImage
operator>>( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_RSHIFT ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT,
to_vector( b ) ) );
}
VImage
operator>>( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) );
return( a.boolean_const( VIPS_OPERATION_BOOLEAN_RSHIFT,
b ) );
}
VImage &
operator>>=( VImage a, const VImage b )
operator>>=( VImage &a, const VImage b )
{
return( a = a << b );
}
VImage &
operator>>=( VImage a, const double b )
operator>>=( VImage &a, const double b )
{
return( a = a << b );
}
VImage &
operator>>=( VImage a, std::vector<double> b )
operator>>=( VImage &a, std::vector<double> b )
{
return( a = a << b );
}

View File

@@ -1,5 +1,5 @@
// bodies for vips operations
// Thu 18 Aug 16:01:57 BST 2016
// Mon 13 Mar 13:22:17 GMT 2017
// this file is generated automatically, do not edit!
void VImage::system( char * cmd_format , VOption *options )
@@ -231,7 +231,7 @@ VImage VImage::round( VipsOperationRound round , VOption *options )
return( out );
}
VImage VImage::relational_const( std::vector<double> c , VipsOperationRelational relational , VOption *options )
VImage VImage::relational_const( VipsOperationRelational relational , std::vector<double> c , VOption *options )
{
VImage out;
@@ -239,8 +239,8 @@ VImage VImage::relational_const( std::vector<double> c , VipsOperationRelational
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "c", c ) ->
set( "relational", relational ) );
set( "relational", relational ) ->
set( "c", c ) );
return( out );
}
@@ -258,7 +258,7 @@ VImage VImage::remainder_const( std::vector<double> c , VOption *options )
return( out );
}
VImage VImage::boolean_const( std::vector<double> c , VipsOperationBoolean boolean , VOption *options )
VImage VImage::boolean_const( VipsOperationBoolean boolean , std::vector<double> c , VOption *options )
{
VImage out;
@@ -266,13 +266,13 @@ VImage VImage::boolean_const( std::vector<double> c , VipsOperationBoolean boole
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "c", c ) ->
set( "boolean", boolean ) );
set( "boolean", boolean ) ->
set( "c", c ) );
return( out );
}
VImage VImage::math2_const( std::vector<double> c , VipsOperationMath2 math2 , VOption *options )
VImage VImage::math2_const( VipsOperationMath2 math2 , std::vector<double> c , VOption *options )
{
VImage out;
@@ -280,8 +280,8 @@ VImage VImage::math2_const( std::vector<double> c , VipsOperationMath2 math2 , V
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "c", c ) ->
set( "math2", math2 ) );
set( "math2", math2 ) ->
set( "c", c ) );
return( out );
}
@@ -493,8 +493,8 @@ VImage VImage::copy( VOption *options )
call( "copy" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -505,8 +505,8 @@ VImage VImage::tilecache( VOption *options )
call( "tilecache" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -517,8 +517,8 @@ VImage VImage::linecache( VOption *options )
call( "linecache" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -529,8 +529,8 @@ VImage VImage::sequential( VOption *options )
call( "sequential" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -541,8 +541,8 @@ VImage VImage::cache( VOption *options )
call( "cache" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -569,8 +569,8 @@ VImage VImage::flip( VipsDirection direction , VOption *options )
call( "flip" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "direction", direction ) );
return( out );
@@ -633,6 +633,20 @@ VImage VImage::extract_area( int left , int top , int width , int height , VOpti
return( out );
}
VImage VImage::smartcrop( int width , int height , VOption *options )
{
VImage out;
call( "smartcrop" ,
(options ? options : VImage::option()) ->
set( "input", *this ) ->
set( "out", &out ) ->
set( "width", width ) ->
set( "height", height ) );
return( out );
}
VImage VImage::extract_band( int band , VOption *options )
{
VImage out;
@@ -728,8 +742,8 @@ VImage VImage::cast( VipsBandFormat format , VOption *options )
call( "cast" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "format", format ) );
return( out );
@@ -741,8 +755,8 @@ VImage VImage::rot( VipsAngle angle , VOption *options )
call( "rot" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "angle", angle ) );
return( out );
@@ -754,8 +768,8 @@ VImage VImage::rot45( VOption *options )
call( "rot45" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -766,8 +780,8 @@ VImage VImage::autorot( VOption *options )
call( "autorot" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -805,8 +819,8 @@ VImage VImage::bandfold( VOption *options )
call( "bandfold" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -817,8 +831,8 @@ VImage VImage::bandunfold( VOption *options )
call( "bandunfold" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -829,8 +843,8 @@ VImage VImage::flatten( VOption *options )
call( "flatten" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -841,8 +855,8 @@ VImage VImage::premultiply( VOption *options )
call( "premultiply" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -853,8 +867,8 @@ VImage VImage::unpremultiply( VOption *options )
call( "unpremultiply" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -865,8 +879,8 @@ VImage VImage::grid( int tile_height , int across , int down , VOption *options
call( "grid" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "tile-height", tile_height ) ->
set( "across", across ) ->
set( "down", down ) );
@@ -880,8 +894,8 @@ VImage VImage::scale( VOption *options )
call( "scale" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -892,8 +906,8 @@ VImage VImage::wrap( VOption *options )
call( "wrap" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -944,8 +958,8 @@ VImage VImage::byteswap( VOption *options )
call( "byteswap" ,
(options ? options : VImage::option()) ->
set( "out", &out ) ->
set( "in", *this ) );
set( "in", *this ) ->
set( "out", &out ) );
return( out );
}
@@ -1757,6 +1771,18 @@ void VImage::dzsave( char * filename , VOption *options )
set( "filename", filename ) );
}
VipsBlob * VImage::dzsave_buffer( VOption *options )
{
VipsBlob * buffer;
call( "dzsave_buffer" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::pngsave( char * filename , VOption *options )
{
call( "pngsave" ,
@@ -1832,6 +1858,18 @@ void VImage::tiffsave( char * filename , VOption *options )
set( "filename", filename ) );
}
VipsBlob * VImage::tiffsave_buffer( VOption *options )
{
VipsBlob * buffer;
call( "tiffsave_buffer" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::fitssave( char * filename , VOption *options )
{
call( "fitssave" ,
@@ -1840,6 +1878,32 @@ void VImage::fitssave( char * filename , VOption *options )
set( "filename", filename ) );
}
VImage VImage::thumbnail( char * filename , int width , VOption *options )
{
VImage out;
call( "thumbnail" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) ->
set( "width", width ) );
return( out );
}
VImage VImage::thumbnail_buffer( VipsBlob * buffer , int width , VOption *options )
{
VImage out;
call( "thumbnail_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) ->
set( "width", width ) );
return( out );
}
VImage VImage::mapim( VImage index , VOption *options )
{
VImage out;

View File

@@ -25,9 +25,10 @@
class MetadataWorker : public Nan::AsyncWorker {
public:
MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton,
std::vector<v8::Local<v8::Object>> const buffersToPersist)
: Nan::AsyncWorker(callback), baton(baton), buffersToPersist(buffersToPersist) {
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback), baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
@@ -56,6 +57,7 @@ class MetadataWorker : public Nan::AsyncWorker {
baton->height = image.height();
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
baton->channels = image.bands();
baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format());
if (sharp::HasDensity(image)) {
baton->density = sharp::GetDensity(image);
}
@@ -102,6 +104,7 @@ class MetadataWorker : public Nan::AsyncWorker {
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
Set(info, New("depth").ToLocalChecked(), New<v8::String>(baton->depth).ToLocalChecked());
if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
}
@@ -132,12 +135,21 @@ class MetadataWorker : public Nan::AsyncWorker {
delete baton->input;
delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message);
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv);
}
private:
MetadataBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
};
@@ -155,9 +167,12 @@ NAN_METHOD(metadata) {
// Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist);
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog"));
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, buffersToPersist));
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist));
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);

View File

@@ -29,6 +29,7 @@ struct MetadataBaton {
int height;
std::string space;
int channels;
std::string depth;
int density;
bool hasProfile;
bool hasAlpha;

View File

@@ -29,67 +29,32 @@ using vips::VError;
namespace sharp {
/*
Alpha composite src over dst with given gravity.
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
Composite overlayImage over image at given position
Assumes alpha channels are already premultiplied and will be unpremultiplied after
*/
VImage Composite(VImage src, VImage dst, const int gravity) {
if (IsInputValidForComposition(src, dst)) {
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
VImage Composite(VImage image, VImage overlayImage, int const left, int const top) {
if (HasAlpha(overlayImage)) {
// Alpha composite
if (overlayImage.width() < image.width() || overlayImage.height() < image.height()) {
// Enlarge overlay
std::vector<double> const background { 0.0, 0.0, 0.0, 0.0 };
overlayImage = overlayImage.embed(left, top, image.width(), image.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
}
return CompositeImage(src, dst);
}
// If the input was not valid for composition the return the input image itself
return dst;
}
VImage Composite(VImage src, VImage dst, const int x, const int y) {
if (IsInputValidForComposition(src, dst)) {
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), x, y);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
return AlphaComposite(image, overlayImage);
} else {
if (HasAlpha(image)) {
// Add alpha channel to overlayImage so channels match
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
overlayImage = overlayImage.bandjoin(
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
}
return CompositeImage(src, dst);
return image.insert(overlayImage, left, top);
}
// If the input was not valid for composition the return the input image itself
return dst;
}
bool IsInputValidForComposition(VImage src, VImage dst) {
using sharp::CalculateCrop;
using sharp::HasAlpha;
if (!HasAlpha(src)) {
throw VError("Overlay image must have an alpha channel");
}
if (!HasAlpha(dst)) {
throw VError("Image to be overlaid must have an alpha channel");
}
if (src.width() > dst.width() || src.height() > dst.height()) {
throw VError("Overlay image must have same dimensions or smaller");
}
return true;
}
VImage CompositeImage(VImage src, VImage dst) {
VImage AlphaComposite(VImage dst, VImage src) {
// Split src into non-alpha and alpha channels
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
@@ -302,118 +267,6 @@ namespace sharp {
}
}
/*
Calculate the Shannon entropy
*/
double EntropyStrategy::operator()(VImage image) {
return image.hist_find().hist_entropy();
}
/*
Calculate the intensity of edges, skin tone and saturation
*/
double AttentionStrategy::operator()(VImage image) {
// Flatten RGBA onto a mid-grey background
if (image.bands() == 4 && HasAlpha(image)) {
double const midgrey = sharp::Is16Bit(image.interpretation()) ? 32768.0 : 128.0;
std::vector<double> background { midgrey, midgrey, midgrey };
image = image.flatten(VImage::option()->set("background", background));
}
// Convert to LAB colourspace
VImage lab = image.colourspace(VIPS_INTERPRETATION_LAB);
VImage l = lab[0];
VImage a = lab[1];
VImage b = lab[2];
// Edge detect luminosity with the Sobel operator
VImage sobel = vips::VImage::new_matrixv(3, 3,
-1.0, 0.0, 1.0,
-2.0, 0.0, 2.0,
-1.0, 0.0, 1.0);
VImage edges = l.conv(sobel).abs() + l.conv(sobel.rot90()).abs();
// Skin tone chroma thresholds trained with http://humanae.tumblr.com/
VImage skin = (a >= 3) & (a <= 22) & (b >= 4) & (b <= 31);
// Chroma >~50% saturation
VImage lch = lab.colourspace(VIPS_INTERPRETATION_LCH);
VImage c = lch[1];
VImage saturation = c > 60;
// Find maximum in combined saliency mask
VImage mask = edges + skin + saturation;
return mask.max();
}
/*
Calculate crop area based on image entropy
*/
std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy
) {
int left = 0;
int top = 0;
int const inWidth = image.width();
int const inHeight = image.height();
if (inWidth > outWidth) {
// Reduce width by repeated removing slices from edge with lowest score
int width = inWidth;
double leftScore = 0.0;
double rightScore = 0.0;
// Max width of each slice
int const maxSliceWidth = static_cast<int>(ceil((inWidth - outWidth) / 8.0));
while (width > outWidth) {
// Width of current slice
int const slice = std::min(width - outWidth, maxSliceWidth);
if (leftScore == 0.0) {
// Update score of left slice
leftScore = strategy(image.extract_area(left, 0, slice, inHeight));
}
if (rightScore == 0.0) {
// Update score of right slice
rightScore = strategy(image.extract_area(width - slice - 1, 0, slice, inHeight));
}
// Keep slice with highest score
if (leftScore >= rightScore) {
// Discard right slice
rightScore = 0.0;
} else {
// Discard left slice
leftScore = 0.0;
left = left + slice;
}
width = width - slice;
}
}
if (inHeight > outHeight) {
// Reduce height by repeated removing slices from edge with lowest score
int height = inHeight;
double topScore = 0.0;
double bottomScore = 0.0;
// Max height of each slice
int const maxSliceHeight = static_cast<int>(ceil((inHeight - outHeight) / 8.0));
while (height > outHeight) {
// Height of current slice
int const slice = std::min(height - outHeight, maxSliceHeight);
if (topScore == 0.0) {
// Update score of top slice
topScore = strategy(image.extract_area(0, top, inWidth, slice));
}
if (bottomScore == 0.0) {
// Update score of bottom slice
bottomScore = strategy(image.extract_area(0, height - slice - 1, inWidth, slice));
}
// Keep slice with highest score
if (topScore >= bottomScore) {
// Discard bottom slice
bottomScore = 0.0;
} else {
// Discard top slice
topScore = 0.0;
top = top + slice;
}
height = height - slice;
}
}
return std::make_tuple(left, top);
}
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/

View File

@@ -32,20 +32,14 @@ namespace sharp {
VImage Composite(VImage src, VImage dst, const int gravity);
/*
Alpha composite src over dst with given x and y offsets.
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
Composite overlayImage over image at given position
*/
VImage Composite(VImage src, VImage dst, const int x, const int y);
VImage Composite(VImage image, VImage overlayImage, int const x, int const y);
/*
Check if the src and dst Images for composition operation are valid
Alpha composite overlayImage over image, assumes matching dimensions
*/
bool IsInputValidForComposition(VImage src, VImage dst);
/*
Given a valid src and dst, returns the composite of the two images
*/
VImage CompositeImage(VImage src, VImage dst);
VImage AlphaComposite(VImage image, VImage overlayImage);
/*
Cutout src over dst with given gravity.
@@ -78,22 +72,6 @@ namespace sharp {
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
/*
Crop strategy functors
*/
struct EntropyStrategy {
double operator()(VImage image);
};
struct AttentionStrategy {
double operator()(VImage image);
};
/*
Calculate crop area based on given strategy (Entropy, Attention)
*/
std::tuple<int, int> Crop(
VImage image, int const outWidth, int const outHeight, std::function<double(VImage)> strategy);
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/

View File

@@ -33,9 +33,10 @@
class PipelineWorker : public Nan::AsyncWorker {
public:
PipelineWorker(
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *queueListener,
std::vector<v8::Local<v8::Object>> const buffersToPersist)
: Nan::AsyncWorker(callback), baton(baton), queueListener(queueListener), buffersToPersist(buffersToPersist) {
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback), baton(baton), debuglog(debuglog), queueListener(queueListener),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
@@ -80,16 +81,12 @@ class PipelineWorker : public Nan::AsyncWorker {
// Calculate angle of rotation
VipsAngle rotation;
bool flip;
bool flop;
std::tie(rotation, flip, flop) = CalculateRotationAndFlip(baton->angle, image);
if (flip && !baton->flip) {
// Add flip operation due to EXIF mirroring
baton->flip = TRUE;
}
if (flop && !baton->flop) {
// Add flip operation due to EXIF mirroring
baton->flop = TRUE;
if (baton->useExifOrientation) {
// Rotate and flip image according to Exif orientation
// (ignore the requested rotation and flip)
std::tie(rotation, baton->flip, baton->flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
} else {
rotation = CalculateAngleRotation(baton->angle);
}
// Rotate pre-extract
@@ -241,6 +238,12 @@ class PipelineWorker : public Nan::AsyncWorker {
shrink_on_load = 2;
}
}
// Help ensure a final kernel-based reduction to prevent shrink aliasing
if (shrink_on_load > 1 && (xresidual == 1.0 || yresidual == 1.0)) {
shrink_on_load = shrink_on_load / 2;
xfactor = xfactor * 2;
yfactor = yfactor * 2;
}
if (shrink_on_load > 1) {
// Reload input using shrink-on-load
vips::VOption *option = VImage::option()->set("shrink", shrink_on_load);
@@ -284,6 +287,13 @@ class PipelineWorker : public Nan::AsyncWorker {
std::swap(xresidual, yresidual);
}
}
// Help ensure a final kernel-based reduction to prevent shrink aliasing
if ((xshrink > 1 || yshrink > 1) && (xresidual == 1.0 || yresidual == 1.0)) {
xshrink = xshrink / 2;
yshrink = yshrink / 2;
xresidual = xresidual / 2.0;
yresidual = yresidual / 2.0;
}
// Ensure we're using a device-independent colour space
if (sharp::HasProfile(image)) {
@@ -332,12 +342,20 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.colourspace(VIPS_INTERPRETATION_B_W);
}
// Ensure image has an alpha channel when there is an overlay
bool hasOverlay = baton->overlay != nullptr;
if (hasOverlay && !HasAlpha(image)) {
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
// Ensure image has an alpha channel when there is an overlay with an alpha channel
VImage overlayImage;
ImageType overlayImageType = ImageType::UNKNOWN;
bool shouldOverlayWithAlpha = FALSE;
if (baton->overlay != nullptr) {
std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
if (HasAlpha(overlayImage)) {
shouldOverlayWithAlpha = !baton->overlayCutout;
if (!HasAlpha(image)) {
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
}
}
}
bool const shouldShrink = xshrink > 1 || yshrink > 1;
@@ -345,9 +363,8 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldBlur = baton->blurSigma != 0.0;
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
bool const shouldSharpen = baton->sharpenSigma != 0.0;
bool const shouldCutout = baton->overlayCutout;
bool const shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldShrink || shouldReduce || shouldBlur || shouldConv || shouldSharpen || (hasOverlay && !shouldCutout));
(shouldShrink || shouldReduce || shouldBlur || shouldConv || shouldSharpen || shouldOverlayWithAlpha);
// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
@@ -409,16 +426,24 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("centre", baton->centreSampling));
}
}
// Perform affine enlargement
// Perform enlargement
if (yresidual > 1.0 || xresidual > 1.0) {
vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data());
if (yresidual > 1.0) {
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator));
}
if (xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
->set("interpolate", interpolator));
if (trunc(xresidual) == xresidual && trunc(yresidual) == yresidual && baton->interpolator == "nearest") {
// Fast, integral nearest neighbour enlargement
image = image.zoom(static_cast<int>(xresidual), static_cast<int>(yresidual));
} else {
// Floating point affine transformation
vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data());
if (yresidual > 1.0 && xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator));
} else if (yresidual > 1.0) {
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator));
} else if (xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
->set("interpolate", interpolator));
}
}
}
}
@@ -493,24 +518,20 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("background", background));
} else if (baton->canvas != Canvas::IGNORE_ASPECT) {
// Crop/max/min
int left;
int top;
if (baton->crop < 9) {
// Gravity-based crop
int left;
int top;
std::tie(left, top) = sharp::CalculateCrop(
image.width(), image.height(), baton->width, baton->height, baton->crop);
} else if (baton->crop == 16) {
// Entropy-based crop
std::tie(left, top) = sharp::Crop(image, baton->width, baton->height, sharp::EntropyStrategy());
int width = std::min(image.width(), baton->width);
int height = std::min(image.height(), baton->height);
image = image.extract_area(left, top, width, height);
} else {
// Attention-based crop
std::tie(left, top) = sharp::Crop(image, baton->width, baton->height, sharp::AttentionStrategy());
// Attention-based or Entropy-based crop
image = image.smartcrop(baton->width, baton->height, VImage::option()
->set("interesting", baton->crop == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION));
}
int width = std::min(image.width(), baton->width);
int height = std::min(image.height(), baton->height);
image = image.extract_area(left, top, width, height);
baton->cropCalcLeft = left;
baton->cropCalcTop = top;
}
}
@@ -583,10 +604,11 @@ class PipelineWorker : public Nan::AsyncWorker {
}
// Composite with overlay, if present
if (hasOverlay) {
VImage overlayImage;
ImageType overlayImageType = ImageType::UNKNOWN;
std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
if (baton->overlay != nullptr) {
// Verify overlay image is within current dimensions
if (overlayImage.width() > image.width() || overlayImage.height() > image.height()) {
throw vips::VError("Overlay image must have same dimensions or smaller");
}
// Check if overlay is tiled
if (baton->overlayTile) {
int const overlayImageWidth = overlayImage.width();
@@ -619,31 +641,34 @@ class PipelineWorker : public Nan::AsyncWorker {
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
baton->overlayGravity = 0;
}
if (shouldCutout) {
if (baton->overlayCutout) {
// 'cut out' the image, premultiplication is not required
image = sharp::Cutout(overlayImage, image, baton->overlayGravity);
} else {
// Ensure overlay has alpha channel
if (!HasAlpha(overlayImage)) {
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
overlayImage = overlayImage.bandjoin(
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
// Ensure overlay is sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB);
// Ensure overlay matches premultiplication state
if (shouldPremultiplyAlpha) {
// Ensure overlay has alpha channel
if (!HasAlpha(overlayImage)) {
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
overlayImage = overlayImage.bandjoin(
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
}
overlayImage = overlayImage.premultiply();
}
// Ensure image has alpha channel
if (!HasAlpha(image)) {
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
}
// Ensure overlay is premultiplied sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
int left;
int top;
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
// Composite images with given offsets
image = sharp::Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset);
// Composite images at given offsets
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
overlayImage.width(), overlayImage.height(), baton->overlayXOffset, baton->overlayYOffset);
} else {
// Composite images with given gravity
image = sharp::Composite(overlayImage, image, baton->overlayGravity);
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
overlayImage.width(), overlayImage.height(), baton->overlayGravity);
}
image = sharp::Composite(image, overlayImage, left, top);
}
}
@@ -657,6 +682,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.cast(VIPS_FORMAT_UCHAR);
}
}
baton->premultiplied = shouldPremultiplyAlpha;
// Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3) {
@@ -713,10 +739,11 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->width = image.width();
baton->height = image.height();
// Output
if (baton->fileOut == "") {
if (baton->fileOut.empty()) {
// Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality)
@@ -738,11 +765,12 @@ class PipelineWorker : public Nan::AsyncWorker {
}
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
// Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
// Strip profile
if (!baton->withMetadata) {
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
}
// Write PNG to buffer
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("interlace", baton->pngProgressive)
->set("compression", baton->pngCompressionLevel)
@@ -754,6 +782,7 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) {
// Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality)
@@ -765,6 +794,27 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) {
// Write TIFF to buffer
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
}
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
image = image.cast(VIPS_FORMAT_FLOAT);
}
VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) {
// Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
@@ -805,6 +855,7 @@ class PipelineWorker : public Nan::AsyncWorker {
!(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality)
@@ -818,11 +869,12 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || isPng || (matchInput &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
// Write PNG to file
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
// Strip profile
if (!baton->withMetadata) {
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
}
// Write PNG to file
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("interlace", baton->pngProgressive)
->set("compression", baton->pngCompressionLevel)
@@ -830,6 +882,7 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "png";
} else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) {
// Write WEBP to file
AssertImageTypeDimensions(image, ImageType::WEBP);
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality)
@@ -838,16 +891,20 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) {
// Write TIFF to file
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
}
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
image = image.cast(VIPS_FORMAT_FLOAT);
}
// Write TIFF to file
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) );
->set("predictor", baton->tiffPredictor));
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -940,6 +997,7 @@ class PipelineWorker : public Nan::AsyncWorker {
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width)));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height)));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels)));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied));
if (baton->cropCalcLeft != -1 && baton->cropCalcLeft != -1) {
Set(info, New("cropCalcLeft").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcLeft)));
Set(info, New("cropCalcTop").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcTop)));
@@ -978,6 +1036,14 @@ class PipelineWorker : public Nan::AsyncWorker {
});
delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message);
warning = sharp::VipsWarningPop();
}
// Decrement processing task counter
g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) };
@@ -990,43 +1056,48 @@ class PipelineWorker : public Nan::AsyncWorker {
private:
PipelineBaton *baton;
Nan::Callback *debuglog;
Nan::Callback *queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist;
/*
Calculate the angle of rotation and need-to-flip for the output image.
In order of priority:
1. Use explicitly requested angle (supports 90, 180, 270)
2. Use input image EXIF Orientation header - supports mirroring
3. Otherwise default to zero, i.e. no rotation
Calculate the angle of rotation and need-to-flip for the given Exif orientation
By default, returns zero, i.e. no rotation.
*/
std::tuple<VipsAngle, bool, bool>
CalculateRotationAndFlip(int const angle, vips::VImage image) {
CalculateExifRotationAndFlip(int const exifOrientation) {
VipsAngle rotate = VIPS_ANGLE_D0;
bool flip = FALSE;
bool flop = FALSE;
if (angle == -1) {
switch (sharp::ExifOrientation(image)) {
case 6: rotate = VIPS_ANGLE_D90; break;
case 3: rotate = VIPS_ANGLE_D180; break;
case 8: rotate = VIPS_ANGLE_D270; break;
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
}
} else {
if (angle == 90) {
rotate = VIPS_ANGLE_D90;
} else if (angle == 180) {
rotate = VIPS_ANGLE_D180;
} else if (angle == 270) {
rotate = VIPS_ANGLE_D270;
}
switch (exifOrientation) {
case 6: rotate = VIPS_ANGLE_D90; break;
case 3: rotate = VIPS_ANGLE_D180; break;
case 8: rotate = VIPS_ANGLE_D270; break;
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
}
return std::make_tuple(rotate, flip, flop);
}
/*
Calculate the rotation for the given angle.
Supports any positive or negative angle that is a multiple of 90.
*/
VipsAngle
CalculateAngleRotation(int angle) {
angle = angle % 360;
if (angle < 0)
angle = 360 + angle;
switch (angle) {
case 90: return VIPS_ANGLE_D90;
case 180: return VIPS_ANGLE_D180;
case 270: return VIPS_ANGLE_D270;
}
return VIPS_ANGLE_D0;
}
/*
Assemble the suffix argument to dzsave, which is the format (by extname)
alongisde comma-separated arguments to the corresponding `formatsave` vips
@@ -1151,6 +1222,7 @@ NAN_METHOD(pipeline) {
baton->gamma = AttrTo<double>(options, "gamma");
baton->greyscale = AttrTo<bool>(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip");
@@ -1204,6 +1276,7 @@ NAN_METHOD(pipeline) {
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
// tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
@@ -1237,12 +1310,15 @@ NAN_METHOD(pipeline) {
baton->accessMethod = VIPS_ACCESS_RANDOM;
}
// Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(AttrAs<v8::Function>(options, "debuglog"));
// Function to notify of queue length changes
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener"));
// Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, buffersToPersist));
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist));
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);

View File

@@ -64,6 +64,7 @@ struct PipelineBaton {
int crop;
int cropCalcLeft;
int cropCalcTop;
bool premultiplied;
std::string kernel;
std::string interpolator;
bool centreSampling;
@@ -80,6 +81,7 @@ struct PipelineBaton {
double gamma;
bool greyscale;
bool normalise;
bool useExifOrientation;
int angle;
bool rotateBeforePreExtract;
bool flip;
@@ -106,6 +108,7 @@ struct PipelineBaton {
int tiffQuality;
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
bool tiffSquash;
std::string err;
bool withMetadata;
int withMetadataOrientation;
@@ -142,6 +145,7 @@ struct PipelineBaton {
crop(0),
cropCalcLeft(-1),
cropCalcTop(-1),
premultiplied(false),
centreSampling(false),
flatten(false),
negate(false),
@@ -155,6 +159,7 @@ struct PipelineBaton {
gamma(0.0),
greyscale(false),
normalise(false),
useExifOrientation(false),
angle(0),
flip(false),
flop(false),
@@ -176,6 +181,7 @@ struct PipelineBaton {
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE),
tiffSquash(false),
withMetadata(false),
withMetadataOrientation(-1),
convKernelWidth(0),

View File

@@ -24,6 +24,9 @@
NAN_MODULE_INIT(init) {
vips_init("sharp");
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());

View File

@@ -5,7 +5,7 @@
"author": "Lovell Fuller <npm@lovell.info>",
"description": "Benchmark and performance tests for sharp",
"scripts": {
"test": "VIPS_WARNING=0 node perf && node random && node parallel"
"test": "node perf && node random && node parallel"
},
"devDependencies": {
"async": "^2.1.4",
@@ -13,9 +13,10 @@
"gm": "^1.23.0",
"imagemagick": "^0.1.3",
"imagemagick-native": "^1.9.3",
"images": "^3.0.0",
"jimp": "^0.2.27",
"lwip": "^0.0.9",
"mapnik": "^3.5.14",
"mapnik": "^3.6.0",
"pajk-lwip": "^0.2.0",
"semver": "^5.3.0"
},
"license": "Apache-2.0",

View File

@@ -7,11 +7,12 @@ const assert = require('assert');
const Benchmark = require('benchmark');
// Contenders
const sharp = require('../../');
const gm = require('gm');
const imagemagick = require('imagemagick');
const mapnik = require('mapnik');
const jimp = require('jimp');
const sharp = require('../../');
const images = require('images');
let imagemagickNative;
try {
imagemagickNative = require('imagemagick-native');
@@ -20,7 +21,7 @@ try {
}
let lwip;
try {
lwip = require('lwip');
lwip = require('pajk-lwip');
} catch (err) {
console.log('Excluding lwip');
}
@@ -145,7 +146,7 @@ async.series({
}).add('mapnik-buffer-buffer', {
defer: true,
fn: function (deferred) {
mapnik.Image.fromBytes(inputJpgBuffer, function (err, img) {
mapnik.Image.fromBytes(inputJpgBuffer, { max_size: 3000 }, function (err, img) {
if (err) throw err;
img
.resize(width, height, {
@@ -266,6 +267,12 @@ async.series({
});
}
});
// images
jpegSuite.add('images-file-file', function () {
images(fixtures.inputJpg)
.resize(width, height)
.save(fixtures.outputJpg, { quality: 80 });
});
// sharp
jpegSuite.add('sharp-buffer-file', {
defer: true,
@@ -733,7 +740,7 @@ async.series({
}).add('mapnik-buffer-buffer', {
defer: true,
fn: function (deferred) {
mapnik.Image.fromBytes(inputPngBuffer, function (err, img) {
mapnik.Image.fromBytes(inputPngBuffer, { max_size: 3000 }, function (err, img) {
if (err) throw err;
img.premultiply(function (err, img) {
if (err) throw err;
@@ -819,6 +826,12 @@ async.series({
});
}
});
// images
pngSuite.add('images-file-file', function () {
images(fixtures.inputPng)
.resize(width, height)
.save(fixtures.outputPng);
});
// sharp
pngSuite.add('sharp-buffer-file', {
defer: true,
@@ -957,7 +970,7 @@ async.series({
}).add('sharp-file-buffer', {
defer: true,
fn: function (deferred) {
sharp(fixtures.inputWebp)
sharp(fixtures.inputWebP)
.resize(width, height)
.toBuffer(function (err, buffer) {
if (err) {

BIN
test/fixtures/320x240.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
test/fixtures/8bit_depth.tiff vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 987 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 B

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -64,6 +64,7 @@ module.exports = {
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
inputJpgLarge: getPath('giant-image.jpg'),
inputJpg320x240: getPath('320x240.jpg'), // http://www.andrewault.net/2010/01/26/create-a-test-pattern-video-with-perl/
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngWithTransparency: getPath('blackbug.png'), // public domain
@@ -85,6 +86,7 @@ module.exports = {
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg

View File

@@ -161,7 +161,7 @@ describe('Crop', function () {
describe('Entropy-based strategy', function () {
it('JPEG', function (done) {
sharp(fixtures.inputJpgWithCmykProfile)
sharp(fixtures.inputJpg)
.resize(80, 320)
.crop(sharp.strategy.entropy)
.toBuffer(function (err, data, info) {
@@ -170,9 +170,7 @@ describe('Crop', function () {
assert.strictEqual(3, info.channels);
assert.strictEqual(80, info.width);
assert.strictEqual(320, info.height);
assert.strictEqual(250, info.cropCalcLeft);
assert.strictEqual(0, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.jpg'), data, done);
fixtures.assertSimilar(fixtures.expected('crop-strategy-entropy.jpg'), data, done);
});
});
@@ -186,8 +184,6 @@ describe('Crop', function () {
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
assert.strictEqual(0, info.cropCalcLeft);
assert.strictEqual(80, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
});
});
@@ -202,8 +198,6 @@ describe('Crop', function () {
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
assert.strictEqual(0, info.cropCalcLeft);
assert.strictEqual(80, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
});
});
@@ -211,7 +205,7 @@ describe('Crop', function () {
describe('Attention strategy', function () {
it('JPEG', function (done) {
sharp(fixtures.inputJpgWithCmykProfile)
sharp(fixtures.inputJpg)
.resize(80, 320)
.crop(sharp.strategy.attention)
.toBuffer(function (err, data, info) {
@@ -220,9 +214,7 @@ describe('Crop', function () {
assert.strictEqual(3, info.channels);
assert.strictEqual(80, info.width);
assert.strictEqual(320, info.height);
assert.strictEqual(250, info.cropCalcLeft);
assert.strictEqual(0, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.jpg'), data, done);
fixtures.assertSimilar(fixtures.expected('crop-strategy-attention.jpg'), data, done);
});
});
@@ -236,8 +228,6 @@ describe('Crop', function () {
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
assert.strictEqual(0, info.cropCalcLeft);
assert.strictEqual(80, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
});
});
@@ -252,8 +242,6 @@ describe('Crop', function () {
assert.strictEqual(4, info.channels);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
assert.strictEqual(0, info.cropCalcLeft);
assert.strictEqual(80, info.cropCalcTop);
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
});
});

View File

@@ -35,17 +35,54 @@ describe('Interpolators and kernels', function () {
sharp.interpolator.locallyBoundedBicubic,
sharp.interpolator.vertexSplitQuadraticBasisSpline
].forEach(function (interpolator) {
it(interpolator, function (done) {
sharp(fixtures.inputJpg)
.resize(320, null, { interpolator: interpolator })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
fixtures.assertSimilar(fixtures.inputJpg, data, done);
});
describe(interpolator, function () {
it('x and y', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(200, 200, { interpolator: interpolator })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(200, info.height);
done();
});
});
it('x only', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(200, 21, { interpolator: interpolator })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(21, info.height);
done();
});
});
it('y only', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(21, 200, { interpolator: interpolator })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(21, info.width);
assert.strictEqual(200, info.height);
done();
});
});
});
});
it('nearest with integral factor', function (done) {
sharp(fixtures.inputTiff8BitDepth)
.resize(210, 210, { interpolator: 'nearest' })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(210, info.width);
assert.strictEqual(210, info.height);
done();
});
});
});
it('unknown kernel throws', function () {

View File

@@ -253,6 +253,21 @@ describe('Input/output', function () {
});
});
it('Support output to jpg format', function (done) {
sharp(fixtures.inputPng)
.resize(320, 240)
.toFormat('jpg')
.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();
});
});
it('Fail when output File is input File', function (done) {
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
assert(!!err);
@@ -288,7 +303,7 @@ describe('Input/output', function () {
});
it('Fail when input is empty Buffer', function (done) {
sharp(new Buffer(0)).toBuffer().then(function () {
sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false);
done();
}).catch(function (err) {
@@ -298,7 +313,7 @@ describe('Input/output', function () {
});
it('Fail when input is invalid Buffer', function (done) {
sharp(new Buffer([0x1, 0x2, 0x3, 0x4])).toBuffer().then(function () {
sharp(Buffer.from([0x1, 0x2, 0x3, 0x4])).toBuffer().then(function () {
assert(false);
done();
}).catch(function (err) {
@@ -308,6 +323,16 @@ describe('Input/output', function () {
});
describe('Fail for unsupported input', function () {
it('Undefined', function () {
assert.throws(function () {
sharp(undefined);
});
});
it('Null', function () {
assert.throws(function () {
sharp(null);
});
});
it('Numeric', function () {
assert.throws(function () {
sharp(1);
@@ -318,11 +343,6 @@ describe('Input/output', function () {
sharp(true);
});
});
it('Empty Object', function () {
assert.throws(function () {
sharp({});
});
});
it('Error Object', function () {
assert.throws(function () {
sharp(new Error());
@@ -837,6 +857,20 @@ describe('Input/output', function () {
});
});
it('Save TIFF to Buffer', function (done) {
sharp(fixtures.inputTiff)
.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('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Invalid WebP quality throws error', function () {
assert.throws(function () {
sharp().webp({ quality: 101 });
@@ -861,6 +895,44 @@ describe('Input/output', function () {
});
});
it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) {
const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size;
sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images
.tiff({
squash: false,
compression: 'none'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size === startSize);
fs.unlink(fixtures.outputTiff, done);
});
});
it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) {
const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size;
sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images
.tiff({
squash: true,
compression: 'none'
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size < (startSize / 2));
fs.unlink(fixtures.outputTiff, done);
});
});
it('Invalid TIFF squash value throws error', function () {
assert.throws(function () {
sharp().tiff({ squash: 'true' });
});
});
it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
@@ -876,7 +948,7 @@ describe('Input/output', function () {
});
});
it('TIFF deflate compression with hoizontal predictor shrinks test file', function (done) {
it('TIFF deflate compression with horizontal predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
@@ -1205,27 +1277,27 @@ describe('Input/output', function () {
describe('Raw pixel input', function () {
it('Missing options', function () {
assert.throws(function () {
sharp(null, { raw: {} });
sharp({ raw: {} });
});
});
it('Incomplete options', function () {
assert.throws(function () {
sharp(null, { raw: { width: 1, height: 1 } });
sharp({ raw: { width: 1, height: 1 } });
});
});
it('Invalid channels', function () {
assert.throws(function () {
sharp(null, { raw: { width: 1, height: 1, channels: 5 } });
sharp({ raw: { width: 1, height: 1, channels: 5 } });
});
});
it('Invalid height', function () {
assert.throws(function () {
sharp(null, { raw: { width: 1, height: 0, channels: 4 } });
sharp({ raw: { width: 1, height: 0, channels: 4 } });
});
});
it('Invalid width', function () {
assert.throws(function () {
sharp(null, { raw: { width: 'zoinks', height: 1, channels: 4 } });
sharp({ raw: { width: 'zoinks', height: 1, channels: 4 } });
});
});
it('RGB', function (done) {
@@ -1278,7 +1350,7 @@ describe('Input/output', function () {
assert.strictEqual(256, info.width);
assert.strictEqual(192, info.height);
assert.strictEqual(4, info.channels);
fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, done);
fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, { threshold: 7 }, done);
});
});
});
@@ -1292,7 +1364,7 @@ describe('Input/output', function () {
channels: 3,
background: { r: 0, g: 255, b: 0 }
};
sharp(null, { create: create })
sharp({ create: create })
.jpeg()
.toBuffer(function (err, data, info) {
if (err) throw err;
@@ -1310,7 +1382,7 @@ describe('Input/output', function () {
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 128 }
};
sharp(null, { create: create })
sharp({ create: create })
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
@@ -1329,7 +1401,7 @@ describe('Input/output', function () {
background: { r: 0, g: 0, b: 0 }
};
assert.throws(function () {
sharp(null, { create: create });
sharp({ create: create });
});
});
it('Missing background', function () {
@@ -1339,7 +1411,7 @@ describe('Input/output', function () {
channels: 3
};
assert.throws(function () {
sharp(null, { create: create });
sharp({ create: create });
});
});
});

View File

@@ -17,6 +17,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
@@ -35,6 +36,7 @@ describe('Image metadata', function () {
assert.strictEqual(600, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(72, metadata.density);
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
@@ -56,25 +58,24 @@ describe('Image metadata', function () {
});
});
if (sharp.format.tiff.input.file) {
it('TIFF', function (done) {
sharp(fixtures.inputTiff).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('tiff', metadata.format);
assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(1, metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
it('TIFF', function (done) {
sharp(fixtures.inputTiff).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('tiff', metadata.format);
assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(1, metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
}
});
it('PNG', function (done) {
sharp(fixtures.inputPng).metadata(function (err, metadata) {
@@ -84,6 +85,7 @@ describe('Image metadata', function () {
assert.strictEqual(2074, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
@@ -102,6 +104,7 @@ describe('Image metadata', function () {
assert.strictEqual(1536, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(72, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
@@ -112,80 +115,59 @@ describe('Image metadata', function () {
});
});
if (sharp.format.webp.input.file) {
it('WebP', function (done) {
sharp(fixtures.inputWebP).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('webp', metadata.format);
assert.strictEqual(1024, metadata.width);
assert.strictEqual(772, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
it('WebP', function (done) {
sharp(fixtures.inputWebP).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('webp', metadata.format);
assert.strictEqual(1024, metadata.width);
assert.strictEqual(772, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
}
});
if (sharp.format.gif.input.file) {
it('GIF via giflib', function (done) {
sharp(fixtures.inputGif).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('gif', metadata.format);
assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
it('GIF via giflib', function (done) {
sharp(fixtures.inputGif).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('gif', metadata.format);
assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
it('GIF grey+alpha via giflib', function (done) {
sharp(fixtures.inputGifGreyPlusAlpha).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('gif', metadata.format);
assert.strictEqual(2, metadata.width);
assert.strictEqual(1, metadata.height);
assert.strictEqual(2, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
it('GIF grey+alpha via giflib', function (done) {
sharp(fixtures.inputGifGreyPlusAlpha).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('gif', metadata.format);
assert.strictEqual(2, metadata.width);
assert.strictEqual(1, metadata.height);
assert.strictEqual(2, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
}
if (sharp.format.openslide.input.file) {
it('Aperio SVS via openslide', function (done) {
sharp(fixtures.inputSvs).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('openslide', metadata.format);
assert.strictEqual(2220, metadata.width);
assert.strictEqual(2967, metadata.height);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual('rgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
});
});
}
});
it('File in, Promise out', function (done) {
sharp(fixtures.inputJpg).metadata().then(function (metadata) {
@@ -194,6 +176,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
@@ -222,6 +205,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
@@ -244,6 +228,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
@@ -264,6 +249,7 @@ describe('Image metadata', function () {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);

View File

@@ -155,20 +155,22 @@ describe('Overlays', function () {
});
}
it('Composite JPEG onto PNG', function (done) {
it('Composite JPEG onto PNG, no premultiply', function (done) {
sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
.toBuffer(function (error) {
if (error) return done(error);
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(false, info.premultiplied);
done();
});
});
it('Composite opaque JPEG onto JPEG', function (done) {
it('Composite opaque JPEG onto JPEG, no premultiply', function (done) {
sharp(fixtures.inputJpg)
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
.toBuffer(function (error) {
if (error) return done(error);
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(false, info.premultiplied);
done();
});
});
@@ -561,14 +563,15 @@ describe('Overlays', function () {
sharp(fixtures.inputJpg)
.resize(2048, 1536)
.overlayWith(data, { raw: info })
.toBuffer(function (err, data) {
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, info.premultiplied);
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-rgb.jpg'), data, done);
});
});
});
it('Throws an error when called with an invalid file', function (done) {
it('Returns an error when called with an invalid file', function (done) {
sharp(fixtures.inputJpg)
.overlayWith('notfound.png')
.toBuffer(function (err) {
@@ -576,4 +579,20 @@ describe('Overlays', function () {
done();
});
});
it('Composite JPEG onto JPEG, no premultiply', function (done) {
sharp(fixtures.inputJpg)
.resize(480, 320)
.overlayWith(fixtures.inputJpgBooleanTest)
.png()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(480, info.width);
assert.strictEqual(320, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual(false, info.premultiplied);
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-jpeg.jpg'), data, done);
});
});
});

View File

@@ -1,15 +0,0 @@
'use strict';
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "(bufferutil|sharp)" }] */
describe('Require-time checks', function () {
/**
Including sharp alongside another C++ module that does not require
-stdlib=libc++ (for its C++11 features) has caused clang/llvm to
segfault due to the use of static function variables.
*/
it('Require alongside C++ module that does not use libc++', function () {
const bufferutil = require('bufferutil');
const sharp = require('../../');
});
});

View File

@@ -66,37 +66,47 @@ describe('Resize dimensions', function () {
it('Invalid width - NaN', function () {
assert.throws(function () {
sharp().resize('spoons', 240);
}, /Expected integer between 1 and 16383 for width but received spoons of type string/);
}, /Expected positive integer for width but received spoons of type string/);
});
it('Invalid height - NaN', function () {
assert.throws(function () {
sharp().resize(320, 'spoons');
}, /Expected integer between 1 and 16383 for height but received spoons of type string/);
}, /Expected positive integer for height but received spoons of type string/);
});
it('Invalid width - float', function () {
assert.throws(function () {
sharp().resize(1.5, 240);
}, /Expected integer between 1 and 16383 for width but received 1.5 of type number/);
}, /Expected positive integer for width but received 1.5 of type number/);
});
it('Invalid height - float', function () {
assert.throws(function () {
sharp().resize(320, 1.5);
}, /Expected integer between 1 and 16383 for height but received 1.5 of type number/);
}, /Expected positive integer for height but received 1.5 of type number/);
});
it('Invalid width - too large', function () {
assert.throws(function () {
sharp().resize(0x4000, 240);
}, /Expected integer between 1 and 16383 for width but received 16384 of type number/);
it('Invalid width - too large', function (done) {
sharp(fixtures.inputJpg)
.resize(0x4000, 1)
.webp()
.toBuffer(function (err) {
assert.strictEqual(true, err instanceof Error);
assert.strictEqual('Processed image is too large for the WebP format', err.message);
done();
});
});
it('Invalid height - too large', function () {
assert.throws(function () {
sharp().resize(320, 0x4000);
}, /Expected integer between 1 and 16383 for height but received 16384 of type number/);
it('Invalid height - too large', function (done) {
sharp(fixtures.inputJpg)
.resize(1, 0x4000)
.webp()
.toBuffer(function (err) {
assert.strictEqual(true, err instanceof Error);
assert.strictEqual('Processed image is too large for the WebP format', err.message);
done();
});
});
it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) {

View File

@@ -34,6 +34,28 @@ describe('Rotation', function () {
});
});
[-3690, -450, -90, 90, 450, 3690].forEach(function (angle) {
it('Rotate by any 90-multiple angle (' + angle + 'deg)', function (done) {
sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
});
});
});
[-3780, -540, 0, 180, 540, 3780].forEach(function (angle) {
it('Rotate by any 180-multiple angle (' + angle + 'deg)', function (done) {
sharp(fixtures.inputJpg320x240).rotate(angle).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
});
it('Rotate by 270 degrees, square output ignoring aspect ratio', function (done) {
sharp(fixtures.inputJpg)
.resize(240, 240)