Compare commits

...

24 Commits

Author SHA1 Message Date
Lovell Fuller
44a0ee3fd3 Release v0.32.5 2023-08-15 19:29:42 +01:00
Lovell Fuller
ccd51c8cbf Upgrade to libvips v8.14.4 2023-08-15 16:40:22 +01:00
Lovell Fuller
bb7469b2d1 Ensure withMetadata adds default sRGB profile #3761 2023-08-15 13:02:20 +01:00
Kleis Auke Wolthuizen
a2cac61209 Simplify 90/270 orient-before-resize logic (#3762) 2023-08-15 07:56:07 +01:00
Lovell Fuller
5c19f6dd9b Ensure resize fit=inside respects 90/270 rotate #3756 2023-08-14 13:45:23 +01:00
Lovell Fuller
3d01775972 Docs: changelog entries for #3748 #3755 #3758 2023-08-14 13:33:13 +01:00
sho-xizz
87562a5111 TypeScript: Ensure WebpOptions minSize is boolean (#3758) 2023-08-09 13:45:10 +01:00
Kleis Auke Wolthuizen
2829e17743 Fix build with musl 1.2.4 (#3755) 2023-08-07 21:57:00 +01:00
pilotso11
ffefbd2ecc TypeScript: add missing WebpPresetEnum (#3748) 2023-08-04 10:51:06 +01:00
Kleis Auke Wolthuizen
bc8f983329 Tests: ensure Jimp benchmark uses bicubic as resizing kernel (#3745) 2023-07-30 11:25:45 +01:00
Kleis Auke Wolthuizen
440936a699 Tests: update benchmark deps and container (#3744)
Use Node 18.x in benchmark container
2023-07-30 11:24:27 +01:00
Lovell Fuller
0bc79cdb95 Docs: include paletteBitDepth metadata 2023-07-28 16:04:02 +01:00
Lovell Fuller
9a66e25f53 Docs: ensure resize fit image supports dark mode 2023-07-25 10:06:44 +01:00
Lovell Fuller
8370935ccf Docs: ensure 'fit' values are clearly separated
Smaller text, slightly closer to image, varied fill colour
2023-07-21 23:07:57 +01:00
Kleis Auke Wolthuizen
f908987f35 Docs: use SVG image for the resize fit property example (#3735) 2023-07-21 21:58:02 +01:00
Lovell Fuller
aea368a3a0 Release v0.32.4 2023-07-21 11:41:08 +01:00
Lovell Fuller
7ecbc20d3d Upgrade to libvips v8.14.3 2023-07-21 11:10:21 +01:00
Lovell Fuller
cb0e2a91c4 Bump dep 2023-07-19 16:55:12 +01:00
Lovell Fuller
739b317a6f Expose ability to (un)block libvips ops by name 2023-07-19 16:53:52 +01:00
Lovell Fuller
a0e1c39785 Release v0.32.3 2023-07-14 11:03:39 +01:00
Lovell Fuller
85b26dab68 Expose preset option for WebP output #3639 2023-07-12 19:12:04 +01:00
Lovell Fuller
66f7cef253 Docs: fix a few typos 2023-07-12 14:22:29 +01:00
Lovell Fuller
863174f201 CI: FreeBSD: Use 13.2 stable, upgrade to Node.js 20 2023-07-12 12:10:31 +01:00
Lovell Fuller
bcd865cc96 Ensure decoding remains sequential for all ops #3725 2023-07-12 11:35:59 +01:00
38 changed files with 506 additions and 108 deletions

View File

@@ -1,5 +1,5 @@
freebsd_instance:
image_family: freebsd-14-0-snap
image_family: freebsd-13-2
task:
name: FreeBSD
@@ -9,7 +9,7 @@ task:
prerequisites_script:
- pkg update -f
- pkg upgrade -y
- pkg install -y devel/pkgconf graphics/vips www/node16 www/npm
- pkg install -y devel/git devel/pkgconf graphics/vips www/node20 www/npm
install_script:
- npm install --build-from-source --unsafe-perm
test_script:

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/coverage
test/bench/node_modules
test/fixtures/output*
test/fixtures/vips-properties.xml
test/leak/libvips.supp
test/saliency/report.json
test/saliency/Image*

View File

@@ -42,14 +42,14 @@ and https://www.cairographics.org/operators/
| [images[].input.text.align] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | text alignment (`'left'`, `'centre'`, `'center'`, `'right'`). |
| [images[].input.text.justify] | <code>boolean</code> | <code>false</code> | set this to true to apply justification to the text. |
| [images[].input.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
| [images[].input.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. |
| [images[].input.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`. |
| [images[].input.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
| [images[].blend] | <code>String</code> | <code>&#x27;over&#x27;</code> | how to blend this image with the image below. |
| [images[].gravity] | <code>String</code> | <code>&#x27;centre&#x27;</code> | gravity at which to place the overlay. |
| [images[].top] | <code>Number</code> | | the pixel offset from the top edge. |
| [images[].left] | <code>Number</code> | | the pixel offset from the left edge. |
| [images[].tile] | <code>Boolean</code> | <code>false</code> | set to true to repeat the overlay image across the entire image with the given `gravity`. |
| [images[].premultiplied] | <code>Boolean</code> | <code>false</code> | set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. |
| [images[].premultiplied] | <code>Boolean</code> | <code>false</code> | set to true to avoid premultiplying the image below. Equivalent to the `--premultiplied` vips option. |
| [images[].density] | <code>Number</code> | <code>72</code> | number representing the DPI for vector overlay image. |
| [images[].raw] | <code>Object</code> | | describes overlay when using raw pixel data. |
| [images[].raw.width] | <code>Number</code> | | |

View File

@@ -22,7 +22,7 @@ Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_st
| --- | --- | --- | --- |
| [input] | <code>Buffer</code> \| <code>ArrayBuffer</code> \| <code>Uint8Array</code> \| <code>Uint8ClampedArray</code> \| <code>Int8Array</code> \| <code>Uint16Array</code> \| <code>Int16Array</code> \| <code>Uint32Array</code> \| <code>Int32Array</code> \| <code>Float32Array</code> \| <code>Float64Array</code> \| <code>string</code> | | if present, can be a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or a TypedArray containing raw pixel image data, or a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file. JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. |
| [options] | <code>Object</code> | | if present, is an Object with optional attributes. |
| [options.failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. |
| [options.failOn] | <code>string</code> | <code>&quot;&#x27;warning&#x27;&quot;</code> | when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort. |
| [options.limitInputPixels] | <code>number</code> \| <code>boolean</code> | <code>268402689</code> | Do not process input images where the number of pixels (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). |
| [options.unlimited] | <code>boolean</code> | <code>false</code> | Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF). |
| [options.sequentialRead] | <code>boolean</code> | <code>true</code> | Set this to `false` to use random access rather than sequential read. Some operations will do this automatically. |
@@ -84,7 +84,7 @@ readableStream.pipe(transformer).pipe(writableStream);
```
**Example**
```js
// Create a blank 300x200 PNG image of semi-transluent red pixels
// Create a blank 300x200 PNG image of semi-translucent red pixels
sharp({
create: {
width: 300,

View File

@@ -22,6 +22,7 @@ A `Promise` is returned when `callback` is not provided.
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
- `pageHeight`: Number of pixels high each page in a multi-page image will be.
- `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image

View File

@@ -3,7 +3,7 @@ Rotate the output image by either an explicit angle
or auto-orient based on the EXIF `Orientation` tag.
If an angle is provided, it is converted to a valid positive degree rotation.
For example, `-450` will produce a 270deg rotation.
For example, `-450` will produce a 270 degree rotation.
When rotating by an angle other than a multiple of 90,
the background colour can be provided with the `background` option.
@@ -533,7 +533,7 @@ await sharp(rgbInput)
## recomb
Recomb the image with the specified matrix.
Recombine the image with the specified matrix.
**Throws**:
@@ -556,7 +556,7 @@ sharp(input)
])
.raw()
.toBuffer(function(err, data, info) {
// data contains the raw pixel data after applying the recomb
// data contains the raw pixel data after applying the matrix
// With this example input, a sepia filter has been applied
});
```
@@ -607,7 +607,7 @@ const output = await sharp(input)
```
**Example**
```js
// decreate brightness and saturation while also hue-rotating by 90 degrees
// decrease brightness and saturation while also hue-rotating by 90 degrees
const output = await sharp(input)
.modulate({
brightness: 0.5,

View File

@@ -294,6 +294,7 @@ Use these WebP options for output image.
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression mode |
| [options.nearLossless] | <code>boolean</code> | <code>false</code> | use near_lossless compression mode |
| [options.smartSubsample] | <code>boolean</code> | <code>false</code> | use high quality chroma subsampling |
| [options.preset] | <code>string</code> | <code>&quot;&#x27;default&#x27;&quot;</code> | named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 6 (slowest) |
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
| [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |

View File

@@ -10,7 +10,7 @@ When both a `width` and `height` are provided, the possible methods by which the
Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.png">
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.

View File

@@ -166,3 +166,58 @@ const simd = sharp.simd();
const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime
```
## block
Block libvips operations at runtime.
This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
which when set will block all "untrusted" operations.
**Since**: 0.32.4
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | |
| options.operation | <code>Array.&lt;string&gt;</code> | List of libvips low-level operation names to block. |
**Example** *(Block all TIFF input.)*
```js
sharp.block({
operation: ['VipsForeignLoadTiff']
});
```
## unblock
Unblock libvips operations at runtime.
This is useful for defining a list of allowed operations.
**Since**: 0.32.4
| Param | Type | Description |
| --- | --- | --- |
| options | <code>Object</code> | |
| options.operation | <code>Array.&lt;string&gt;</code> | List of libvips low-level operation names to unblock. |
**Example** *(Block all input except WebP from the filesystem.)*
```js
sharp.block({
operation: ['VipsForeignLoad']
});
sharp.unblock({
operation: ['VipsForeignLoadWebpFile']
});
```
**Example** *(Block all input except JPEG and PNG from a Buffer or Stream.)*
```js
sharp.block({
operation: ['VipsForeignLoad']
});
sharp.unblock({
operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
});
```

View File

@@ -2,7 +2,46 @@
## v0.32 - *flow*
Requires libvips v8.14.2
Requires libvips v8.14.4
### v0.32.5 - 15th August 2023
* Upgrade to libvips v8.14.4 for upstream bug fixes.
* TypeScript: Add missing `WebpPresetEnum` to definitions.
[#3748](https://github.com/lovell/sharp/pull/3748)
[@pilotso11](https://github.com/pilotso11)
* Ensure compilation using musl v1.2.4.
[#3755](https://github.com/lovell/sharp/pull/3755)
[@kleisauke](https://github.com/kleisauke)
* Ensure resize with a `fit` of `inside` respects 90/270 degree rotation.
[#3756](https://github.com/lovell/sharp/issues/3756)
* TypeScript: Ensure `minSize` property of `WebpOptions` is boolean.
[#3758](https://github.com/lovell/sharp/pull/3758)
[@sho-xizz](https://github.com/sho-xizz)
* Ensure `withMetadata` adds default sRGB profile.
[#3761](https://github.com/lovell/sharp/issues/3761)
### v0.32.4 - 21st July 2023
* Upgrade to libvips v8.14.3 for upstream bug fixes.
* Expose ability to (un)block low-level libvips operations by name.
* Prebuilt binaries: restore support for tile-based output.
[#3581](https://github.com/lovell/sharp/issues/3581)
### v0.32.3 - 14th July 2023
* Expose `preset` option for WebP output.
[#3639](https://github.com/lovell/sharp/issues/3639)
* Ensure decoding remains sequential for all operations (regression in 0.32.2).
[#3725](https://github.com/lovell/sharp/issues/3725)
### v0.32.2 - 11th July 2023

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="998" height="243" viewBox="0 0 998 243">
<defs>
<g id="placeholder">
<rect width="180" height="128" fill="#64bed8"/>
<circle cx="61.1" cy="36.8" r="19.3" fill="#ffefa9"/>
<circle cx="61.1" cy="36.8" r="18.1" fill="#fdda42"/>
<path d="m67.2 34.7 15.2 46L90 57.9l30.4 38 7.8-15.2 7.5 15.4H44z" fill="#6a696f"/>
<path d="m82.4 80.7-15.2-46-.3 69h22.9z" fill="#474749"/>
<path d="m90.1 58 12.2 15 18.2 23-13.9.1z" fill="#474749"/>
<path d="M135.8 96H131l-2.8-15.3z" fill="#474749"/>
<path d="M35.2 96h107.1c0 1.7-1.4 3.2-3.2 3.2H38.4a3.2 3.2 0 0 1-3.2-3.2z" fill="#b9c861"/>
<path d="m67.2 34.7-.1 31-6.2-3-5.3 2.7z" fill="#fff"/>
<path d="m67.2 34.7 7.6 23-7.7 8z" fill="#b3b1b4"/>
<rect width="30.8" height="7.7" x="71.1" y="27.2" rx="2.8" ry="4.1" fill="#fff"/>
<rect width="30.8" height="7.7" x="82.2" y="34.8" rx="2.8" ry="4.1" fill="#fff"/>
<rect width="30.8" height="7.7" x="36.2" y="19.6" rx="2.8" ry="4.1" fill="#fff"/>
<path d="m89.6 72.8-7.2 7.9L90 57.9l10 23z" fill="#fff"/>
<path d="m90.1 58 10 23 2.2-8z" fill="#b3b1b4"/>
<path d="M131.2 85.2 137 68l9 17.2-8 6z" fill="#8da128"/>
<rect width="109.4" height="6.8" x="33.9" y="99.1" rx="13.2" ry="11.4" fill="#22b0d6"/>
<path d="m137 68-5.8 17.2 6.8 6.1.3-13.7z" fill="#727d2e"/>
<rect width="83.3" height="6.8" x="50.8" y="103.6" rx="10" ry="11.4" fill="#22b0d6"/>
<rect width=".7" height="18.4" x="138" y="77.6" fill="#585657"/>
<rect width=".5" height="5.2" x="2" y="-161.3" fill="#585657" transform="rotate(120)"/>
<rect width=".5" height="5.3" x="5.5" y="-163.3" fill="#585657" transform="rotate(120)"/>
<rect width=".5" height="4.8" x="-142.4" y="77.7" fill="#585657" transform="rotate(240)"/>
<rect width=".5" height="5.1" x="-146" y="75.6" fill="#585657" transform="rotate(240)"/>
</g>
<pattern id="img" height="100%" width="100%" viewBox="0 0 180 128">
<use xlink:href="#placeholder"/>
</pattern>
<pattern id="img-fill" width="100%" height="100%" viewBox="0 0 180 128" preserveAspectRatio="none">
<use xlink:href="#placeholder"/>
</pattern>
</defs>
<rect x="0" y="0" width="998" height="243" fill="#ddd"/>
<g id="cover">
<rect x="22" y="28" width="180" height="132" fill="url(#img)"/>
<rect x="48" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4"/>
<text x="112" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">cover</text>
</g>
<g id="contain">
<rect x="240" y="30" width="128" height="128" fill="url(#img)" stroke="#000" stroke-width="4"/>
<text x="304" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold" fill="#555">contain</text>
</g>
<g id="fill">
<rect x="432" y="30" width="128" height="128" fill="url(#img-fill)" stroke="#000" stroke-width="4"/>
<text x="496" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">fill</text>
</g>
<g id="inside">
<rect x="624" y="48" width="128" height="92" fill="url(#img)" stroke="#000" stroke-width="4"/>
<rect x="624" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4" stroke-dasharray="12 4" stroke-dashoffset="6"/>
<text x="688" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold" fill="#555">inside</text>
</g>
<g id="outside">
<rect x="792" y="30" width="176" height="128" fill="url(#img)" stroke="#000" stroke-width="4"/>
<rect x="816" y="30" width="128" height="128" fill="none" stroke="#000" stroke-width="4" stroke-dasharray="12 4" stroke-dashoffset="-2"/>
<text x="880" y="85%" dominant-baseline="middle" text-anchor="middle" font-family="sans" font-size="32" font-weight="bold">outside</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because one or more lines are too long

View File

@@ -108,14 +108,14 @@ const blend = {
* @param {string} [images[].input.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
* @param {boolean} [images[].input.text.justify=false] - set this to true to apply justification to the text.
* @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
* @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
* @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`.
* @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
* @param {Number} [images[].top] - the pixel offset from the top edge.
* @param {Number} [images[].left] - the pixel offset from the left edge.
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option.
* @param {Boolean} [images[].premultiplied=false] - set to true to avoid premultiplying the image below. Equivalent to the `--premultiplied` vips option.
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
* @param {Object} [images[].raw] - describes overlay when using raw pixel data.
* @param {Number} [images[].raw.width]

View File

@@ -49,7 +49,7 @@ const debuglog = util.debuglog('sharp');
* readableStream.pipe(transformer).pipe(writableStream);
*
* @example
* // Create a blank 300x200 PNG image of semi-transluent red pixels
* // Create a blank 300x200 PNG image of semi-translucent red pixels
* sharp({
* create: {
* width: 300,
@@ -122,7 +122,7 @@ const debuglog = util.debuglog('sharp');
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, AVIF, 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 {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort.
* @param {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort.
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
@@ -291,6 +291,7 @@ const Sharp = function (input, options) {
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpPreset: 'default',
webpEffort: 4,
webpMinSize: false,
webpMixed: false,

13
lib/index.d.ts vendored
View File

@@ -1124,9 +1124,11 @@ declare namespace sharp {
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
effort?: number | undefined;
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
minSize?: number;
minSize?: boolean;
/** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */
mixed?: boolean;
/** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
preset?: keyof PresetEnum | undefined;
}
interface AvifOptions extends OutputOptions {
@@ -1476,6 +1478,15 @@ declare namespace sharp {
lanczos3: 'lanczos3';
}
interface PresetEnum {
default: 'default';
picture: 'picture';
photo: 'photo';
drawing: 'drawing';
icon: 'icon';
text: 'text';
}
interface BoolEnum {
and: 'and';
or: 'or';

View File

@@ -432,6 +432,7 @@ function _isStreamInput () {
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
* - `pageHeight`: Number of pixels high each page in a multi-page image will be.
* - `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
* - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image

View File

@@ -11,7 +11,7 @@ const is = require('./is');
* or auto-orient based on the EXIF `Orientation` tag.
*
* If an angle is provided, it is converted to a valid positive degree rotation.
* For example, `-450` will produce a 270deg rotation.
* For example, `-450` will produce a 270 degree rotation.
*
* When rotating by an angle other than a multiple of 90,
* the background colour can be provided with the `background` option.
@@ -772,7 +772,7 @@ function linear (a, b) {
}
/**
* Recomb the image with the specified matrix.
* Recombine the image with the specified matrix.
*
* @since 0.21.1
*
@@ -785,7 +785,7 @@ function linear (a, b) {
* ])
* .raw()
* .toBuffer(function(err, data, info) {
* // data contains the raw pixel data after applying the recomb
* // data contains the raw pixel data after applying the matrix
* // With this example input, a sepia filter has been applied
* });
*
@@ -842,7 +842,7 @@ function recomb (inputMatrix) {
* .toBuffer();
*
* @example
* // decreate brightness and saturation while also hue-rotating by 90 degrees
* // decrease brightness and saturation while also hue-rotating by 90 degrees
* const output = await sharp(input)
* .modulate({
* brightness: 0.5,

View File

@@ -483,6 +483,7 @@ function png (options) {
* @param {boolean} [options.lossless=false] - use lossless compression mode
* @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {string} [options.preset='default'] - named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
@@ -517,6 +518,13 @@ function webp (options) {
if (is.defined(options.smartSubsample)) {
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
}
if (is.defined(options.preset)) {
if (is.string(options.preset) && is.inArray(options.preset, ['default', 'photo', 'picture', 'drawing', 'icon', 'text'])) {
this.options.webpPreset = options.preset;
} else {
throw is.invalidParameterError('preset', 'one of: default, photo, picture, drawing, icon, text', options.preset);
}
}
if (is.defined(options.effort)) {
if (is.integer(options.effort) && is.inRange(options.effort, 0, 6)) {
this.options.webpEffort = options.effort;

View File

@@ -126,7 +126,7 @@ function isResizeExpected (options) {
*
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
*
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.png">
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
*
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.

View File

@@ -202,6 +202,72 @@ function simd (simd) {
}
simd(true);
/**
* Block libvips operations at runtime.
*
* This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
* which when set will block all "untrusted" operations.
*
* @since 0.32.4
*
* @example <caption>Block all TIFF input.</caption>
* sharp.block({
* operation: ['VipsForeignLoadTiff']
* });
*
* @param {Object} options
* @param {Array<string>} options.operation - List of libvips low-level operation names to block.
*/
function block (options) {
if (is.object(options)) {
if (Array.isArray(options.operation) && options.operation.every(is.string)) {
sharp.block(options.operation, true);
} else {
throw is.invalidParameterError('operation', 'Array<string>', options.operation);
}
} else {
throw is.invalidParameterError('options', 'object', options);
}
}
/**
* Unblock libvips operations at runtime.
*
* This is useful for defining a list of allowed operations.
*
* @since 0.32.4
*
* @example <caption>Block all input except WebP from the filesystem.</caption>
* sharp.block({
* operation: ['VipsForeignLoad']
* });
* sharp.unblock({
* operation: ['VipsForeignLoadWebpFile']
* });
*
* @example <caption>Block all input except JPEG and PNG from a Buffer or Stream.</caption>
* sharp.block({
* operation: ['VipsForeignLoad']
* });
* sharp.unblock({
* operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
* });
*
* @param {Object} options
* @param {Array<string>} options.operation - List of libvips low-level operation names to unblock.
*/
function unblock (options) {
if (is.object(options)) {
if (Array.isArray(options.operation) && options.operation.every(is.string)) {
sharp.block(options.operation, false);
} else {
throw is.invalidParameterError('operation', 'Array<string>', options.operation);
}
} else {
throw is.invalidParameterError('options', 'object', options);
}
}
/**
* Decorate the Sharp class with utility-related functions.
* @private
@@ -216,4 +282,6 @@ module.exports = function (Sharp) {
Sharp.versions = versions;
Sharp.vendor = vendor;
Sharp.queue = queue;
Sharp.block = block;
Sharp.unblock = unblock;
};

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.32.2",
"version": "0.32.5",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -133,7 +133,7 @@
],
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.1",
"detect-libc": "^2.0.2",
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.1",
"semver": "^7.5.4",
@@ -159,19 +159,19 @@
},
"license": "Apache-2.0",
"config": {
"libvips": "8.14.2",
"libvips": "8.14.4",
"integrity": {
"darwin-arm64v8": "sha512-eUuxg6H0tXgX4z2lsaGtZ4cbPAm7yoFgkvPDd4csxoiVt+QUB25pEJwiXw7oB53VlBFIp3O8lbydSFS5zH8MQQ==",
"darwin-x64": "sha512-cMT4v76IgzSR0VoXqLk/yftRyzMEZ+SBVMLzXCgqP/lmnYisrpmHHNqrWnoZbUUBXbPXLn6KMultYOJHe/c9ZQ==",
"linux-arm64v8": "sha512-OcDJ/ly80pxwaKnw0W91sSvZczPtWsjmzrY/+6NMiQZT84LkmeaRuwErbHhorKDxnl7iZuNn9Uj5V25Xmj+LDQ==",
"linux-armv6": "sha512-hk2ohSOYTJEtVQxEQFyQ+tuayKpYqx6NiXa7AE+8MF+yscxt+g+mLJ7TjDqtmb4ttFGH4IVfsEfU2YXIqWqkpg==",
"linux-armv7": "sha512-/5Ci2Cd+yLZmTaEt9lVJ89elxX3RMJpps0ESjj43X40yrwka51QfXeg1QV38uNzZpCDIZkrbXZK0lyKldjpLuA==",
"linux-x64": "sha512-wjCKmWfBb0uz1UB7rPDLvO0s+VWuoAY/Vv/YGCRFEQUkdSLQUgHExrOMMbOM3FleuYfQqznDYCXXphkl7X44+w==",
"linuxmusl-arm64v8": "sha512-QtD2n90yi+rLE65C0gksFUU5uMUFPICI/pS3A0bgthpIcoCejAOYs3ZjVWpZbHQuV/lWahIUYO78MB9CzY860A==",
"linuxmusl-x64": "sha512-TokQ/ETCJAsPYuxIMOPYDp25rlcwtpmIMtRUR9PB75TmZEJe7abRfCEInIPYeD8F/HxxnJSLiEdlbn1z1Jfzng==",
"win32-arm64v8": "sha512-IIuj4EAgLqEVAoOuYH79C61a7TcJXlU/RBwk+5JsGWc2mr4J/Ar5J01e6XBvU4Lu3eqcU+3GPaACZEa1511buA==",
"win32-ia32": "sha512-CsZi7lrReX3B6tmYgOGJ0IiAfcN5APDC6l+3gdosxfTfwpLLO+jXaSmyNwIGeMqrdgckG/gwwc+IrUZmkmjJ/A==",
"win32-x64": "sha512-J7znmNKUK4ZKo6SnSnEtzT1xRAwvkGXxIx9/QihAadu1TFdS06yNhcENmwC4973+KZBlAdVpWbZ8sLrEoWkdCA=="
"darwin-arm64v8": "sha512-jZt5+ZBQzdloop9z/XlOAy8jHxD+ZGt3J8YUm1g3njjjKmZ/RmM9r6QAeLLILe67ATHaaAtmCil37fDc400OrQ==",
"darwin-x64": "sha512-Mhpr8n8CjrU+u5K9YLucmkCgwtJGexECLOZejPfqM8CiOMerowR0wJTuSt9WTOtb9qGOL/ndybfrymsw+YH8PA==",
"linux-arm64v8": "sha512-k2PiOOv8amzS4m5jc4Vceozv8h041IoyHL/1s0Rj290jg3w6BUJL3V+TLwKUPM35i7rV5rm14gtnGZ7qKENdmA==",
"linux-armv6": "sha512-CuPTo50owR8P+BCCcWk1tF4qB3XSAaHeaIzSanJM/v9zBZfUfMGI0OLv+ByyHCL3BE2CbGXaSXhuEVw2JQ08Sw==",
"linux-armv7": "sha512-CvD6fMy9PkZk1m2UPTWDcFfcD4qFA3RALyAWIih8ftOY9ksI3Y4uz6c0ML+ixBl0hqQK3WEg6+ac5TGDjZbbYA==",
"linux-x64": "sha512-vqoV61ka2hBYQ5582nQlyUcVPtItu927mng9RUU9nyO4Wt50z9nNT/pfcYEfF2jkBNW9JaiMaj6bENHgxA6mMg==",
"linuxmusl-arm64v8": "sha512-iCyl0y/qxdvgGidsYn11R8d4TEcU92uYHtYI8FSHyUobZw/9i2y3189PUTQ/fw44oqaBzTR3p9NF2eP6aLT9gQ==",
"linuxmusl-x64": "sha512-qj7IUqWUqCtxECpgNp4E1NcIbsNe1ujzBuJcnnQot7GZOuPUhI5N6ZUhozmh6LfbGFdBZpPc/JFh1eDZ0IEpbQ==",
"win32-arm64v8": "sha512-VRi7fpE9Kb3xQGcNmPPTJnWGAEUMq+YOq9abpaIIB2r3Ax327/7wHS7o2ezD6zQKdxIX6gODC5io/hReIJ9Jnw==",
"win32-ia32": "sha512-EnvtU7Q6+pjl5/Y1/UngCFDM2CSqpYWVDwY03ilUKSuqTeDKTJYyus0rJ+n6p4nmdjJlVdhYlkvpy8kkEAtDHg==",
"win32-x64": "sha512-fCl/KQuSijVYC8hULWbff8Mfuh3vjjdz4j5p73VgdLP6aZUrHctbhBvEIe0aQ8HpmcGdBnATX5pXUQ4GDl3mwQ=="
},
"runtime": "napi",
"target": 7

View File

@@ -964,12 +964,7 @@ namespace sharp {
}
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
if (swap && canvas != Canvas::IGNORE_ASPECT) {
// Swap input width and height when requested.
std::swap(width, height);
}
Canvas canvas, bool withoutEnlargement, bool withoutReduction) {
double hshrink = 1.0;
double vshrink = 1.0;
@@ -1035,4 +1030,13 @@ namespace sharp {
return std::make_pair(hshrink, vshrink);
}
/*
Ensure decoding remains sequential.
*/
VImage StaySequential(VImage image, VipsAccess access, bool condition) {
if (access == VIPS_ACCESS_SEQUENTIAL && condition) {
return image.copy_memory();
}
return image;
}
} // namespace sharp

View File

@@ -15,8 +15,8 @@
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 14) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 14 && VIPS_MICRO_VERSION < 2)
#error "libvips version 8.14.2+ is required - please see https://sharp.pixelplumbing.com/install"
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 14 && VIPS_MICRO_VERSION < 4)
#error "libvips version 8.14.4+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -362,13 +362,15 @@ namespace sharp {
VImage EnsureAlpha(VImage image, double const value);
/*
Calculate the shrink factor, taking into account auto-rotate, the canvas
mode, and so on. The hshrink/vshrink are the amount to shrink the input
image axes by in order for the output axes (ie. after rotation) to match
the required thumbnail width/height and canvas mode.
Calculate the horizontal and vertical shrink factors, taking the canvas mode into account.
*/
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction);
Canvas canvas, bool withoutEnlargement, bool withoutReduction);
/*
Ensure decoding remains sequential.
*/
VImage StaySequential(VImage image, VipsAccess access, bool condition = TRUE);
} // namespace sharp

View File

@@ -20,18 +20,15 @@
#include "operations.h"
#include "pipeline.h"
#if defined(WIN32)
#ifdef _WIN32
#define STAT64_STRUCT __stat64
#define STAT64_FUNCTION _stat64
#elif defined(__APPLE__)
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#else
#elif defined(_LARGEFILE64_SOURCE)
#define STAT64_STRUCT stat64
#define STAT64_FUNCTION stat64
#else
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#endif
class PipelineWorker : public Napi::AsyncWorker {
@@ -56,6 +53,7 @@ class PipelineWorker : public Napi::AsyncWorker {
vips::VImage image;
sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
VipsAccess access = baton->input->access;
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
int nPages = baton->input->pages;
@@ -79,9 +77,6 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate and flip image according to Exif orientation
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
image = sharp::RemoveExifOrientation(image);
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL && (autoRotation != VIPS_ANGLE_D0 || autoFlip)) {
image = image.copy_memory();
}
} else {
rotation = CalculateAngleRotation(baton->angle);
}
@@ -93,6 +88,13 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->rotationAngle != 0.0);
if (shouldRotateBefore) {
image = sharp::StaySequential(image, access,
rotation != VIPS_ANGLE_D0 ||
autoRotation != VIPS_ANGLE_D0 ||
autoFlip ||
baton->flip ||
baton->rotationAngle != 0.0);
if (autoRotation != VIPS_ANGLE_D0) {
image = image.rot(autoRotation);
autoRotation = VIPS_ANGLE_D0;
@@ -126,6 +128,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Trim
if (baton->trimThreshold > 0.0) {
MultiPageUnsupported(nPages, "Trim");
image = sharp::StaySequential(image, access);
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold);
baton->trimOffsetLeft = image.xoffset();
baton->trimOffsetTop = image.yoffset();
@@ -154,15 +157,18 @@ class PipelineWorker : public Napi::AsyncWorker {
int targetResizeWidth = baton->width;
int targetResizeHeight = baton->height;
// Swap input output width and height when rotating by 90 or 270 degrees
bool swap = !baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270 ||
autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270);
// When auto-rotating by 90 or 270 degrees, swap the target width and
// height to ensure the behavior aligns with how it would have been if
// the rotation had taken place *before* resizing.
if (!baton->rotateBeforePreExtract &&
(autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270)) {
std::swap(targetResizeWidth, targetResizeHeight);
}
// Shrink to pageHeight, so we work for multi-page images
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
baton->canvas, baton->withoutEnlargement, baton->withoutReduction);
// The jpeg preload shrink.
int jpegShrinkOnLoad = 1;
@@ -212,7 +218,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// pdfload* and svgload*
if (jpegShrinkOnLoad > 1) {
vips::VOption *option = VImage::option()
->set("access", baton->input->access)
->set("access", access)
->set("shrink", jpegShrinkOnLoad)
->set("unlimited", baton->input->unlimited)
->set("fail_on", baton->input->failOn);
@@ -227,7 +233,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
} else if (scale != 1.0) {
vips::VOption *option = VImage::option()
->set("access", baton->input->access)
->set("access", access)
->set("scale", scale)
->set("fail_on", baton->input->failOn);
if (inputImageType == sharp::ImageType::WEBP) {
@@ -296,7 +302,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Shrink to pageHeight, so we work for multi-page images
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
baton->canvas, baton->withoutEnlargement, baton->withoutReduction);
int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
int targetPageHeight = targetHeight;
@@ -379,6 +385,11 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("kernel", baton->kernel));
}
image = sharp::StaySequential(image, access,
autoRotation != VIPS_ANGLE_D0 ||
baton->flip ||
autoFlip ||
rotation != VIPS_ANGLE_D0);
// Auto-rotate post-extract
if (autoRotation != VIPS_ANGLE_D0) {
image = image.rot(autoRotation);
@@ -402,7 +413,7 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
baton->joinChannelIn[i]->access = baton->input->access;
baton->joinChannelIn[i]->access = access;
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
image = image.bandjoin(joinImage);
@@ -471,10 +482,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Attention-based or Entropy-based crop
MultiPageUnsupported(nPages, "Resize strategy");
image = image.tilecache(VImage::option()
->set("access", VIPS_ACCESS_RANDOM)
->set("threaded", TRUE));
image = sharp::StaySequential(image, access);
image = image.smartcrop(baton->width, baton->height, VImage::option()
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 15)
@@ -495,6 +503,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate post-extract non-90 angle
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
MultiPageUnsupported(nPages, "Rotate");
image = sharp::StaySequential(image, access);
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
@@ -518,6 +527,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Affine transform
if (!baton->affineMatrix.empty()) {
MultiPageUnsupported(nPages, "Affine");
image = sharp::StaySequential(image, access);
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
@@ -614,7 +624,7 @@ class PipelineWorker : public Napi::AsyncWorker {
for (Composite *composite : baton->composite) {
VImage compositeImage;
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
composite->input->access = baton->input->access;
composite->input->access = access;
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
// Verify within current dimensions
@@ -701,11 +711,13 @@ class PipelineWorker : public Napi::AsyncWorker {
// Apply normalisation - stretch luminance to cover full dynamic range
if (baton->normalise) {
image = sharp::StaySequential(image, access);
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
}
// Apply contrast limiting adaptive histogram equalization (CLAHE)
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
image = sharp::StaySequential(image, access);
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
}
@@ -713,7 +725,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->boolean != nullptr) {
VImage booleanImage;
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
baton->boolean->access = baton->input->access;
baton->boolean->access = access;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
@@ -776,9 +788,9 @@ class PipelineWorker : public Napi::AsyncWorker {
}
// Apply output ICC profile
if (!baton->withMetadataIcc.empty()) {
if (baton->withMetadata) {
image = image.icc_transform(
const_cast<char*>(baton->withMetadataIcc.data()),
baton->withMetadataIcc.empty() ? "srgb" : const_cast<char*>(baton->withMetadataIcc.data()),
VImage::option()
->set("input_profile", processingProfile)
->set("embedded", TRUE)
@@ -882,6 +894,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("preset", baton->webpPreset)
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
@@ -963,6 +976,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back();
}
image = sharp::StaySequential(image, access, baton->tileAngle != 0);
vips::VOption *options = BuildOptionsDZ(baton);
VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
baton->bufferOut = static_cast<char*>(area->data);
@@ -1086,6 +1100,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("preset", baton->webpPreset)
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
@@ -1162,6 +1177,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back();
}
image = sharp::StaySequential(image, access, baton->tileAngle != 0);
vips::VOption *options = BuildOptionsDZ(baton);
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz";
@@ -1363,6 +1379,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
{"effort", std::to_string(baton->webpEffort)}
@@ -1615,6 +1632,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpPreset = sharp::AttrAsEnum<VipsForeignWebpPreset>(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET);
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
@@ -1674,23 +1692,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->tileId = sharp::AttrAsStr(options, "tileId");
baton->tileBasename = sharp::AttrAsStr(options, "tileBasename");
// Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
if (
baton->trimThreshold > 0.0 ||
baton->normalise ||
baton->position == 16 || baton->position == 17 ||
baton->angle != 0 ||
baton->rotationAngle != 0.0 ||
baton->tileAngle != 0 ||
baton->flip ||
baton->claheWidth != 0 ||
!baton->affineMatrix.empty()
) {
baton->input->access = VIPS_ACCESS_RANDOM;
}
}
// Function to notify of libvips warnings
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();

View File

@@ -153,6 +153,7 @@ struct PipelineBaton {
bool webpNearLossless;
bool webpLossless;
bool webpSmartSubsample;
VipsForeignWebpPreset webpPreset;
int webpEffort;
bool webpMinSize;
bool webpMixed;
@@ -318,6 +319,7 @@ struct PipelineBaton {
webpNearLossless(false),
webpLossless(false),
webpSmartSubsample(false),
webpPreset(VIPS_FOREIGN_WEBP_PRESET_DEFAULT),
webpEffort(4),
webpMinSize(false),
webpMixed(false),

View File

@@ -31,6 +31,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("simd", Napi::Function::New(env, simd));
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
exports.Set("format", Napi::Function::New(env, format));
exports.Set("block", Napi::Function::New(env, block));
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
exports.Set("stats", Napi::Function::New(env, stats));

View File

@@ -164,6 +164,17 @@ Napi::Value format(const Napi::CallbackInfo& info) {
return format;
}
/*
(Un)block libvips operations at runtime.
*/
void block(const Napi::CallbackInfo& info) {
Napi::Array ops = info[size_t(0)].As<Napi::Array>();
bool const state = info[size_t(1)].As<Napi::Boolean>().Value();
for (unsigned int i = 0; i < ops.Length(); i++) {
vips_operation_block_set(ops.Get(i).As<Napi::String>().Utf8Value().c_str(), state);
}
}
/*
Synchronous, internal-only method used by some of the functional tests.
Calculates the maximum colour distance using the DE2000 algorithm

View File

@@ -12,6 +12,7 @@ Napi::Value counters(const Napi::CallbackInfo& info);
Napi::Value simd(const Napi::CallbackInfo& info);
Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
Napi::Value format(const Napi::CallbackInfo& info);
void block(const Napi::CallbackInfo& info);
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);

View File

@@ -5,7 +5,7 @@ ARG BRANCH=main
RUN apt-get -y update && apt-get install -y build-essential curl git
# Install latest Node.js LTS
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get install -y nodejs
# Install benchmark dependencies
@@ -23,4 +23,9 @@ RUN cat /etc/os-release | grep VERSION=
RUN node -v
WORKDIR /tmp/sharp/test/bench
# Workaround for: https://github.com/emscripten-core/emscripten/pull/16917
# This could be removed once Squoosh is an optional dependency.
ENV NODE_OPTIONS="--no-experimental-fetch"
CMD [ "node", "perf" ]

View File

@@ -9,15 +9,15 @@
},
"dependencies": {
"@squoosh/cli": "0.7.3",
"@squoosh/lib": "0.4.0",
"@squoosh/lib": "0.5.3",
"async": "3.2.4",
"benchmark": "2.1.4",
"gm": "1.25.0",
"imagemagick": "0.1.3",
"jimp": "0.22.7"
"jimp": "0.22.10"
},
"optionalDependencies": {
"@tensorflow/tfjs-node": "4.2.0",
"@tensorflow/tfjs-node": "4.9.0",
"mapnik": "4.5.9"
},
"license": "Apache-2.0",

View File

@@ -109,7 +109,7 @@ async.series({
jpegSuite.add('squoosh-lib-buffer-buffer', {
defer: true,
fn: function (deferred) {
const pool = new squoosh.ImagePool();
const pool = new squoosh.ImagePool(os.cpus().length);
const image = pool.ingestImage(inputJpgBuffer);
image.decoded
.then(function () {
@@ -652,7 +652,7 @@ async.series({
throw err;
} else {
image
.resize(width, heightPng)
.resize(width, heightPng, jimp.RESIZE_BICUBIC)
.deflateLevel(6)
.filterType(0)
.getBuffer(jimp.MIME_PNG, function (err) {
@@ -673,7 +673,7 @@ async.series({
throw err;
} else {
image
.resize(width, heightPng)
.resize(width, heightPng, jimp.RESIZE_BICUBIC)
.deflateLevel(6)
.filterType(0)
.write(outputPng, function (err) {
@@ -707,7 +707,7 @@ async.series({
pngSuite.add('squoosh-lib-buffer-buffer', {
defer: true,
fn: function (deferred) {
const pool = new squoosh.ImagePool();
const pool = new squoosh.ImagePool(os.cpus().length);
const image = pool.ingestImage(inputPngBuffer);
image.decoded
.then(function () {

View File

@@ -524,7 +524,7 @@ sharp('input.tiff').jxl({ lossless: true }).toFile('out.jxl');
sharp('input.tiff').jxl({ effort: 7 }).toFile('out.jxl');
// Support `minSize` and `mixed` webp options
sharp('input.tiff').webp({ minSize: 1000, mixed: true }).toFile('out.gif');
sharp('input.tiff').webp({ minSize: true, mixed: true }).toFile('out.gif');
// 'failOn' input param
sharp('input.tiff', { failOn: 'none' });
@@ -651,3 +651,13 @@ sharp(input).composite([
unlimited: true,
}
]);
// Support for webp preset in types
// https://github.com/lovell/sharp/issues/3747
sharp('input.tiff').webp({ preset: 'photo' }).toFile('out.webp');
sharp('input.tiff').webp({ preset: 'picture' }).toFile('out.webp');
sharp('input.tiff').webp({ preset: 'icon' }).toFile('out.webp');
sharp('input.tiff').webp({ preset: 'drawing' }).toFile('out.webp');
sharp('input.tiff').webp({ preset: 'text' }).toFile('out.webp');
sharp('input.tiff').webp({ preset: 'default' }).toFile('out.webp');

View File

@@ -781,6 +781,19 @@ describe('Image metadata', function () {
});
});
it('withMetadata adds default sRGB profile', async () => {
const data = await sharp(fixtures.inputJpg)
.resize(32, 24)
.withMetadata()
.toBuffer();
const metadata = await sharp(data).metadata();
const { colorSpace, deviceClass, intent } = icc.parse(metadata.icc);
assert.strictEqual(colorSpace, 'RGB');
assert.strictEqual(deviceClass, 'Monitor');
assert.strictEqual(intent, 'Perceptual');
});
it('File input with corrupt header fails gracefully', function (done) {
sharp(fixtures.inputJpgWithCorruptHeader)
.metadata(function (err) {

View File

@@ -192,6 +192,23 @@ describe('Rotation', function () {
});
});
it('Auto-rotate by 270 degrees, rectangular output ignoring aspect ratio', function (done) {
sharp(fixtures.inputJpgWithLandscapeExif8)
.resize(320, 240, { fit: sharp.fit.fill })
.rotate()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
sharp(data).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(320, metadata.width);
assert.strictEqual(240, metadata.height);
done();
});
});
});
it('Rotate by 30 degrees, rectangular output ignoring aspect ratio', function (done) {
sharp(fixtures.inputJpg)
.resize(320, 240, { fit: sharp.fit.fill })
@@ -489,4 +506,28 @@ describe('Rotation', function () {
.timeout({ seconds: 5 })
.toBuffer()
);
it('Rotate 90 then resize with inside fit', async () => {
const data = await sharp({ create: { width: 16, height: 8, channels: 3, background: 'red' } })
.rotate(90)
.resize({ width: 6, fit: 'inside' })
.png({ compressionLevel: 0 })
.toBuffer();
const { width, height } = await sharp(data).metadata();
assert.strictEqual(width, 6);
assert.strictEqual(height, 12);
});
it('Resize with inside fit then rotate 90', async () => {
const data = await sharp({ create: { width: 16, height: 8, channels: 3, background: 'red' } })
.resize({ width: 6, fit: 'inside' })
.rotate(90)
.png({ compressionLevel: 0 })
.toBuffer();
const { width, height } = await sharp(data).metadata();
assert.strictEqual(width, 3);
assert.strictEqual(height, 6);
});
});

View File

@@ -928,7 +928,7 @@ describe('Tile', function () {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
extractZip(container, { dir: path.dirname(extractTo) })
extractZip(container, { dir: extractTo })
.then(() => {
assertDeepZoomTiles(directory, 256, 13, done);
})
@@ -959,7 +959,7 @@ describe('Tile', function () {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
extractZip(container, { dir: path.dirname(extractTo) })
extractZip(container, { dir: extractTo })
.then(() => {
assertDeepZoomTiles(directory, 256, 13, done);
})
@@ -988,7 +988,7 @@ describe('Tile', function () {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
extractZip(container, { dir: path.dirname(extractTo) })
extractZip(container, { dir: extractTo })
.then(() => {
assertDeepZoomTiles(directory, 256, 13, done);
})

View File

@@ -153,4 +153,41 @@ describe('Utilities', function () {
assert.strictEqual(true, Array.isArray(sharp.vendor.installed));
});
});
describe('Block', () => {
it('Can block a named operation', () => {
sharp.block({ operation: ['test'] });
});
it('Can unblock a named operation', () => {
sharp.unblock({ operation: ['test'] });
});
it('Invalid block operation throws', () => {
assert.throws(() => sharp.block(1),
/Expected object for options but received 1 of type number/
);
assert.throws(() => sharp.block({}),
/Expected Array<string> for operation but received undefined of type undefined/
);
assert.throws(() => sharp.block({ operation: 'fail' }),
/Expected Array<string> for operation but received fail of type string/
);
assert.throws(() => sharp.block({ operation: ['maybe', false] }),
/Expected Array<string> for operation but received maybe,false of type object/
);
});
it('Invalid unblock operation throws', () => {
assert.throws(() => sharp.unblock(1),
/Expected object for options but received 1 of type number/
);
assert.throws(() => sharp.unblock({}),
/Expected Array<string> for operation but received undefined of type undefined/
);
assert.throws(() => sharp.unblock({ operation: 'fail' }),
/Expected Array<string> for operation but received fail of type string/
);
assert.throws(() => sharp.unblock({ operation: ['maybe', false] }),
/Expected Array<string> for operation but received maybe,false of type object/
);
});
});
});

View File

@@ -102,6 +102,29 @@ describe('WebP', function () {
});
});
it('should produce a different file size with specific preset', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ preset: 'default' })
.toBuffer()
.then(presetDefault =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ preset: 'picture' })
.toBuffer()
.then(presetPicture => {
assert.notStrictEqual(presetDefault.length, presetPicture.length);
})
)
);
it('invalid preset throws', () => {
assert.throws(
() => sharp().webp({ preset: 'fail' }),
/Expected one of: default, photo, picture, drawing, icon, text for preset but received fail of type string/
);
});
it('should produce a smaller file size with increased effort', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)