mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 02:30:12 +02:00
Compare commits
4 Commits
8c53d499f7
...
76995deefa
Author | SHA1 | Date | |
---|---|---|---|
|
76995deefa | ||
|
e688c53659 | ||
|
c4b1d80c35 | ||
|
f92540f134 |
@ -44,10 +44,6 @@ 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.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.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.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. |
|
||||
@ -82,6 +78,17 @@ 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.halign] | <code>string</code> | <code>"'left'"</code> | horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`). |
|
||||
| [options.join.valign] | <code>string</code> | <code>"'top'"</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**
|
||||
```js
|
||||
|
@ -113,15 +113,9 @@ Some image format libraries spawn additional threads,
|
||||
e.g. libaom manages its own 4 threads when encoding AVIF images,
|
||||
and these are independent of the value set here.
|
||||
|
||||
The maximum number of images that sharp can process in parallel
|
||||
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.
|
||||
:::note
|
||||
Further [control over performance](/performance) is available.
|
||||
:::
|
||||
|
||||
|
||||
**Returns**: <code>number</code> - concurrency
|
||||
|
@ -12,6 +12,10 @@ Requires libvips v8.17.0
|
||||
|
||||
* 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.
|
||||
|
||||
* Expose JPEG 2000 `oneshot` decoder option.
|
||||
|
@ -2,6 +2,38 @@
|
||||
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.
|
||||
|
||||
Greater libvips performance can be expected with caching enabled (default)
|
||||
@ -9,28 +41,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.
|
||||
|
||||
## Contenders
|
||||
### Contenders
|
||||
|
||||
* [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*".
|
||||
* [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.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)
|
||||
* Ubuntu 24.10 [fad5ba7223f8](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-fad5ba7223f8d87179dfa23211d31845d47e07a474ac31ad5258afb606523c0d)
|
||||
* 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)
|
||||
* Ubuntu 24.10 [133f2e05cb69](https://hub.docker.com/layers/library/ubuntu/24.10/images/sha256-133f2e05cb6958c3ce7ec870fd5a864558ba780fb7062315b51a23670bff7e76)
|
||||
* Node.js 22.14.0
|
||||
|
||||
## Task: JPEG
|
||||
### Task: JPEG
|
||||
|
||||
Decompress a 2725x2225 JPEG image,
|
||||
resize to 720x588 using Lanczos 3 resampling (where available),
|
||||
@ -62,7 +94,7 @@ Note: jimp does not support Lanczos 3, bicubic resampling used instead.
|
||||
| sharp | file | file | 48.42 | 22.7 |
|
||||
| sharp | buffer | buffer | 50.16 | 23.6 |
|
||||
|
||||
## Task: PNG
|
||||
### Task: PNG
|
||||
|
||||
Decompress a 2048x1536 RGBA PNG image,
|
||||
premultiply the alpha channel,
|
||||
@ -72,7 +104,7 @@ and without adaptive filtering.
|
||||
|
||||
Note: jimp does not support premultiply/unpremultiply.
|
||||
|
||||
### Results: PNG (AMD64)
|
||||
#### Results: PNG (AMD64)
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
@ -82,7 +114,7 @@ Note: jimp does not support premultiply/unpremultiply.
|
||||
| sharp | file | file | 27.93 | 3.2 |
|
||||
| sharp | buffer | buffer | 28.69 | 3.3 |
|
||||
|
||||
### Results: PNG (ARM64)
|
||||
#### Results: PNG (ARM64)
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
|
@ -153,10 +153,6 @@ const debuglog = util.debuglog('sharp');
|
||||
* @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.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 {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||
* @param {number} [options.raw.width] - integral number of pixels wide.
|
||||
@ -192,7 +188,17 @@ 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} [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 {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}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
|
47
lib/index.d.ts
vendored
47
lib/index.d.ts
vendored
@ -1003,14 +1003,22 @@ declare namespace sharp {
|
||||
pages?: number | undefined;
|
||||
/** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */
|
||||
page?: number | undefined;
|
||||
/** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default -1) */
|
||||
/** TIFF specific input options */
|
||||
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;
|
||||
/** Level to extract from a multi-level input (OpenSlide), zero based. (optional, default 0) */
|
||||
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. */
|
||||
/** Deprecated: use pdf.background instead */
|
||||
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;
|
||||
/** Deprecated: use openSlide.level instead */
|
||||
level?: number | undefined;
|
||||
/** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
|
||||
animated?: boolean | undefined;
|
||||
/** Describes raw pixel input image data. See raw() for pixel ordering. */
|
||||
@ -1116,6 +1124,33 @@ declare namespace sharp {
|
||||
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 {
|
||||
[k: string]: string;
|
||||
}
|
||||
|
75
lib/input.js
75
lib/input.js
@ -22,14 +22,27 @@ const align = {
|
||||
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', 'level', 'pdfBackground', 'subifd'
|
||||
];
|
||||
|
||||
/**
|
||||
* Extract input options, if any, from an object.
|
||||
* @private
|
||||
*/
|
||||
function _inputOptionsFromObject (obj) {
|
||||
const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } = obj;
|
||||
return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot].some(is.defined)
|
||||
? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot }
|
||||
const params = inputStreamParameters
|
||||
.filter(p => is.defined(obj[p]))
|
||||
.map(p => ([p, obj[p]]));
|
||||
return params.length
|
||||
? Object.fromEntries(params)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@ -230,32 +243,66 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
|
||||
}
|
||||
}
|
||||
// Multi-level input (OpenSlide)
|
||||
if (is.defined(inputOptions.level)) {
|
||||
// OpenSlide specific options
|
||||
if (is.object(inputOptions.openSlide) && is.defined(inputOptions.openSlide.level)) {
|
||||
if (is.integer(inputOptions.openSlide.level) && is.inRange(inputOptions.openSlide.level, 0, 256)) {
|
||||
inputDescriptor.level = 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)) {
|
||||
inputDescriptor.level = inputOptions.level;
|
||||
} else {
|
||||
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
|
||||
}
|
||||
}
|
||||
// Sub Image File Directory (TIFF)
|
||||
if (is.defined(inputOptions.subifd)) {
|
||||
// TIFF specific options
|
||||
if (is.object(inputOptions.tiff) && is.defined(inputOptions.tiff.subifd)) {
|
||||
if (is.integer(inputOptions.tiff.subifd) && is.inRange(inputOptions.tiff.subifd, -1, 100000)) {
|
||||
inputDescriptor.subifd = 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)) {
|
||||
inputDescriptor.subifd = inputOptions.subifd;
|
||||
} else {
|
||||
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
|
||||
}
|
||||
}
|
||||
// PDF background colour
|
||||
if (is.defined(inputOptions.pdfBackground)) {
|
||||
// SVG specific options
|
||||
if (is.object(inputOptions.svg)) {
|
||||
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);
|
||||
}
|
||||
// JP2 oneshot
|
||||
if (is.defined(inputOptions.jp2Oneshot)) {
|
||||
if (is.bool(inputOptions.jp2Oneshot)) {
|
||||
inputDescriptor.jp2Oneshot = inputOptions.jp2Oneshot;
|
||||
// JPEG 2000 specific options
|
||||
if (is.object(inputOptions.jp2) && is.defined(inputOptions.jp2.oneshot)) {
|
||||
if (is.bool(inputOptions.jp2.oneshot)) {
|
||||
inputDescriptor.jp2Oneshot = inputOptions.jp2.oneshot;
|
||||
} else {
|
||||
throw is.invalidParameterError('jp2Oneshot', 'boolean', inputOptions.jp2Oneshot);
|
||||
throw is.invalidParameterError('jp2.oneshot', 'boolean', inputOptions.jp2.oneshot);
|
||||
}
|
||||
}
|
||||
// Create new image
|
||||
|
@ -135,15 +135,9 @@ cache(true);
|
||||
* e.g. libaom manages its own 4 threads when encoding AVIF images,
|
||||
* and these are independent of the value set here.
|
||||
*
|
||||
* The maximum number of images that sharp can process in parallel
|
||||
* 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.
|
||||
* :::note
|
||||
* Further {@link /performance|control over performance} is available.
|
||||
* :::
|
||||
*
|
||||
* @example
|
||||
* const threads = sharp.concurrency(); // 4
|
||||
|
@ -101,6 +101,13 @@ namespace sharp {
|
||||
if (HasAttr(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)
|
||||
if (HasAttr(input, "level")) {
|
||||
descriptor->level = AttrAsUint32(input, "level");
|
||||
@ -429,6 +436,10 @@ namespace sharp {
|
||||
option->set("n", descriptor->pages);
|
||||
option->set("page", descriptor->page);
|
||||
}
|
||||
if (imageType == ImageType::SVG) {
|
||||
option->set("stylesheet", descriptor->svgStylesheet.data());
|
||||
option->set("high_bitdepth", descriptor->svgHighBitdepth);
|
||||
}
|
||||
if (imageType == ImageType::OPENSLIDE) {
|
||||
option->set("level", descriptor->level);
|
||||
}
|
||||
|
@ -77,6 +77,8 @@ namespace sharp {
|
||||
std::vector<double> joinBackground;
|
||||
VipsAlign joinHalign;
|
||||
VipsAlign joinValign;
|
||||
std::string svgStylesheet;
|
||||
bool svgHighBitdepth;
|
||||
std::vector<double> pdfBackground;
|
||||
bool jp2Oneshot;
|
||||
|
||||
@ -121,6 +123,7 @@ namespace sharp {
|
||||
joinBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
joinHalign(VIPS_ALIGN_LOW),
|
||||
joinValign(VIPS_ALIGN_LOW),
|
||||
svgHighBitdepth(false),
|
||||
pdfBackground{ 255.0, 255.0, 255.0, 255.0 },
|
||||
jp2Oneshot(false) {}
|
||||
};
|
||||
|
@ -276,6 +276,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
} else if (inputImageType == sharp::ImageType::SVG) {
|
||||
option->set("unlimited", baton->input->unlimited);
|
||||
option->set("dpi", baton->input->density);
|
||||
option->set("stylesheet", baton->input->svgStylesheet.data());
|
||||
option->set("high_bitdepth", baton->input->svgHighBitdepth);
|
||||
|
||||
if (baton->input->buffer != nullptr) {
|
||||
// Reload SVG buffer
|
||||
|
@ -435,9 +435,6 @@ sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile
|
||||
// Support `unlimited` input option
|
||||
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
|
||||
sharp({
|
||||
create: {
|
||||
@ -720,13 +717,22 @@ sharp(input).composite([
|
||||
}
|
||||
])
|
||||
|
||||
// Support format-specific input options
|
||||
const colour: sharp.Colour = '#fff';
|
||||
const color: sharp.Color = '#fff';
|
||||
sharp({ pdfBackground: colour });
|
||||
sharp({ pdfBackground: color });
|
||||
|
||||
sharp({ jp2Oneshot: true });
|
||||
sharp({ jp2Oneshot: false });
|
||||
sharp({ pdf: { background: colour } });
|
||||
sharp({ pdf: { background: 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 }});
|
||||
|
||||
sharp({ autoOrient: true });
|
||||
sharp({ autoOrient: false });
|
||||
|
103
test/unit/io.js
103
test/unit/io.js
@ -867,52 +867,91 @@ describe('Input/output', function () {
|
||||
sharp({ pages: '1' });
|
||||
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
|
||||
});
|
||||
it('Valid level property', function () {
|
||||
it('Valid openSlide.level property', function () {
|
||||
sharp({ openSlide: { level: 1 } });
|
||||
sharp({ level: 1 });
|
||||
});
|
||||
it('Invalid level property (string) throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ level: '1' });
|
||||
}, /Expected integer between 0 and 256 for level but received 1 of type string/);
|
||||
it('Invalid openSlide.level property (string) throws', function () {
|
||||
assert.throws(
|
||||
() => sharp({ openSlide: { level: '1' } }),
|
||||
/Expected integer between 0 and 256 for openSlide.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 level property (negative) throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ level: -1 });
|
||||
}, /Expected integer between 0 and 256 for level but received -1 of type number/);
|
||||
it('Invalid openSlide.level property (negative) throws', function () {
|
||||
assert.throws(
|
||||
() => sharp({ openSlide: { level: -1 } }),
|
||||
/Expected integer between 0 and 256 for openSlide\.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 subifd property', function () {
|
||||
it('Valid tiff.subifd property', function () {
|
||||
sharp({ tiff: { subifd: 1 } });
|
||||
sharp({ subifd: 1 });
|
||||
});
|
||||
it('Invalid subifd property (string) throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ subifd: '1' });
|
||||
}, /Expected integer between -1 and 100000 for subifd but received 1 of type string/);
|
||||
it('Invalid tiff.subifd property (string) throws', function () {
|
||||
assert.throws(
|
||||
() => sharp({ tiff: { subifd: '1' } }),
|
||||
/Expected integer between -1 and 100000 for tiff\.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 subifd property (float) throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ subifd: 1.2 });
|
||||
}, /Expected integer between -1 and 100000 for subifd but received 1.2 of type number/);
|
||||
it('Invalid tiff.subifd property (float) throws', function () {
|
||||
assert.throws(
|
||||
() => sharp({ tiff: { subifd: 1.2 } }),
|
||||
/Expected integer between -1 and 100000 for tiff\.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 pdfBackground property (string)', function () {
|
||||
it('Valid pdf.background property (string)', function () {
|
||||
sharp({ pdf: { background: '#00ff00' } });
|
||||
sharp({ pdfBackground: '#00ff00' });
|
||||
});
|
||||
it('Valid pdfBackground property (object)', function () {
|
||||
it('Valid pdf.background property (object)', function () {
|
||||
sharp({ pdf: { background: { r: 0, g: 255, b: 0 } } });
|
||||
sharp({ pdfBackground: { r: 0, g: 255, b: 0 } });
|
||||
});
|
||||
it('Invalid pdfBackground property (string) throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ pdfBackground: '00ff00' });
|
||||
}, /Unable to parse color from string/);
|
||||
it('Invalid pdf.background property (string) throws', function () {
|
||||
assert.throws(
|
||||
() => sharp({ pdf: { background: '00ff00' } }),
|
||||
/Unable to parse color from string/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ pdfBackground: '00ff00' }),
|
||||
/Unable to parse color from string/
|
||||
);
|
||||
});
|
||||
it('Invalid pdfBackground property (number) throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ pdfBackground: 255 });
|
||||
}, /Expected object or string for background/);
|
||||
it('Invalid pdf.background property (number) throws', function () {
|
||||
assert.throws(
|
||||
() => sharp({ pdf: { background: 255 } }),
|
||||
/Expected object or string for background/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ pdf: { background: 255 } }),
|
||||
/Expected object or string for background/
|
||||
);
|
||||
});
|
||||
it('Invalid pdfBackground property (object)', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } });
|
||||
}, /Unable to parse color from object/);
|
||||
it('Invalid pdf.background property (object)', function () {
|
||||
assert.throws(
|
||||
() => sharp({ pdf: { background: { red: 0, green: 255, blue: 0 } } }),
|
||||
/Unable to parse color from object/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ pdfBackground: { red: 0, green: 255, blue: 0 } }),
|
||||
/Unable to parse color from object/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -117,14 +117,14 @@ describe('JP2 output', () => {
|
||||
|
||||
it('valid JP2 oneshot value does not throw error', () => {
|
||||
assert.doesNotThrow(
|
||||
() => sharp(fixtures.inputJp2TileParts, { jp2Oneshot: true })
|
||||
() => sharp({ jp2: { oneshot: true } })
|
||||
);
|
||||
});
|
||||
|
||||
it('invalid JP2 oneshot value throws error', () => {
|
||||
assert.throws(
|
||||
() => sharp(fixtures.inputJp2TileParts, { jp2Oneshot: 'fail' }),
|
||||
/Expected boolean for jp2Oneshot but received fail of type string/
|
||||
() => sharp({ jp2: { oneshot: 'fail' } }),
|
||||
/Expected boolean for jp2.oneshot but received fail of type string/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -139,6 +139,41 @@ describe('SVG input', function () {
|
||||
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', () =>
|
||||
assert.rejects(
|
||||
() => sharp(Buffer.from('<svg xmlns="http://www.w3.org/2000/svg" width="32768" height="1" />')).toBuffer(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user