Compare commits

..

No commits in common. "main" and "v0.34.3-rc.0" have entirely different histories.

41 changed files with 291 additions and 1029 deletions

View File

@ -320,6 +320,3 @@ GitHub: https://github.com/qpincon
Name: Hans Chen Name: Hans Chen
GitHub: https://github.com/hans00 GitHub: https://github.com/hans00
Name: Thibaut Patel
GitHub: https://github.com/tpatel

View File

@ -44,23 +44,25 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
| [options.ignoreIcc] | <code>number</code> | <code>false</code> | should the embedded ICC profile, if any, be ignored. | | [options.ignoreIcc] | <code>number</code> | <code>false</code> | should the embedded ICC profile, if any, be ignored. |
| [options.pages] | <code>number</code> | <code>1</code> | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. | | [options.pages] | <code>number</code> | <code>1</code> | Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. |
| [options.page] | <code>number</code> | <code>0</code> | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. | | [options.page] | <code>number</code> | <code>0</code> | Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. |
| [options.subifd] | <code>number</code> | <code>-1</code> | subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. |
| [options.level] | <code>number</code> | <code>0</code> | level to extract from a multi-level input (OpenSlide), zero based. |
| [options.pdfBackground] | <code>string</code> \| <code>Object</code> | | Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. |
| [options.jp2Oneshot] | <code>boolean</code> | <code>false</code> | Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility. |
| [options.animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. | | [options.animated] | <code>boolean</code> | <code>false</code> | Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. |
| [options.raw] | <code>Object</code> | | describes raw pixel input image data. See `raw()` for pixel ordering. | | [options.raw] | <code>Object</code> | | describes raw pixel input image data. See `raw()` for pixel ordering. |
| [options.raw.width] | <code>number</code> | | integral number of pixels wide. | | [options.raw.width] | <code>number</code> | | integral number of pixels wide. |
| [options.raw.height] | <code>number</code> | | integral number of pixels high. | | [options.raw.height] | <code>number</code> | | integral number of pixels high. |
| [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. | | [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. |
| [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) | | [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) |
| [options.raw.pageHeight] | <code>number</code> | | The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`. |
| [options.create] | <code>Object</code> | | describes a new image to be created. | | [options.create] | <code>Object</code> | | describes a new image to be created. |
| [options.create.width] | <code>number</code> | | integral number of pixels wide. | | [options.create.width] | <code>number</code> | | integral number of pixels wide. |
| [options.create.height] | <code>number</code> | | integral number of pixels high. | | [options.create.height] | <code>number</code> | | integral number of pixels high. |
| [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). | | [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). |
| [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | | [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.create.pageHeight] | <code>number</code> | | The pixel height of each page/frame for animated images, must be an integral factor of `create.height`. |
| [options.create.noise] | <code>Object</code> | | describes a noise to be created. | | [options.create.noise] | <code>Object</code> | | describes a noise to be created. |
| [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. | | [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. |
| [options.create.noise.mean] | <code>number</code> | <code>128</code> | Mean value of pixels in the generated noise. | | [options.create.noise.mean] | <code>number</code> | | mean of pixels in generated noise. |
| [options.create.noise.sigma] | <code>number</code> | <code>30</code> | Standard deviation of pixel values in the generated noise. | | [options.create.noise.sigma] | <code>number</code> | | standard deviation of pixels in generated noise. |
| [options.text] | <code>Object</code> | | describes a new text image to be created. | | [options.text] | <code>Object</code> | | describes a new text image to be created. |
| [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. | | [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
| [options.text.font] | <code>string</code> | | font name to render with. | | [options.text.font] | <code>string</code> | | font name to render with. |
@ -80,17 +82,6 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
| [options.join.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. | | [options.join.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.join.halign] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). | | [options.join.halign] | <code>string</code> | <code>&quot;&#x27;left&#x27;&quot;</code> | horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). |
| [options.join.valign] | <code>string</code> | <code>&quot;&#x27;top&#x27;&quot;</code> | vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). | | [options.join.valign] | <code>string</code> | <code>&quot;&#x27;top&#x27;&quot;</code> | vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). |
| [options.tiff] | <code>Object</code> | | Describes TIFF specific options. |
| [options.tiff.subifd] | <code>number</code> | <code>-1</code> | Sub Image File Directory to extract for OME-TIFF, defaults to main image. |
| [options.svg] | <code>Object</code> | | Describes SVG specific options. |
| [options.svg.stylesheet] | <code>string</code> | | Custom CSS for SVG input, applied with a User Origin during the CSS cascade. |
| [options.svg.highBitdepth] | <code>boolean</code> | <code>false</code> | Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. |
| [options.pdf] | <code>Object</code> | | Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. |
| [options.pdf.background] | <code>string</code> \| <code>Object</code> | | Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
| [options.openSlide] | <code>Object</code> | | Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide. |
| [options.openSlide.level] | <code>number</code> | <code>0</code> | Level to extract from a multi-level input, zero based. |
| [options.jp2] | <code>Object</code> | | Describes JPEG 2000 specific options. Requires the use of a globally-installed libvips compiled with support for OpenJPEG. |
| [options.jp2.oneshot] | <code>boolean</code> | <code>false</code> | Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility. |
**Example** **Example**
```js ```js

View File

@ -242,57 +242,6 @@ const outputWithP3 = await sharp(input)
``` ```
## keepXmp
> keepXmp() ⇒ <code>Sharp</code>
Keep XMP metadata from the input image in the output image.
**Since**: 0.34.3
**Example**
```js
const outputWithXmp = await sharp(inputWithXmp)
.keepXmp()
.toBuffer();
```
## withXmp
> withXmp(xmp) ⇒ <code>Sharp</code>
Set XMP metadata in the output image.
Supported by PNG, JPEG, WebP, and TIFF output.
**Throws**:
- <code>Error</code> Invalid parameters
**Since**: 0.34.3
| Param | Type | Description |
| --- | --- | --- |
| xmp | <code>string</code> | String containing XMP metadata to be embedded in the output image. |
**Example**
```js
const xmpString = `
<?xml version="1.0"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:creator><rdf:Seq><rdf:li>John Doe</rdf:li></rdf:Seq></dc:creator>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>`;
const data = await sharp(input)
.withXmp(xmpString)
.toBuffer();
```
## keepMetadata ## keepMetadata
> keepMetadata() ⇒ <code>Sharp</code> > keepMetadata() ⇒ <code>Sharp</code>
@ -547,7 +496,6 @@ The palette of the input image will be re-used if possible.
| [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) | | [options.dither] | <code>number</code> | <code>1.0</code> | level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) |
| [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 | | [options.interFrameMaxError] | <code>number</code> | <code>0</code> | maximum inter-frame error for transparency, between 0 (lossless) and 32 |
| [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 | | [options.interPaletteMaxError] | <code>number</code> | <code>3</code> | maximum inter-palette error for palette reuse, between 0 and 256 |
| [options.keepDuplicateFrames] | <code>boolean</code> | <code>false</code> | keep duplicate frames in the output instead of combining them |
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation | | [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) | | [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |
| [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format | | [options.force] | <code>boolean</code> | <code>true</code> | force GIF output, otherwise attempt to use input format |

View File

@ -38,8 +38,6 @@ Possible downsizing kernels are:
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf). - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
- `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
- `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators. When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
Downsampling kernels without a matching upsampling interpolator map to `cubic`. Downsampling kernels without a matching upsampling interpolator map to `cubic`.

View File

@ -113,9 +113,15 @@ Some image format libraries spawn additional threads,
e.g. libaom manages its own 4 threads when encoding AVIF images, e.g. libaom manages its own 4 threads when encoding AVIF images,
and these are independent of the value set here. and these are independent of the value set here.
:::note The maximum number of images that sharp can process in parallel
Further [control over performance](/performance) is available. is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
::: which defaults to 4.
https://nodejs.org/api/cli.html#uv_threadpool_sizesize
For example, by default, a machine with 8 CPU cores will process
4 images in parallel and use up to 8 threads per image,
so there will be up to 32 concurrent threads.
**Returns**: <code>number</code> - concurrency **Returns**: <code>number</code> - concurrency

View File

@ -4,26 +4,11 @@ title: Changelog
## v0.34 - *hat* ## v0.34 - *hat*
Requires libvips v8.17.1 Requires libvips v8.17.0
### v0.34.3 - TBD ### v0.34.3 - TBD
* Upgrade to libvips v8.17.1 for upstream bug fixes. * Upgrade to libvips v8.17.0 for upstream bug fixes.
* Add "Magic Kernel Sharp" (no relation) to resizing kernels.
* Deprecate top-level, format-specific constructor parameters, e.g. `subifd` becomes `tiff.subifd`.
* Expose `stylesheet` and `highBitdepth` SVG input parameters.
* Expose `keepDuplicateFrames` GIF output parameter.
* Add support for RAW digital camera image input. Requires libvips compiled with libraw support.
* Provide XMP metadata as a string, as well as a Buffer, where possible.
* Add `pageHeight` option to `create` and `raw` input for animated images.
[#3236](https://github.com/lovell/sharp/issues/3236)
* Expose JPEG 2000 `oneshot` decoder option. * Expose JPEG 2000 `oneshot` decoder option.
[#4262](https://github.com/lovell/sharp/pull/4262) [#4262](https://github.com/lovell/sharp/pull/4262)
@ -33,10 +18,6 @@ Requires libvips v8.17.1
[#4412](https://github.com/lovell/sharp/pull/4412) [#4412](https://github.com/lovell/sharp/pull/4412)
[@kleisauke](https://github.com/kleisauke) [@kleisauke](https://github.com/kleisauke)
* Add `keepXmp` and `withXmp` for control over output XMP metadata.
[#4416](https://github.com/lovell/sharp/pull/4416)
[@tpatel](https://github.com/tpatel)
### v0.34.2 - 20th May 2025 ### v0.34.2 - 20th May 2025
* Ensure animated GIF to WebP conversion retains loop (regression in 0.34.0). * Ensure animated GIF to WebP conversion retains loop (regression in 0.34.0).

View File

@ -2,38 +2,6 @@
title: Performance title: Performance
--- ---
## Parallelism and concurrency
Node.js uses a libuv-managed thread pool when processing asynchronous calls to native modules such as sharp.
The maximum number of images that sharp can process in parallel is controlled by libuv's
[`UV_THREADPOOL_SIZE`](https://nodejs.org/api/cli.html#uv_threadpool_sizesize)
environment variable, which defaults to 4.
When using more than 4 physical CPU cores, set this environment variable
before the Node.js process starts to increase the thread pool size.
```sh
export UV_THREADPOOL_SIZE="$(lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l)"
```
libvips uses a glib-managed thread pool to avoid the overhead of spawning new threads.
The default number of threads used to concurrently process each image is the same as the number of CPU cores,
except when using glibc-based Linux without jemalloc, where the default is `1` to help reduce memory fragmentation.
Use [`sharp.concurrency()`](/api-utility/#concurrency) to manage the number of threads per image.
To reduce memory fragmentation when using the default Linux glibc memory allocator, set the
[`MALLOC_ARENA_MAX`](https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html)
environment variable before the Node.js process starts to reduce the number of memory pools.
```sh
export MALLOC_ARENA_MAX="2"
```
## Benchmark
A test to benchmark the performance of this module relative to alternatives. A test to benchmark the performance of this module relative to alternatives.
Greater libvips performance can be expected with caching enabled (default) Greater libvips performance can be expected with caching enabled (default)
@ -41,28 +9,28 @@ and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput. The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Contenders ## Contenders
- [jimp](https://www.npmjs.com/package/jimp) v1.6.0 - Image processing in pure JavaScript. * [jimp](https://www.npmjs.com/package/jimp) v1.6.0 - Image processing in pure JavaScript.
- [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "_has been unmaintained for a long time_". * [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
- [gm](https://www.npmjs.com/package/gm) v1.25.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility, but "_has been sunset_". * [gm](https://www.npmjs.com/package/gm) v1.25.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility, but "*has been sunset*".
- sharp v0.34.3 / libvips v8.17.0 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.34.0 / libvips v8.16.1 - Caching within libvips disabled to ensure a fair comparison.
### Environment ## Environment
#### AMD64 ### AMD64
- AWS EC2 us-west-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14) * AWS EC2 us-west-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14)
- Ubuntu 25.04 * Ubuntu 24.10 [fad5ba7223f8](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-fad5ba7223f8d87179dfa23211d31845d47e07a474ac31ad5258afb606523c0d)
- Node.js 24.3.0 * Node.js 22.14.0
#### ARM64 ### ARM64
- AWS EC2 us-west-2 [c8g.xlarge](https://aws.amazon.com/ec2/instance-types/c8g/) (4x ARM Graviton4) * AWS EC2 us-west-2 [c8g.xlarge](https://aws.amazon.com/ec2/instance-types/c8g/) (4x ARM Graviton4)
- Ubuntu 25.04 * Ubuntu 24.10 [133f2e05cb69](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-133f2e05cb6958c3ce7ec870fd5a864558ba780fb7062315b51a23670bff7e76)
- Node.js 24.3.0 * Node.js 22.14.0
### Task: JPEG ## Task: JPEG
Decompress a 2725x2225 JPEG image, Decompress a 2725x2225 JPEG image,
resize to 720x588 using Lanczos 3 resampling (where available), resize to 720x588 using Lanczos 3 resampling (where available),
@ -72,31 +40,29 @@ Note: jimp does not support Lanczos 3, bicubic resampling used instead.
#### Results: JPEG (AMD64) #### Results: JPEG (AMD64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | 2.40 | 1.0 | | jimp | buffer | buffer | 2.35 | 1.0 |
| jimp | file | 2.60 | 1.1 | | imagemagick | file | file | 10.51 | 4.5 |
| imagemagick | file | 9.70 | 4.0 | | gm | buffer | buffer | 11.67 | 5.0 |
| gm | buffer | 11.60 | 4.8 | | gm | file | file | 11.75 | 5.1 |
| gm | file | 11.72 | 4.9 | | sharp | stream | stream | 60.72 | 25.8 |
| sharp | stream | 59.40 | 24.8 | | sharp | file | file | 62.37 | 26.5 |
| sharp | file | 62.67 | 26.1 | | sharp | buffer | buffer | 65.15 | 27.7 |
| sharp | buffer | 64.42 | 26.8 |
#### Results: JPEG (ARM64) #### Results: JPEG (ARM64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | 2.24 | 1.0 | | jimp | buffer | buffer | 2.13 | 1.0 |
| jimp | file | 2.47 | 1.1 | | imagemagick | file | file | 12.95 | 6.1 |
| imagemagick | file | 10.42 | 4.7 | | gm | buffer | buffer | 13.53 | 6.4 |
| gm | buffer | 12.80 | 5.7 | | gm | file | file | 13.52 | 6.4 |
| gm | file | 12.88 | 5.7 | | sharp | stream | stream | 46.58 | 21.9 |
| sharp | stream | 45.58 | 20.3 | | sharp | file | file | 48.42 | 22.7 |
| sharp | file | 47.99 | 21.4 | | sharp | buffer | buffer | 50.16 | 23.6 |
| sharp | buffer | 49.20 | 22.0 |
### Task: PNG ## Task: PNG
Decompress a 2048x1536 RGBA PNG image, Decompress a 2048x1536 RGBA PNG image,
premultiply the alpha channel, premultiply the alpha channel,
@ -106,25 +72,25 @@ and without adaptive filtering.
Note: jimp does not support premultiply/unpremultiply. Note: jimp does not support premultiply/unpremultiply.
#### Results: PNG (AMD64) ### Results: PNG (AMD64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| imagemagick | file | 6.06 | 1.0 | | gm | file | file | 8.66 | 1.0 |
| gm | file | 8.44 | 1.4 | | imagemagick | file | file | 8.79 | 1.0 |
| jimp | buffer | 10.98 | 1.8 | | jimp | buffer | buffer | 11.26 | 1.3 |
| sharp | file | 28.26 | 4.7 | | sharp | file | file | 27.93 | 3.2 |
| sharp | buffer | 28.70 | 4.7 | | sharp | buffer | buffer | 28.69 | 3.3 |
#### Results: PNG (ARM64) ### Results: PNG (ARM64)
| Package | I/O | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| imagemagick | file | 7.09 | 1.0 | | gm | file | file | 9.65 | 1.0 |
| gm | file | 8.93 | 1.3 | | imagemagick | file | file | 9.72 | 1.0 |
| jimp | buffer | 10.28 | 1.5 | | jimp | buffer | buffer | 10.68 | 1.1 |
| sharp | file | 23.81 | 3.4 | | sharp | file | file | 23.90 | 2.5 |
| sharp | buffer | 24.19 | 3.4 | | sharp | buffer | buffer | 24.48 | 2.5 |
## Running the benchmark test ## Running the benchmark test

View File

@ -153,6 +153,10 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored. * @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
* @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages. * @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages.
* @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based. * @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based.
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {string|Object} [options.pdfBackground] - Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick.
* @param {boolean} [options.jp2Oneshot=false] - Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`. * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`.
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {number} [options.raw.width] - integral number of pixels wide. * @param {number} [options.raw.width] - integral number of pixels wide.
@ -160,17 +164,15 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4. * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true` * @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
* to avoid sharp premultiplying the image. (optional, default `false`) * to avoid sharp premultiplying the image. (optional, default `false`)
* @param {number} [options.raw.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`.
* @param {Object} [options.create] - describes a new image to be created. * @param {Object} [options.create] - describes a new image to be created.
* @param {number} [options.create.width] - integral number of pixels wide. * @param {number} [options.create.width] - integral number of pixels wide.
* @param {number} [options.create.height] - integral number of pixels high. * @param {number} [options.create.height] - integral number of pixels high.
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA). * @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {number} [options.create.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `create.height`.
* @param {Object} [options.create.noise] - describes a noise to be created. * @param {Object} [options.create.noise] - describes a noise to be created.
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported. * @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
* @param {number} [options.create.noise.mean=128] - Mean value of pixels in the generated noise. * @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
* @param {number} [options.create.noise.sigma=30] - Standard deviation of pixel values in the generated noise. * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
* @param {Object} [options.text] - describes a new text image to be created. * @param {Object} [options.text] - describes a new text image to be created.
* @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
* @param {string} [options.text.font] - font name to render with. * @param {string} [options.text.font] - font name to render with.
@ -190,17 +192,7 @@ const debuglog = util.debuglog('sharp');
* @param {string|Object} [options.join.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.join.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {string} [options.join.halign='left'] - horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). * @param {string} [options.join.halign='left'] - horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`).
* @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`). * @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`).
* @param {Object} [options.tiff] - Describes TIFF specific options. *
* @param {number} [options.tiff.subifd=-1] - Sub Image File Directory to extract for OME-TIFF, defaults to main image.
* @param {Object} [options.svg] - Describes SVG specific options.
* @param {string} [options.svg.stylesheet] - Custom CSS for SVG input, applied with a User Origin during the CSS cascade.
* @param {boolean} [options.svg.highBitdepth=false] - Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA.
* @param {Object} [options.pdf] - Describes PDF specific options. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick.
* @param {string|Object} [options.pdf.background] - Background colour to use when PDF is partially transparent. Parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.openSlide] - Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide.
* @param {number} [options.openSlide.level=0] - Level to extract from a multi-level input, zero based.
* @param {Object} [options.jp2] - Describes JPEG 2000 specific options. Requires the use of a globally-installed libvips compiled with support for OpenJPEG.
* @param {boolean} [options.jp2.oneshot=false] - Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@ -306,7 +298,6 @@ const Sharp = function (input, options) {
withIccProfile: '', withIccProfile: '',
withExif: {}, withExif: {},
withExifMerge: true, withExifMerge: true,
withXmp: '',
resolveWithObject: false, resolveWithObject: false,
loop: -1, loop: -1,
delay: [], delay: [],
@ -347,7 +338,6 @@ const Sharp = function (input, options) {
gifDither: 1, gifDither: 1,
gifInterFrameMaxError: 0, gifInterFrameMaxError: 0,
gifInterPaletteMaxError: 3, gifInterPaletteMaxError: 3,
gifKeepDuplicateFrames: false,
gifReuse: true, gifReuse: true,
gifProgressive: false, gifProgressive: false,
tiffQuality: 80, tiffQuality: 80,

81
lib/index.d.ts vendored
View File

@ -419,7 +419,7 @@ declare namespace sharp {
* @returns {Sharp} * @returns {Sharp}
*/ */
autoOrient(): Sharp autoOrient(): Sharp
/** /**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any. * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
* The use of flip implies the removal of the EXIF Orientation tag, if any. * The use of flip implies the removal of the EXIF Orientation tag, if any.
@ -730,20 +730,6 @@ declare namespace sharp {
*/ */
withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp; withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp;
/**
* Keep all XMP metadata from the input image in the output image.
* @returns A sharp instance that can be used to chain operations
*/
keepXmp(): Sharp;
/**
* Set XMP metadata in the output image.
* @param {string} xmp - String containing XMP metadata to be embedded in the output image.
* @returns A sharp instance that can be used to chain operations
* @throws {Error} Invalid parameters
*/
withXmp(xmp: string): Sharp;
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space. * The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
@ -1017,22 +1003,14 @@ declare namespace sharp {
pages?: number | undefined; pages?: number | undefined;
/** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */ /** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */
page?: number | undefined; page?: number | undefined;
/** TIFF specific input options */ /** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default -1) */
tiff?: TiffInputOptions | undefined;
/** SVG specific input options */
svg?: SvgInputOptions | undefined;
/** PDF specific input options */
pdf?: PdfInputOptions | undefined;
/** OpenSlide specific input options */
openSlide?: OpenSlideInputOptions | undefined;
/** JPEG 2000 specific input options */
jp2?: Jp2InputOptions | undefined;
/** Deprecated: use tiff.subifd instead */
subifd?: number | undefined; subifd?: number | undefined;
/** Deprecated: use pdf.background instead */ /** Level to extract from a multi-level input (OpenSlide), zero based. (optional, default 0) */
pdfBackground?: Colour | Color | undefined;
/** Deprecated: use openSlide.level instead */
level?: number | undefined; level?: number | undefined;
/** Background colour to use when PDF is partially transparent. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. */
pdfBackground?: Colour | Color | undefined;
/** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) */
jp2Oneshot?: boolean | undefined;
/** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */ /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
animated?: boolean | undefined; animated?: boolean | undefined;
/** Describes raw pixel input image data. See raw() for pixel ordering. */ /** Describes raw pixel input image data. See raw() for pixel ordering. */
@ -1075,8 +1053,6 @@ declare namespace sharp {
interface CreateRaw extends Raw { interface CreateRaw extends Raw {
/** Specifies that the raw input has already been premultiplied, set to true to avoid sharp premultiplying the image. (optional, default false) */ /** Specifies that the raw input has already been premultiplied, set to true to avoid sharp premultiplying the image. (optional, default false) */
premultiplied?: boolean | undefined; premultiplied?: boolean | undefined;
/** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
pageHeight?: number | undefined;
} }
type CreateChannels = 3 | 4; type CreateChannels = 3 | 4;
@ -1092,9 +1068,6 @@ declare namespace sharp {
background: Colour | Color; background: Colour | Color;
/** Describes a noise to be created. */ /** Describes a noise to be created. */
noise?: Noise | undefined; noise?: Noise | undefined;
/** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
pageHeight?: number | undefined;
} }
interface CreateText { interface CreateText {
@ -1143,33 +1116,6 @@ declare namespace sharp {
valign?: VerticalAlignment | undefined; valign?: VerticalAlignment | undefined;
} }
interface TiffInputOptions {
/** Sub Image File Directory to extract, defaults to main image. Use -1 for all subifds. */
subifd?: number | undefined;
}
interface SvgInputOptions {
/** Custom CSS for SVG input, applied with a User Origin during the CSS cascade. */
stylesheet?: string | undefined;
/** Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. */
highBitdepth?: boolean | undefined;
}
interface PdfInputOptions {
/** Background colour to use when PDF is partially transparent. Requires the use of a globally-installed libvips compiled with support for PDFium, Poppler, ImageMagick or GraphicsMagick. */
background?: Colour | Color | undefined;
}
interface OpenSlideInputOptions {
/** Level to extract from a multi-level input, zero based. (optional, default 0) */
level?: number | undefined;
}
interface Jp2InputOptions {
/** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) */
oneshot?: boolean | undefined;
}
interface ExifDir { interface ExifDir {
[k: string]: string; [k: string]: string;
} }
@ -1254,8 +1200,6 @@ declare namespace sharp {
iptc?: Buffer | undefined; iptc?: Buffer | undefined;
/** Buffer containing raw XMP data, if present */ /** Buffer containing raw XMP data, if present */
xmp?: Buffer | undefined; xmp?: Buffer | undefined;
/** String containing XMP data, if valid UTF-8 */
xmpAsString?: string | undefined;
/** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */ /** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */
tifftagPhotoshop?: Buffer | undefined; tifftagPhotoshop?: Buffer | undefined;
/** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */ /** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */
@ -1448,11 +1392,9 @@ declare namespace sharp {
/** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */ /** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */
dither?: number | undefined; dither?: number | undefined;
/** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */ /** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */
interFrameMaxError?: number | undefined; interFrameMaxError?: number;
/** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */ /** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */
interPaletteMaxError?: number | undefined; interPaletteMaxError?: number;
/** Keep duplicate frames in the output instead of combining them (optional, default false) */
keepDuplicateFrames?: boolean | undefined;
} }
interface TiffOptions extends OutputOptions { interface TiffOptions extends OutputOptions {
@ -1570,7 +1512,7 @@ declare namespace sharp {
interface Noise { interface Noise {
/** type of generated noise, currently only gaussian is supported. */ /** type of generated noise, currently only gaussian is supported. */
type: 'gaussian'; type?: 'gaussian' | undefined;
/** mean of pixels in generated noise. */ /** mean of pixels in generated noise. */
mean?: number | undefined; mean?: number | undefined;
/** standard deviation of pixels in generated noise. */ /** standard deviation of pixels in generated noise. */
@ -1787,8 +1729,6 @@ declare namespace sharp {
mitchell: 'mitchell'; mitchell: 'mitchell';
lanczos2: 'lanczos2'; lanczos2: 'lanczos2';
lanczos3: 'lanczos3'; lanczos3: 'lanczos3';
mks2013: 'mks2013';
mks2021: 'mks2021';
} }
interface PresetEnum { interface PresetEnum {
@ -1906,7 +1846,6 @@ declare namespace sharp {
interface FormatEnum { interface FormatEnum {
avif: AvailableFormatInfo; avif: AvailableFormatInfo;
dcraw: AvailableFormatInfo;
dz: AvailableFormatInfo; dz: AvailableFormatInfo;
exr: AvailableFormatInfo; exr: AvailableFormatInfo;
fits: AvailableFormatInfo; fits: AvailableFormatInfo;

View File

@ -22,27 +22,14 @@ const align = {
high: 'high' high: 'high'
}; };
const inputStreamParameters = [
// Limits and error handling
'failOn', 'limitInputPixels', 'unlimited',
// Format-generic
'animated', 'autoOrient', 'density', 'ignoreIcc', 'page', 'pages', 'sequentialRead',
// Format-specific
'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
// Deprecated
'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
];
/** /**
* Extract input options, if any, from an object. * Extract input options, if any, from an object.
* @private * @private
*/ */
function _inputOptionsFromObject (obj) { function _inputOptionsFromObject (obj) {
const params = inputStreamParameters const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } = obj;
.filter(p => is.defined(obj[p])) return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot].some(is.defined)
.map(p => ([p, obj[p]])); ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot }
return params.length
? Object.fromEntries(params)
: undefined; : undefined;
} }
@ -185,6 +172,8 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.rawWidth = inputOptions.raw.width; inputDescriptor.rawWidth = inputOptions.raw.width;
inputDescriptor.rawHeight = inputOptions.raw.height; inputDescriptor.rawHeight = inputOptions.raw.height;
inputDescriptor.rawChannels = inputOptions.raw.channels; inputDescriptor.rawChannels = inputOptions.raw.channels;
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
switch (input.constructor) { switch (input.constructor) {
case Uint8Array: case Uint8Array:
case Uint8ClampedArray: case Uint8ClampedArray:
@ -218,25 +207,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else { } else {
throw new Error('Expected width, height and channels for raw pixel input'); throw new Error('Expected width, height and channels for raw pixel input');
} }
inputDescriptor.rawPremultiplied = false;
if (is.defined(inputOptions.raw.premultiplied)) {
if (is.bool(inputOptions.raw.premultiplied)) {
inputDescriptor.rawPremultiplied = inputOptions.raw.premultiplied;
} else {
throw is.invalidParameterError('raw.premultiplied', 'boolean', inputOptions.raw.premultiplied);
}
}
inputDescriptor.rawPageHeight = 0;
if (is.defined(inputOptions.raw.pageHeight)) {
if (is.integer(inputOptions.raw.pageHeight) && inputOptions.raw.pageHeight > 0 && inputOptions.raw.pageHeight <= inputOptions.raw.height) {
if (inputOptions.raw.height % inputOptions.raw.pageHeight !== 0) {
throw new Error(`Expected raw.height ${inputOptions.raw.height} to be a multiple of raw.pageHeight ${inputOptions.raw.pageHeight}`);
}
inputDescriptor.rawPageHeight = inputOptions.raw.pageHeight;
} else {
throw is.invalidParameterError('raw.pageHeight', 'positive integer', inputOptions.raw.pageHeight);
}
}
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.animated)) { if (is.defined(inputOptions.animated)) {
@ -260,66 +230,32 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page); throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
} }
} }
// OpenSlide specific options // Multi-level input (OpenSlide)
if (is.object(inputOptions.openSlide) && is.defined(inputOptions.openSlide.level)) { if (is.defined(inputOptions.level)) {
if (is.integer(inputOptions.openSlide.level) && is.inRange(inputOptions.openSlide.level, 0, 256)) {
inputDescriptor.openSlideLevel = inputOptions.openSlide.level;
} else {
throw is.invalidParameterError('openSlide.level', 'integer between 0 and 256', inputOptions.openSlide.level);
}
} else if (is.defined(inputOptions.level)) {
// Deprecated
if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) { if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
inputDescriptor.openSlideLevel = inputOptions.level; inputDescriptor.level = inputOptions.level;
} else { } else {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level); throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
} }
} }
// TIFF specific options // Sub Image File Directory (TIFF)
if (is.object(inputOptions.tiff) && is.defined(inputOptions.tiff.subifd)) { if (is.defined(inputOptions.subifd)) {
if (is.integer(inputOptions.tiff.subifd) && is.inRange(inputOptions.tiff.subifd, -1, 100000)) {
inputDescriptor.tiffSubifd = inputOptions.tiff.subifd;
} else {
throw is.invalidParameterError('tiff.subifd', 'integer between -1 and 100000', inputOptions.tiff.subifd);
}
} else if (is.defined(inputOptions.subifd)) {
// Deprecated
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) { if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
inputDescriptor.tiffSubifd = inputOptions.subifd; inputDescriptor.subifd = inputOptions.subifd;
} else { } else {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd); throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
} }
} }
// SVG specific options // PDF background colour
if (is.object(inputOptions.svg)) { if (is.defined(inputOptions.pdfBackground)) {
if (is.defined(inputOptions.svg.stylesheet)) {
if (is.string(inputOptions.svg.stylesheet)) {
inputDescriptor.svgStylesheet = inputOptions.svg.stylesheet;
} else {
throw is.invalidParameterError('svg.stylesheet', 'string', inputOptions.svg.stylesheet);
}
}
if (is.defined(inputOptions.svg.highBitdepth)) {
if (is.bool(inputOptions.svg.highBitdepth)) {
inputDescriptor.svgHighBitdepth = inputOptions.svg.highBitdepth;
} else {
throw is.invalidParameterError('svg.highBitdepth', 'boolean', inputOptions.svg.highBitdepth);
}
}
}
// PDF specific options
if (is.object(inputOptions.pdf) && is.defined(inputOptions.pdf.background)) {
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdf.background);
} else if (is.defined(inputOptions.pdfBackground)) {
// Deprecated
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground); inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground);
} }
// JPEG 2000 specific options // JP2 oneshot
if (is.object(inputOptions.jp2) && is.defined(inputOptions.jp2.oneshot)) { if (is.defined(inputOptions.jp2Oneshot)) {
if (is.bool(inputOptions.jp2.oneshot)) { if (is.bool(inputOptions.jp2Oneshot)) {
inputDescriptor.jp2Oneshot = inputOptions.jp2.oneshot; inputDescriptor.jp2Oneshot = inputOptions.jp2Oneshot;
} else { } else {
throw is.invalidParameterError('jp2.oneshot', 'boolean', inputOptions.jp2.oneshot); throw is.invalidParameterError('jp2Oneshot', 'boolean', inputOptions.jp2Oneshot);
} }
} }
// Create new image // Create new image
@ -333,44 +269,27 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.createWidth = inputOptions.create.width; inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height; inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels; inputDescriptor.createChannels = inputOptions.create.channels;
inputDescriptor.createPageHeight = 0;
if (is.defined(inputOptions.create.pageHeight)) {
if (is.integer(inputOptions.create.pageHeight) && inputOptions.create.pageHeight > 0 && inputOptions.create.pageHeight <= inputOptions.create.height) {
if (inputOptions.create.height % inputOptions.create.pageHeight !== 0) {
throw new Error(`Expected create.height ${inputOptions.create.height} to be a multiple of create.pageHeight ${inputOptions.create.pageHeight}`);
}
inputDescriptor.createPageHeight = inputOptions.create.pageHeight;
} else {
throw is.invalidParameterError('create.pageHeight', 'positive integer', inputOptions.create.pageHeight);
}
}
// Noise // Noise
if (is.defined(inputOptions.create.noise)) { if (is.defined(inputOptions.create.noise)) {
if (!is.object(inputOptions.create.noise)) { if (!is.object(inputOptions.create.noise)) {
throw new Error('Expected noise to be an object'); throw new Error('Expected noise to be an object');
} }
if (inputOptions.create.noise.type !== 'gaussian') { if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
throw new Error('Only gaussian noise is supported at the moment'); throw new Error('Only gaussian noise is supported at the moment');
} }
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (!is.inRange(inputOptions.create.channels, 1, 4)) { if (!is.inRange(inputOptions.create.channels, 1, 4)) {
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels); throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
} }
inputDescriptor.createNoiseMean = 128; inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (is.defined(inputOptions.create.noise.mean)) { if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) { inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean; } else {
} else { throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
}
} }
inputDescriptor.createNoiseSigma = 30; if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
if (is.defined(inputOptions.create.noise.sigma)) { inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) { } else {
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma; throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
} else {
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
}
} }
} else if (is.defined(inputOptions.create.background)) { } else if (is.defined(inputOptions.create.background)) {
if (!is.inRange(inputOptions.create.channels, 3, 4)) { if (!is.inRange(inputOptions.create.channels, 3, 4)) {
@ -605,7 +524,6 @@ function _isStreamInput () {
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
* - `iptc`: Buffer containing raw IPTC data, if present * - `iptc`: Buffer containing raw IPTC data, if present
* - `xmp`: Buffer containing raw XMP data, if present * - `xmp`: Buffer containing raw XMP data, if present
* - `xmpAsString`: String containing XMP data, if valid UTF-8.
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* - `formatMagick`: String containing format for images loaded via *magick * - `formatMagick`: String containing format for images loaded via *magick
* - `comments`: Array of keyword/text pairs representing PNG text blocks, if present. * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.

View File

@ -312,59 +312,6 @@ function withIccProfile (icc, options) {
return this; return this;
} }
/**
* Keep XMP metadata from the input image in the output image.
*
* @since 0.34.3
*
* @example
* const outputWithXmp = await sharp(inputWithXmp)
* .keepXmp()
* .toBuffer();
*
* @returns {Sharp}
*/
function keepXmp () {
this.options.keepMetadata |= 0b00010;
return this;
}
/**
* Set XMP metadata in the output image.
*
* Supported by PNG, JPEG, WebP, and TIFF output.
*
* @since 0.34.3
*
* @example
* const xmpString = `
* <?xml version="1.0"?>
* <x:xmpmeta xmlns:x="adobe:ns:meta/">
* <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
* <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
* <dc:creator><rdf:Seq><rdf:li>John Doe</rdf:li></rdf:Seq></dc:creator>
* </rdf:Description>
* </rdf:RDF>
* </x:xmpmeta>`;
*
* const data = await sharp(input)
* .withXmp(xmpString)
* .toBuffer();
*
* @param {string} xmp String containing XMP metadata to be embedded in the output image.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function withXmp (xmp) {
if (is.string(xmp) && xmp.length > 0) {
this.options.withXmp = xmp;
this.options.keepMetadata |= 0b00010;
} else {
throw is.invalidParameterError('xmp', 'non-empty string', xmp);
}
return this;
}
/** /**
* Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image. * Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
* *
@ -782,7 +729,6 @@ function webp (options) {
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
* @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32 * @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
* @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256 * @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
* @param {boolean} [options.keepDuplicateFrames=false] - keep duplicate frames in the output instead of combining them
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation * @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) * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
@ -833,13 +779,6 @@ function gif (options) {
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError); throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
} }
} }
if (is.defined(options.keepDuplicateFrames)) {
if (is.bool(options.keepDuplicateFrames)) {
this._setBooleanOption('gifKeepDuplicateFrames', options.keepDuplicateFrames);
} else {
throw is.invalidParameterError('keepDuplicateFrames', 'boolean', options.keepDuplicateFrames);
}
}
} }
trySetAnimationOptions(options, this.options); trySetAnimationOptions(options, this.options);
return this._updateFormatOut('gif', options); return this._updateFormatOut('gif', options);
@ -1629,8 +1568,6 @@ module.exports = function (Sharp) {
withExifMerge, withExifMerge,
keepIccProfile, keepIccProfile,
withIccProfile, withIccProfile,
keepXmp,
withXmp,
keepMetadata, keepMetadata,
withMetadata, withMetadata,
toFormat, toFormat,

View File

@ -150,8 +150,6 @@ function isResizeExpected (options) {
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf). * - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default). * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
* - `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
* - `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
* *
* When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators. * When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
* Downsampling kernels without a matching upsampling interpolator map to `cubic`. * Downsampling kernels without a matching upsampling interpolator map to `cubic`.

View File

@ -135,9 +135,15 @@ cache(true);
* e.g. libaom manages its own 4 threads when encoding AVIF images, * e.g. libaom manages its own 4 threads when encoding AVIF images,
* and these are independent of the value set here. * and these are independent of the value set here.
* *
* :::note * The maximum number of images that sharp can process in parallel
* Further {@link /performance|control over performance} is available. * is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
* ::: * which defaults to 4.
*
* https://nodejs.org/api/cli.html#uv_threadpool_sizesize
*
* For example, by default, a machine with 8 CPU cores will process
* 4 images in parallel and use up to 8 threads per image,
* so there will be up to 32 concurrent threads.
* *
* @example * @example
* const threads = sharp.concurrency(); // 4 * const threads = sharp.concurrency(); // 4

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.0" "@img/sharp-libvips-darwin-arm64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.0" "@img/sharp-libvips-darwin-x64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.0" "@img/sharp-libvips-linux-arm": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.0" "@img/sharp-libvips-linux-arm64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.0" "@img/sharp-libvips-linux-ppc64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.0" "@img/sharp-libvips-linux-s390x": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.0" "@img/sharp-libvips-linux-x64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0" "@img/sharp-libvips-linuxmusl-arm64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.0" "@img/sharp-libvips-linuxmusl-x64": "1.2.0-rc.2"
}, },
"files": [ "files": [
"lib" "lib"

View File

@ -31,7 +31,7 @@
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
}, },
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.4.4" "@emnapi/runtime": "^1.4.3"
}, },
"cpu": [ "cpu": [
"wasm32" "wasm32"

View File

@ -143,15 +143,15 @@
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.3-rc.0", "@img/sharp-darwin-arm64": "0.34.3-rc.0",
"@img/sharp-darwin-x64": "0.34.3-rc.0", "@img/sharp-darwin-x64": "0.34.3-rc.0",
"@img/sharp-libvips-darwin-arm64": "1.2.0", "@img/sharp-libvips-darwin-arm64": "1.2.0-rc.2",
"@img/sharp-libvips-darwin-x64": "1.2.0", "@img/sharp-libvips-darwin-x64": "1.2.0-rc.2",
"@img/sharp-libvips-linux-arm": "1.2.0", "@img/sharp-libvips-linux-arm": "1.2.0-rc.2",
"@img/sharp-libvips-linux-arm64": "1.2.0", "@img/sharp-libvips-linux-arm64": "1.2.0-rc.2",
"@img/sharp-libvips-linux-ppc64": "1.2.0", "@img/sharp-libvips-linux-ppc64": "1.2.0-rc.2",
"@img/sharp-libvips-linux-s390x": "1.2.0", "@img/sharp-libvips-linux-s390x": "1.2.0-rc.2",
"@img/sharp-libvips-linux-x64": "1.2.0", "@img/sharp-libvips-linux-x64": "1.2.0-rc.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.0", "@img/sharp-libvips-linuxmusl-arm64": "1.2.0-rc.2",
"@img/sharp-libvips-linuxmusl-x64": "1.2.0", "@img/sharp-libvips-linuxmusl-x64": "1.2.0-rc.2",
"@img/sharp-linux-arm": "0.34.3-rc.0", "@img/sharp-linux-arm": "0.34.3-rc.0",
"@img/sharp-linux-arm64": "0.34.3-rc.0", "@img/sharp-linux-arm64": "0.34.3-rc.0",
"@img/sharp-linux-ppc64": "0.34.3-rc.0", "@img/sharp-linux-ppc64": "0.34.3-rc.0",
@ -165,26 +165,26 @@
"@img/sharp-win32-x64": "0.34.3-rc.0" "@img/sharp-win32-x64": "0.34.3-rc.0"
}, },
"devDependencies": { "devDependencies": {
"@emnapi/runtime": "^1.4.4", "@emnapi/runtime": "^1.4.3",
"@img/sharp-libvips-dev": "1.2.0", "@img/sharp-libvips-dev": "1.2.0-rc.2",
"@img/sharp-libvips-dev-wasm32": "1.2.0", "@img/sharp-libvips-dev-wasm32": "1.2.0-rc.2",
"@img/sharp-libvips-win32-arm64": "1.2.0", "@img/sharp-libvips-win32-arm64": "1.2.0-rc.2",
"@img/sharp-libvips-win32-ia32": "1.2.0", "@img/sharp-libvips-win32-ia32": "1.2.0-rc.2",
"@img/sharp-libvips-win32-x64": "1.2.0", "@img/sharp-libvips-win32-x64": "1.2.0-rc.2",
"@types/node": "*", "@types/node": "*",
"cc": "^3.0.1", "cc": "^3.0.1",
"emnapi": "^1.4.4", "emnapi": "^1.4.3",
"exif-reader": "^2.0.2", "exif-reader": "^2.0.2",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"icc": "^3.0.0", "icc": "^3.0.0",
"jsdoc-to-markdown": "^9.1.1", "jsdoc-to-markdown": "^9.1.1",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^11.7.1", "mocha": "^11.6.0",
"node-addon-api": "^8.4.0", "node-addon-api": "^8.3.1",
"node-gyp": "^11.2.0", "node-gyp": "^11.2.0",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"semistandard": "^17.0.0", "semistandard": "^17.0.0",
"tar-fs": "^3.1.0", "tar-fs": "^3.0.9",
"tsd": "^0.32.0" "tsd": "^0.32.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
@ -192,7 +192,7 @@
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
}, },
"config": { "config": {
"libvips": ">=8.17.1" "libvips": ">=8.17.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"

View File

@ -93,7 +93,6 @@ namespace sharp {
descriptor->rawWidth = AttrAsUint32(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrAsUint32(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied"); descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
descriptor->rawPageHeight = AttrAsUint32(input, "rawPageHeight");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
@ -102,20 +101,13 @@ namespace sharp {
if (HasAttr(input, "page")) { if (HasAttr(input, "page")) {
descriptor->page = AttrAsUint32(input, "page"); descriptor->page = AttrAsUint32(input, "page");
} }
// SVG
if (HasAttr(input, "svgStylesheet")) {
descriptor->svgStylesheet = AttrAsStr(input, "svgStylesheet");
}
if (HasAttr(input, "svgHighBitdepth")) {
descriptor->svgHighBitdepth = AttrAsBool(input, "svgHighBitdepth");
}
// Multi-level input (OpenSlide) // Multi-level input (OpenSlide)
if (HasAttr(input, "openSlideLevel")) { if (HasAttr(input, "level")) {
descriptor->openSlideLevel = AttrAsUint32(input, "openSlideLevel"); descriptor->level = AttrAsUint32(input, "level");
} }
// subIFD (OME-TIFF) // subIFD (OME-TIFF)
if (HasAttr(input, "subifd")) { if (HasAttr(input, "subifd")) {
descriptor->tiffSubifd = AttrAsInt32(input, "tiffSubifd"); descriptor->subifd = AttrAsInt32(input, "subifd");
} }
// // PDF background color // // PDF background color
if (HasAttr(input, "pdfBackground")) { if (HasAttr(input, "pdfBackground")) {
@ -130,7 +122,6 @@ namespace sharp {
descriptor->createChannels = AttrAsUint32(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrAsUint32(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrAsUint32(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createPageHeight = AttrAsUint32(input, "createPageHeight");
if (HasAttr(input, "createNoiseType")) { if (HasAttr(input, "createNoiseType")) {
descriptor->createNoiseType = AttrAsStr(input, "createNoiseType"); descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean"); descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
@ -284,7 +275,6 @@ namespace sharp {
case ImageType::EXR: id = "exr"; break; case ImageType::EXR: id = "exr"; break;
case ImageType::JXL: id = "jxl"; break; case ImageType::JXL: id = "jxl"; break;
case ImageType::RAD: id = "rad"; break; case ImageType::RAD: id = "rad"; break;
case ImageType::DCRAW: id = "dcraw"; break;
case ImageType::VIPS: id = "vips"; break; case ImageType::VIPS: id = "vips"; break;
case ImageType::RAW: id = "raw"; break; case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break; case ImageType::UNKNOWN: id = "unknown"; break;
@ -333,8 +323,6 @@ namespace sharp {
{ "VipsForeignLoadJxlBuffer", ImageType::JXL }, { "VipsForeignLoadJxlBuffer", ImageType::JXL },
{ "VipsForeignLoadRadFile", ImageType::RAD }, { "VipsForeignLoadRadFile", ImageType::RAD },
{ "VipsForeignLoadRadBuffer", ImageType::RAD }, { "VipsForeignLoadRadBuffer", ImageType::RAD },
{ "VipsForeignLoadDcRawFile", ImageType::DCRAW },
{ "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
{ "VipsForeignLoadVips", ImageType::VIPS }, { "VipsForeignLoadVips", ImageType::VIPS },
{ "VipsForeignLoadVipsFile", ImageType::VIPS }, { "VipsForeignLoadVipsFile", ImageType::VIPS },
{ "VipsForeignLoadRaw", ImageType::RAW } { "VipsForeignLoadRaw", ImageType::RAW }
@ -399,48 +387,6 @@ namespace sharp {
imageType == ImageType::HEIF; imageType == ImageType::HEIF;
} }
/*
Format-specific options builder
*/
vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor) {
vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", true);
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
switch (imageType) {
case ImageType::SVG:
option->set("dpi", descriptor->density)
->set("stylesheet", descriptor->svgStylesheet.data())
->set("high_bitdepth", descriptor->svgHighBitdepth);
break;
case ImageType::TIFF:
option->set("tiffSubifd", descriptor->tiffSubifd);
break;
case ImageType::PDF:
option->set("dpi", descriptor->density)
->set("background", descriptor->pdfBackground);
break;
case ImageType::OPENSLIDE:
option->set("openSlideLevel", descriptor->openSlideLevel);
break;
case ImageType::JP2:
option->set("oneshot", descriptor->jp2Oneshot);
break;
case ImageType::MAGICK:
option->set("density", std::to_string(descriptor->density).data());
break;
default:
break;
}
return option;
}
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
*/ */
@ -458,10 +404,6 @@ namespace sharp {
} else { } else {
image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16; image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16;
} }
if (descriptor->rawPageHeight > 0) {
image.set(VIPS_META_PAGE_HEIGHT, descriptor->rawPageHeight);
image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->rawHeight / descriptor->rawPageHeight));
}
if (descriptor->rawPremultiplied) { if (descriptor->rawPremultiplied) {
image = image.unpremultiply(); image = image.unpremultiply();
} }
@ -471,7 +413,34 @@ namespace sharp {
imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength); imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = GetOptionsForImageType(imageType, descriptor); vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", true);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
if (imageType == ImageType::PDF) {
option->set("background", descriptor->pdfBackground);
}
if (imageType == ImageType::JP2) {
option->set("oneshot", descriptor->jp2Oneshot);
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@ -511,10 +480,6 @@ namespace sharp {
channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB)) channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
.new_from_image(background); .new_from_image(background);
} }
if (descriptor->createPageHeight > 0) {
image.set(VIPS_META_PAGE_HEIGHT, descriptor->createPageHeight);
image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->createHeight / descriptor->createPageHeight));
}
image = image.cast(VIPS_FORMAT_UCHAR); image = image.cast(VIPS_FORMAT_UCHAR);
imageType = ImageType::RAW; imageType = ImageType::RAW;
} else if (descriptor->textValue.length() > 0) { } else if (descriptor->textValue.length() > 0) {
@ -558,7 +523,34 @@ namespace sharp {
} }
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
try { try {
vips::VOption *option = GetOptionsForImageType(imageType, descriptor); vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", true);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
}
if (imageType == ImageType::MAGICK) {
option->set("density", std::to_string(descriptor->density).data());
}
if (ImageTypeSupportsPage(imageType)) {
option->set("n", descriptor->pages);
option->set("page", descriptor->page);
}
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
if (imageType == ImageType::PDF) {
option->set("background", descriptor->pdfBackground);
}
if (imageType == ImageType::JP2) {
option->set("oneshot", descriptor->jp2Oneshot);
}
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);

View File

@ -16,8 +16,8 @@
#if (VIPS_MAJOR_VERSION < 8) || \ #if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 1) (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 0)
#error "libvips version 8.17.1+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.17.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if defined(__has_include) #if defined(__has_include)
@ -48,13 +48,13 @@ namespace sharp {
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
bool rawPremultiplied; bool rawPremultiplied;
int rawPageHeight;
int pages; int pages;
int page; int page;
int level;
int subifd;
int createChannels; int createChannels;
int createWidth; int createWidth;
int createHeight; int createHeight;
int createPageHeight;
std::vector<double> createBackground; std::vector<double> createBackground;
std::string createNoiseType; std::string createNoiseType;
double createNoiseMean; double createNoiseMean;
@ -77,10 +77,6 @@ namespace sharp {
std::vector<double> joinBackground; std::vector<double> joinBackground;
VipsAlign joinHalign; VipsAlign joinHalign;
VipsAlign joinValign; VipsAlign joinValign;
std::string svgStylesheet;
bool svgHighBitdepth;
int tiffSubifd;
int openSlideLevel;
std::vector<double> pdfBackground; std::vector<double> pdfBackground;
bool jp2Oneshot; bool jp2Oneshot;
@ -100,13 +96,13 @@ namespace sharp {
rawWidth(0), rawWidth(0),
rawHeight(0), rawHeight(0),
rawPremultiplied(false), rawPremultiplied(false),
rawPageHeight(0),
pages(1), pages(1),
page(0), page(0),
level(0),
subifd(-1),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),
createPageHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 }, createBackground{ 0.0, 0.0, 0.0, 255.0 },
createNoiseMean(0.0), createNoiseMean(0.0),
createNoiseSigma(0.0), createNoiseSigma(0.0),
@ -125,9 +121,6 @@ namespace sharp {
joinBackground{ 0.0, 0.0, 0.0, 255.0 }, joinBackground{ 0.0, 0.0, 0.0, 255.0 },
joinHalign(VIPS_ALIGN_LOW), joinHalign(VIPS_ALIGN_LOW),
joinValign(VIPS_ALIGN_LOW), joinValign(VIPS_ALIGN_LOW),
svgHighBitdepth(false),
tiffSubifd(-1),
openSlideLevel(0),
pdfBackground{ 255.0, 255.0, 255.0, 255.0 }, pdfBackground{ 255.0, 255.0, 255.0, 255.0 },
jp2Oneshot(false) {} jp2Oneshot(false) {}
}; };
@ -169,7 +162,6 @@ namespace sharp {
EXR, EXR,
JXL, JXL,
RAD, RAD,
DCRAW,
VIPS, VIPS,
RAW, RAW,
UNKNOWN, UNKNOWN,
@ -226,9 +218,14 @@ namespace sharp {
ImageType DetermineImageType(char const *file); ImageType DetermineImageType(char const *file);
/* /*
Format-specific options builder Does this image type support multiple pages?
*/ */
vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor); bool ImageTypeSupportsPage(ImageType imageType);
/*
Does this image type support removal of safety limits?
*/
bool ImageTypeSupportsUnlimited(ImageType imageType);
/* /*
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data) Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)

View File

@ -262,10 +262,6 @@ class MetadataWorker : public Napi::AsyncWorker {
} }
if (baton->xmpLength > 0) { if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback)); info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
if (g_utf8_validate(static_cast<char const *>(baton->xmp), baton->xmpLength, nullptr)) {
info.Set("xmpAsString",
Napi::String::New(env, static_cast<char const *>(baton->xmp), baton->xmpLength));
}
} }
if (baton->tifftagPhotoshopLength > 0) { if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop", info.Set("tifftagPhotoshop",

View File

@ -241,7 +241,11 @@ class PipelineWorker : public Napi::AsyncWorker {
// factor for jpegload*, a double scale factor for webpload*, // factor for jpegload*, a double scale factor for webpload*,
// pdfload* and svgload* // pdfload* and svgload*
if (jpegShrinkOnLoad > 1) { if (jpegShrinkOnLoad > 1) {
vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad); vips::VOption *option = VImage::option()
->set("access", access)
->set("shrink", jpegShrinkOnLoad)
->set("unlimited", baton->input->unlimited)
->set("fail_on", baton->input->failOn);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload JPEG buffer // Reload JPEG buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -252,8 +256,14 @@ class PipelineWorker : public Napi::AsyncWorker {
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option); image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} }
} else if (scale != 1.0) { } else if (scale != 1.0) {
vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale); vips::VOption *option = VImage::option()
->set("access", access)
->set("scale", scale)
->set("fail_on", baton->input->failOn);
if (inputImageType == sharp::ImageType::WEBP) { if (inputImageType == sharp::ImageType::WEBP) {
option->set("n", baton->input->pages);
option->set("page", baton->input->page);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload WebP buffer // Reload WebP buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -264,6 +274,9 @@ class PipelineWorker : public Napi::AsyncWorker {
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option); image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
} }
} else if (inputImageType == sharp::ImageType::SVG) { } else if (inputImageType == sharp::ImageType::SVG) {
option->set("unlimited", baton->input->unlimited);
option->set("dpi", baton->input->density);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload SVG buffer // Reload SVG buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -278,6 +291,11 @@ class PipelineWorker : public Napi::AsyncWorker {
throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled"); throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
} }
} else if (inputImageType == sharp::ImageType::PDF) { } else if (inputImageType == sharp::ImageType::PDF) {
option->set("n", baton->input->pages);
option->set("page", baton->input->page);
option->set("dpi", baton->input->density);
option->set("background", baton->input->pdfBackground);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
// Reload PDF buffer // Reload PDF buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@ -287,6 +305,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Reload PDF file // Reload PDF file
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option); image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
} }
sharp::SetDensity(image, baton->input->density); sharp::SetDensity(image, baton->input->density);
} }
} else { } else {
@ -876,12 +895,7 @@ class PipelineWorker : public Napi::AsyncWorker {
image.set(s.first.data(), s.second.data()); image.set(s.first.data(), s.second.data());
} }
} }
// XMP buffer
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {
image = image.copy();
image.set(VIPS_META_XMP_NAME, nullptr,
const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());
}
// Number of channels used in output image // Number of channels used in output image
baton->channels = image.bands(); baton->channels = image.bands();
baton->width = image.width(); baton->width = image.width();
@ -992,7 +1006,6 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("interlace", baton->gifProgressive) ->set("interlace", baton->gifProgressive)
->set("interframe_maxerror", baton->gifInterFrameMaxError) ->set("interframe_maxerror", baton->gifInterFrameMaxError)
->set("interpalette_maxerror", baton->gifInterPaletteMaxError) ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
->set("dither", baton->gifDither))); ->set("dither", baton->gifDither)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length; baton->bufferOutLength = area->length;
@ -1196,9 +1209,6 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("effort", baton->gifEffort) ->set("effort", baton->gifEffort)
->set("reuse", baton->gifReuse) ->set("reuse", baton->gifReuse)
->set("interlace", baton->gifProgressive) ->set("interlace", baton->gifProgressive)
->set("interframe_maxerror", baton->gifInterFrameMaxError)
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
->set("dither", baton->gifDither)); ->set("dither", baton->gifDither));
baton->formatOut = "gif"; baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@ -1711,7 +1721,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
} }
} }
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge"); baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
baton->withXmp = sharp::AttrAsStr(options, "withXmp");
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds"); baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
baton->loop = sharp::AttrAsUint32(options, "loop"); baton->loop = sharp::AttrAsUint32(options, "loop");
baton->delay = sharp::AttrAsInt32Vector(options, "delay"); baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@ -1752,7 +1761,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gifDither = sharp::AttrAsDouble(options, "gifDither"); baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError"); baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError"); baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse"); baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive"); baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");

View File

@ -169,7 +169,6 @@ struct PipelineBaton {
double gifDither; double gifDither;
double gifInterFrameMaxError; double gifInterFrameMaxError;
double gifInterPaletteMaxError; double gifInterPaletteMaxError;
bool gifKeepDuplicateFrames;
bool gifReuse; bool gifReuse;
bool gifProgressive; bool gifProgressive;
int tiffQuality; int tiffQuality;
@ -202,7 +201,6 @@ struct PipelineBaton {
std::string withIccProfile; std::string withIccProfile;
std::unordered_map<std::string, std::string> withExif; std::unordered_map<std::string, std::string> withExif;
bool withExifMerge; bool withExifMerge;
std::string withXmp;
int timeoutSeconds; int timeoutSeconds;
std::vector<double> convKernel; std::vector<double> convKernel;
int convKernelWidth; int convKernelWidth;
@ -344,7 +342,6 @@ struct PipelineBaton {
gifDither(1.0), gifDither(1.0),
gifInterFrameMaxError(0.0), gifInterFrameMaxError(0.0),
gifInterPaletteMaxError(3.0), gifInterPaletteMaxError(3.0),
gifKeepDuplicateFrames(false),
gifReuse(true), gifReuse(true),
gifProgressive(false), gifProgressive(false),
tiffQuality(80), tiffQuality(80),

View File

@ -119,7 +119,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
Napi::Object format = Napi::Object::New(env); Napi::Object format = Napi::Object::New(env);
for (std::string const f : { for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw" "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad"
}) { }) {
// Input // Input
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str()); const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());

View File

@ -1,11 +1,11 @@
FROM ubuntu:25.04 FROM ubuntu:24.10
ARG BRANCH=main ARG BRANCH=main
# Install basic dependencies # Install basic dependencies
RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg
# Install latest Node.js LTS # Install latest Node.js LTS
RUN curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs RUN apt-get install -y nodejs

View File

@ -552,32 +552,6 @@ async.series({
} }
}); });
} }
}).add('sharp-mks2013', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2013' })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-mks2021', {
defer: true,
fn: function (deferred) {
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'mks2021' })
.toBuffer(function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).on('cycle', function (event) { }).on('cycle', function (event) {
console.log('kernels ' + String(event.target)); console.log('kernels ' + String(event.target));
}).on('complete', function () { }).on('complete', function () {

View File

@ -188,8 +188,6 @@ sharp(input)
// of the image data in inputBuffer // of the image data in inputBuffer
}); });
sharp(input).resize({ kernel: 'mks2013' });
transformer = sharp() transformer = sharp()
.resize(200, 200, { .resize(200, 200, {
fit: 'cover', fit: 'cover',
@ -375,8 +373,6 @@ sharp(input)
.gif({ reuse: false }) .gif({ reuse: false })
.gif({ progressive: true }) .gif({ progressive: true })
.gif({ progressive: false }) .gif({ progressive: false })
.gif({ keepDuplicateFrames: true })
.gif({ keepDuplicateFrames: false })
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
console.log(data); console.log(data);
@ -418,7 +414,6 @@ sharp({
channels: 4, channels: 4,
height: 25000, height: 25000,
width: 25000, width: 25000,
pageHeight: 1000,
}, },
limitInputPixels: false, limitInputPixels: false,
}) })
@ -436,6 +431,9 @@ sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile
// Support `unlimited` input option // Support `unlimited` input option
sharp('input.png', { unlimited: true }).resize(320, 240).toFile('outfile.png'); sharp('input.png', { unlimited: true }).resize(320, 240).toFile('outfile.png');
// Support `subifd` input option for tiffs
sharp('input.tiff', { subifd: 3 }).resize(320, 240).toFile('outfile.png');
// Support creating with noise // Support creating with noise
sharp({ sharp({
create: { create: {
@ -692,8 +690,6 @@ sharp(input)
k2: 'v2' k2: 'v2'
} }
}) })
.keepXmp()
.withXmp('test')
.keepIccProfile() .keepIccProfile()
.withIccProfile('filename') .withIccProfile('filename')
.withIccProfile('filename', { attach: false }); .withIccProfile('filename', { attach: false });
@ -720,29 +716,13 @@ sharp(input).composite([
} }
]) ])
// Support format-specific input options
const colour: sharp.Colour = '#fff'; const colour: sharp.Colour = '#fff';
const color: sharp.Color = '#fff'; const color: sharp.Color = '#fff';
sharp({ pdf: { background: colour } }); sharp({ pdfBackground: colour });
sharp({ pdf: { background: color } }); sharp({ pdfBackground: color });
sharp({ pdfBackground: colour }); // Deprecated
sharp({ pdfBackground: color }); // Deprecated
sharp({ tiff: { subifd: 3 } });
sharp({ subifd: 3 }); // Deprecated
sharp({ openSlide: { level: 0 } });
sharp({ level: 0 }); // Deprecated
sharp({ jp2: { oneshot: true } });
sharp({ jp2: { oneshot: false } });
sharp({ svg: { stylesheet: 'test' }});
sharp({ svg: { highBitdepth: true }});
sharp({ svg: { highBitdepth: false }});
// Raw input options sharp({ jp2Oneshot: true });
const raw: sharp.Raw = { width: 1, height: 1, channels: 3 }; sharp({ jp2Oneshot: false });
sharp({ raw });
sharp({ raw: { ...raw, premultiplied: true } });
sharp({ raw: { ...raw, premultiplied: false } });
sharp({ raw: { ...raw, pageHeight: 1 } });
sharp({ autoOrient: true }); sharp({ autoOrient: true });
sharp({ autoOrient: false }); sharp({ autoOrient: false });

View File

@ -187,17 +187,6 @@ describe('GIF input', () => {
); );
}); });
it('invalid keepDuplicateFrames throws', () => {
assert.throws(
() => sharp().gif({ keepDuplicateFrames: -1 }),
/Expected boolean for keepDuplicateFrames but received -1 of type number/
);
assert.throws(
() => sharp().gif({ keepDuplicateFrames: 'fail' }),
/Expected boolean for keepDuplicateFrames but received fail of type string/
);
});
it('should work with streams when only animated is set', function (done) { it('should work with streams when only animated is set', function (done) {
fs.createReadStream(fixtures.inputGifAnimated) fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ animated: true })) .pipe(sharp({ animated: true }))
@ -236,20 +225,6 @@ describe('GIF input', () => {
assert.strict(before.length > after.length); assert.strict(before.length > after.length);
}); });
it('should keep duplicate frames via keepDuplicateFrames', async () => {
const create = { width: 8, height: 8, channels: 4, background: 'blue' };
const input = sharp([{ create }, { create }], { join: { animated: true } });
const before = await input.gif({ keepDuplicateFrames: false }).toBuffer();
const after = await input.gif({ keepDuplicateFrames: true }).toBuffer();
assert.strict(before.length < after.length);
const beforeMeta = await sharp(before).metadata();
const afterMeta = await sharp(after).metadata();
assert.strictEqual(beforeMeta.pages, 1);
assert.strictEqual(afterMeta.pages, 2);
});
it('non-animated input defaults to no-loop', async () => { it('non-animated input defaults to no-loop', async () => {
for (const input of [fixtures.inputGif, fixtures.inputPng]) { for (const input of [fixtures.inputGif, fixtures.inputPng]) {
const data = await sharp(input) const data = await sharp(input)

View File

@ -867,91 +867,52 @@ describe('Input/output', function () {
sharp({ pages: '1' }); sharp({ pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/); }, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
}); });
it('Valid openSlide.level property', function () { it('Valid level property', function () {
sharp({ openSlide: { level: 1 } });
sharp({ level: 1 }); sharp({ level: 1 });
}); });
it('Invalid openSlide.level property (string) throws', function () { it('Invalid level property (string) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ openSlide: { level: '1' } }), sharp({ level: '1' });
/Expected integer between 0 and 256 for openSlide.level but received 1 of type string/ }, /Expected integer between 0 and 256 for level but received 1 of type string/);
);
assert.throws(
() => sharp({ level: '1' }),
/Expected integer between 0 and 256 for level but received 1 of type string/
);
}); });
it('Invalid openSlide.level property (negative) throws', function () { it('Invalid level property (negative) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ openSlide: { level: -1 } }), sharp({ level: -1 });
/Expected integer between 0 and 256 for openSlide\.level but received -1 of type number/ }, /Expected integer between 0 and 256 for level but received -1 of type number/);
);
assert.throws(
() => sharp({ level: -1 }),
/Expected integer between 0 and 256 for level but received -1 of type number/
);
}); });
it('Valid tiff.subifd property', function () { it('Valid subifd property', function () {
sharp({ tiff: { subifd: 1 } });
sharp({ subifd: 1 }); sharp({ subifd: 1 });
}); });
it('Invalid tiff.subifd property (string) throws', function () { it('Invalid subifd property (string) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ tiff: { subifd: '1' } }), sharp({ subifd: '1' });
/Expected integer between -1 and 100000 for tiff\.subifd but received 1 of type string/ }, /Expected integer between -1 and 100000 for subifd but received 1 of type string/);
);
assert.throws(
() => sharp({ subifd: '1' }),
/Expected integer between -1 and 100000 for subifd but received 1 of type string/
);
}); });
it('Invalid tiff.subifd property (float) throws', function () { it('Invalid subifd property (float) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ tiff: { subifd: 1.2 } }), sharp({ subifd: 1.2 });
/Expected integer between -1 and 100000 for tiff\.subifd but received 1.2 of type number/ }, /Expected integer between -1 and 100000 for subifd but received 1.2 of type number/);
);
assert.throws(
() => sharp({ subifd: 1.2 }),
/Expected integer between -1 and 100000 for subifd but received 1.2 of type number/
);
}); });
it('Valid pdf.background property (string)', function () { it('Valid pdfBackground property (string)', function () {
sharp({ pdf: { background: '#00ff00' } });
sharp({ pdfBackground: '#00ff00' }); sharp({ pdfBackground: '#00ff00' });
}); });
it('Valid pdf.background property (object)', function () { it('Valid pdfBackground property (object)', function () {
sharp({ pdf: { background: { r: 0, g: 255, b: 0 } } });
sharp({ pdfBackground: { r: 0, g: 255, b: 0 } }); sharp({ pdfBackground: { r: 0, g: 255, b: 0 } });
}); });
it('Invalid pdf.background property (string) throws', function () { it('Invalid pdfBackground property (string) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ pdf: { background: '00ff00' } }), sharp({ pdfBackground: '00ff00' });
/Unable to parse color from string/ }, /Unable to parse color from string/);
);
assert.throws(
() => sharp({ pdfBackground: '00ff00' }),
/Unable to parse color from string/
);
}); });
it('Invalid pdf.background property (number) throws', function () { it('Invalid pdfBackground property (number) throws', function () {
assert.throws( assert.throws(function () {
() => sharp({ pdf: { background: 255 } }), sharp({ pdfBackground: 255 });
/Expected object or string for background/ }, /Expected object or string for background/);
);
assert.throws(
() => sharp({ pdf: { background: 255 } }),
/Expected object or string for background/
);
}); });
it('Invalid pdf.background property (object)', function () { it('Invalid pdfBackground property (object)', function () {
assert.throws( assert.throws(function () {
() => sharp({ pdf: { background: { red: 0, green: 255, blue: 0 } } }), sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } });
/Unable to parse color from object/ }, /Unable to parse color from object/);
);
assert.throws(
() => sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } }),
/Unable to parse color from object/
);
}); });
}); });

View File

@ -117,14 +117,14 @@ describe('JP2 output', () => {
it('valid JP2 oneshot value does not throw error', () => { it('valid JP2 oneshot value does not throw error', () => {
assert.doesNotThrow( assert.doesNotThrow(
() => sharp({ jp2: { oneshot: true } }) () => sharp(fixtures.inputJp2TileParts, { jp2Oneshot: true })
); );
}); });
it('invalid JP2 oneshot value throws error', () => { it('invalid JP2 oneshot value throws error', () => {
assert.throws( assert.throws(
() => sharp({ jp2: { oneshot: 'fail' } }), () => sharp(fixtures.inputJp2TileParts, { jp2Oneshot: 'fail' }),
/Expected boolean for jp2.oneshot but received fail of type string/ /Expected boolean for jp2Oneshot but received fail of type string/
); );
}); });
}); });

View File

@ -179,7 +179,7 @@ describe('libvips binaries', function () {
process.env.npm_config_arch = 's390x'; process.env.npm_config_arch = 's390x';
process.env.npm_config_libc = ''; process.env.npm_config_libc = '';
const locatorHash = libvips.yarnLocator(); const locatorHash = libvips.yarnLocator();
assert.strictEqual(locatorHash, '30afd744f9'); assert.strictEqual(locatorHash, 'e23686d7dd');
delete process.env.npm_config_platform; delete process.env.npm_config_platform;
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
delete process.env.npm_config_libc; delete process.env.npm_config_libc;

View File

@ -82,7 +82,6 @@ describe('Image metadata', function () {
assert.strictEqual(true, metadata.xmp instanceof Buffer); assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(12466, metadata.xmp.byteLength); assert.strictEqual(12466, metadata.xmp.byteLength);
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0); assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
assert(metadata.xmpAsString.startsWith('<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>'));
done(); done();
}); });
}); });
@ -107,8 +106,6 @@ describe('Image metadata', function () {
assert.strictEqual(3248, metadata.autoOrient.height); assert.strictEqual(3248, metadata.autoOrient.height);
assert.strictEqual('undefined', typeof metadata.exif); assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc); assert.strictEqual('undefined', typeof metadata.icc);
assert.strictEqual('undefined', typeof metadata.xmp);
assert.strictEqual('undefined', typeof metadata.xmpAsString);
assert.strictEqual('inch', metadata.resolutionUnit); assert.strictEqual('inch', metadata.resolutionUnit);
done(); done();
}); });
@ -1103,170 +1100,6 @@ describe('Image metadata', function () {
assert.strictEqual(exif2.Image.Software, 'sharp'); assert.strictEqual(exif2.Image.Software, 'sharp');
}); });
describe('XMP metadata tests', function () {
it('withMetadata preserves existing XMP metadata from input', async () => {
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
.resize(320, 240)
.withMetadata()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(true, metadata.xmp.length > 0);
// Check that XMP starts with the expected XML declaration
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
});
it('keepXmp preserves existing XMP metadata from input', async () => {
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
.resize(320, 240)
.keepXmp()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(true, metadata.xmp.length > 0);
// Check that XMP starts with the expected XML declaration
assert.strictEqual(metadata.xmp.indexOf(Buffer.from('<?xpacket begin="')), 0);
});
it('withXmp with custom XMP replaces existing XMP', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li>Test Creator</rdf:li></rdf:Seq></dc:creator><dc:title><rdf:Alt><rdf:li xml:lang="x-default">Test Title</rdf:li></rdf:Alt></dc:title></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpgWithIptcAndXmp)
.resize(320, 240)
.withXmp(customXmp)
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
// Check that the XMP contains our custom content
const xmpString = metadata.xmp.toString();
assert.strictEqual(true, xmpString.includes('Test Creator'));
assert.strictEqual(true, xmpString.includes('Test Title'));
});
it('withXmp with custom XMP buffer on image without existing XMP', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:description><rdf:Alt><rdf:li xml:lang="x-default">Added via Sharp</rdf:li></rdf:Alt></dc:description></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpg)
.resize(320, 240)
.withXmp(customXmp)
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
// Check that the XMP contains our custom content
const xmpString = metadata.xmp.toString();
assert.strictEqual(true, xmpString.includes('Added via Sharp'));
});
it('withXmp with valid XMP metadata for different image formats', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:subject><rdf:Bag><rdf:li>test</rdf:li><rdf:li>metadata</rdf:li></rdf:Bag></dc:subject></rdf:Description></rdf:RDF></x:xmpmeta>';
// Test with JPEG output
const jpegData = await sharp(fixtures.inputJpg)
.resize(100, 100)
.jpeg()
.withXmp(customXmp)
.toBuffer();
const jpegMetadata = await sharp(jpegData).metadata();
assert.strictEqual('object', typeof jpegMetadata.xmp);
assert.strictEqual(true, jpegMetadata.xmp instanceof Buffer);
assert.strictEqual(true, jpegMetadata.xmp.toString().includes('test'));
// Test with PNG output (PNG should also support XMP metadata)
const pngData = await sharp(fixtures.inputJpg)
.resize(100, 100)
.png()
.withXmp(customXmp)
.toBuffer();
const pngMetadata = await sharp(pngData).metadata();
// PNG format should preserve XMP metadata when using withXmp
assert.strictEqual('object', typeof pngMetadata.xmp);
assert.strictEqual(true, pngMetadata.xmp instanceof Buffer);
assert.strictEqual(true, pngMetadata.xmp.toString().includes('test'));
// Test with WebP output (WebP should also support XMP metadata)
const webpData = await sharp(fixtures.inputJpg)
.resize(100, 100)
.webp()
.withXmp(customXmp)
.toBuffer();
const webpMetadata = await sharp(webpData).metadata();
// WebP format should preserve XMP metadata when using withXmp
assert.strictEqual('object', typeof webpMetadata.xmp);
assert.strictEqual(true, webpMetadata.xmp instanceof Buffer);
assert.strictEqual(true, webpMetadata.xmp.toString().includes('test'));
});
it('XMP metadata persists through multiple operations', async () => {
const customXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier>persistent-test</dc:identifier></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpg)
.resize(320, 240)
.withXmp(customXmp)
.rotate(90)
.blur(1)
.sharpen()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
assert.strictEqual(true, metadata.xmp.toString().includes('persistent-test'));
});
it('withXmp XMP works with WebP format specifically', async () => {
const webpXmp = '<?xml version="1.0"?><x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:creator><rdf:Seq><rdf:li>WebP Creator</rdf:li></rdf:Seq></dc:creator><dc:format>image/webp</dc:format></rdf:Description></rdf:RDF></x:xmpmeta>';
const data = await sharp(fixtures.inputJpg)
.resize(120, 80)
.webp({ quality: 80 })
.withXmp(webpXmp)
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual('webp', metadata.format);
assert.strictEqual('object', typeof metadata.xmp);
assert.strictEqual(true, metadata.xmp instanceof Buffer);
const xmpString = metadata.xmp.toString();
assert.strictEqual(true, xmpString.includes('WebP Creator'));
assert.strictEqual(true, xmpString.includes('image/webp'));
});
it('withXmp XMP validation - non-string input', function () {
assert.throws(
() => sharp().withXmp(123),
/Expected non-empty string for xmp but received 123 of type number/
);
});
it('withXmp XMP validation - null input', function () {
assert.throws(
() => sharp().withXmp(null),
/Expected non-empty string for xmp but received null of type object/
);
});
it('withXmp XMP validation - empty string', function () {
assert.throws(
() => sharp().withXmp(''),
/Expected non-empty string for xmp/
);
});
});
describe('Invalid parameters', function () { describe('Invalid parameters', function () {
it('String orientation', function () { it('String orientation', function () {
assert.throws(function () { assert.throws(function () {

View File

@ -173,26 +173,6 @@ describe('Gaussian noise', function () {
}); });
}); });
it('animated noise', async () => {
const gif = await sharp({
create: {
width: 16,
height: 64,
pageHeight: 16,
channels: 3,
noise: { type: 'gaussian' }
}
})
.gif()
.toBuffer();
const { width, height, pages, delay } = await sharp(gif).metadata();
assert.strictEqual(width, 16);
assert.strictEqual(height, 16);
assert.strictEqual(pages, 4);
assert.strictEqual(delay.length, 4);
});
it('no create object properties specified', function () { it('no create object properties specified', function () {
assert.throws(function () { assert.throws(function () {
sharp({ sharp({
@ -279,29 +259,4 @@ describe('Gaussian noise', function () {
}); });
}); });
}); });
it('Invalid pageHeight', () => {
const create = {
width: 8,
height: 8,
channels: 4,
noise: { type: 'gaussian' }
};
assert.throws(
() => sharp({ create: { ...create, pageHeight: 'zoinks' } }),
/Expected positive integer for create\.pageHeight but received zoinks of type string/
);
assert.throws(
() => sharp({ create: { ...create, pageHeight: -1 } }),
/Expected positive integer for create\.pageHeight but received -1 of type number/
);
assert.throws(
() => sharp({ create: { ...create, pageHeight: 9 } }),
/Expected positive integer for create\.pageHeight but received 9 of type number/
);
assert.throws(
() => sharp({ create: { ...create, pageHeight: 3 } }),
/Expected create\.height 8 to be a multiple of create\.pageHeight 3/
);
});
}); });

View File

@ -55,35 +55,6 @@ describe('Raw pixel data', function () {
}); });
}); });
it('Invalid premultiplied', () => {
assert.throws(
() => sharp({ raw: { width: 1, height: 1, channels: 4, premultiplied: 'zoinks' } }),
/Expected boolean for raw\.premultiplied but received zoinks of type string/
);
});
it('Invalid pageHeight', () => {
const width = 8;
const height = 8;
const channels = 4;
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: 'zoinks' } }),
/Expected positive integer for raw\.pageHeight but received zoinks of type string/
);
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: -1 } }),
/Expected positive integer for raw\.pageHeight but received -1 of type number/
);
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: 9 } }),
/Expected positive integer for raw\.pageHeight but received 9 of type number/
);
assert.throws(
() => sharp({ raw: { width, height, channels, pageHeight: 3 } }),
/Expected raw\.height 8 to be a multiple of raw\.pageHeight 3/
);
});
it('RGB', function (done) { it('RGB', function (done) {
// Convert to raw pixel data // Convert to raw pixel data
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
@ -314,23 +285,6 @@ describe('Raw pixel data', function () {
} }
}); });
it('Animated', async () => {
const gif = await sharp(
Buffer.alloc(8),
{ raw: { width: 1, height: 2, channels: 4, pageHeight: 1 }, animated: true }
)
.gif({ keepDuplicateFrames: true })
.toBuffer();
console.log(await sharp(gif).metadata());
const { width, height, pages, delay } = await sharp(gif).metadata();
assert.strictEqual(width, 1);
assert.strictEqual(height, 1);
assert.strictEqual(pages, 2);
assert.strictEqual(delay.length, 2);
});
describe('16-bit roundtrip', () => { describe('16-bit roundtrip', () => {
it('grey', async () => { it('grey', async () => {
const grey = 42000; const grey = 42000;

View File

@ -139,41 +139,6 @@ describe('SVG input', function () {
assert.strictEqual(info.channels, 4); assert.strictEqual(info.channels, 4);
}); });
it('Can apply custom CSS', async () => {
const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="5" r="4" fill="green" />
</svg>`;
const stylesheet = 'circle { fill: red }';
const [r, g, b, a] = await sharp(Buffer.from(svg), { svg: { stylesheet } })
.extract({ left: 5, top: 5, width: 1, height: 1 })
.raw()
.toBuffer();
assert.deepEqual([r, g, b, a], [255, 0, 0, 255]);
});
it('Invalid stylesheet input option throws', () =>
assert.throws(
() => sharp({ svg: { stylesheet: 123 } }),
/Expected string for svg\.stylesheet but received 123 of type number/
)
);
it('Valid highBitdepth input option does not throw', () =>
assert.doesNotThrow(
() => sharp({ svg: { highBitdepth: true } })
)
);
it('Invalid highBitdepth input option throws', () =>
assert.throws(
() => sharp({ svg: { highBitdepth: 123 } }),
/Expected boolean for svg\.highBitdepth but received 123 of type number/
)
);
it('Fails to render SVG larger than 32767x32767', () => it('Fails to render SVG larger than 32767x32767', () =>
assert.rejects( assert.rejects(
() => sharp(Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="32768" height="1" />')).toBuffer(), () => sharp(Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="32768" height="1" />')).toBuffer(),