sharp 0.34.3-rc.0 → 0.34.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -153,10 +153,6 @@ const debuglog = util.debuglog('sharp');
153
153
  * @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
154
154
  * @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages.
155
155
  * @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based.
156
- * @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
157
- * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
158
- * @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.
159
- * @param {boolean} [options.jp2Oneshot=false] - Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility.
160
156
  * @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`.
161
157
  * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
162
158
  * @param {number} [options.raw.width] - integral number of pixels wide.
@@ -164,15 +160,17 @@ const debuglog = util.debuglog('sharp');
164
160
  * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
165
161
  * @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
166
162
  * to avoid sharp premultiplying the image. (optional, default `false`)
163
+ * @param {number} [options.raw.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`.
167
164
  * @param {Object} [options.create] - describes a new image to be created.
168
165
  * @param {number} [options.create.width] - integral number of pixels wide.
169
166
  * @param {number} [options.create.height] - integral number of pixels high.
170
167
  * @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
171
168
  * @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.
169
+ * @param {number} [options.create.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `create.height`.
172
170
  * @param {Object} [options.create.noise] - describes a noise to be created.
173
171
  * @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
174
- * @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
175
- * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
172
+ * @param {number} [options.create.noise.mean=128] - Mean value of pixels in the generated noise.
173
+ * @param {number} [options.create.noise.sigma=30] - Standard deviation of pixel values in the generated noise.
176
174
  * @param {Object} [options.text] - describes a new text image to be created.
177
175
  * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
178
176
  * @param {string} [options.text.font] - font name to render with.
@@ -192,7 +190,17 @@ const debuglog = util.debuglog('sharp');
192
190
  * @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.
193
191
  * @param {string} [options.join.halign='left'] - horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`).
194
192
  * @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`).
195
- *
193
+ * @param {Object} [options.tiff] - Describes TIFF specific options.
194
+ * @param {number} [options.tiff.subifd=-1] - Sub Image File Directory to extract for OME-TIFF, defaults to main image.
195
+ * @param {Object} [options.svg] - Describes SVG specific options.
196
+ * @param {string} [options.svg.stylesheet] - Custom CSS for SVG input, applied with a User Origin during the CSS cascade.
197
+ * @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.
198
+ * @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.
199
+ * @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.
200
+ * @param {Object} [options.openSlide] - Describes OpenSlide specific options. Requires the use of a globally-installed libvips compiled with support for OpenSlide.
201
+ * @param {number} [options.openSlide.level=0] - Level to extract from a multi-level input, zero based.
202
+ * @param {Object} [options.jp2] - Describes JPEG 2000 specific options. Requires the use of a globally-installed libvips compiled with support for OpenJPEG.
203
+ * @param {boolean} [options.jp2.oneshot=false] - Set to `true` to decode tiled JPEG 2000 images in a single operation, improving compatibility.
196
204
  * @returns {Sharp}
197
205
  * @throws {Error} Invalid parameters
198
206
  */
@@ -298,6 +306,7 @@ const Sharp = function (input, options) {
298
306
  withIccProfile: '',
299
307
  withExif: {},
300
308
  withExifMerge: true,
309
+ withXmp: '',
301
310
  resolveWithObject: false,
302
311
  loop: -1,
303
312
  delay: [],
@@ -338,6 +347,7 @@ const Sharp = function (input, options) {
338
347
  gifDither: 1,
339
348
  gifInterFrameMaxError: 0,
340
349
  gifInterPaletteMaxError: 3,
350
+ gifKeepDuplicateFrames: false,
341
351
  gifReuse: true,
342
352
  gifProgressive: false,
343
353
  tiffQuality: 80,
package/lib/index.d.ts CHANGED
@@ -419,7 +419,7 @@ declare namespace sharp {
419
419
  * @returns {Sharp}
420
420
  */
421
421
  autoOrient(): Sharp
422
-
422
+
423
423
  /**
424
424
  * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
425
425
  * The use of flip implies the removal of the EXIF Orientation tag, if any.
@@ -730,6 +730,20 @@ declare namespace sharp {
730
730
  */
731
731
  withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp;
732
732
 
733
+ /**
734
+ * Keep all XMP metadata from the input image in the output image.
735
+ * @returns A sharp instance that can be used to chain operations
736
+ */
737
+ keepXmp(): Sharp;
738
+
739
+ /**
740
+ * Set XMP metadata in the output image.
741
+ * @param {string} xmp - String containing XMP metadata to be embedded in the output image.
742
+ * @returns A sharp instance that can be used to chain operations
743
+ * @throws {Error} Invalid parameters
744
+ */
745
+ withXmp(xmp: string): Sharp;
746
+
733
747
  /**
734
748
  * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
735
749
  * The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
@@ -1003,14 +1017,22 @@ declare namespace sharp {
1003
1017
  pages?: number | undefined;
1004
1018
  /** Page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default 0) */
1005
1019
  page?: number | undefined;
1006
- /** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default -1) */
1020
+ /** TIFF specific input options */
1021
+ tiff?: TiffInputOptions | undefined;
1022
+ /** SVG specific input options */
1023
+ svg?: SvgInputOptions | undefined;
1024
+ /** PDF specific input options */
1025
+ pdf?: PdfInputOptions | undefined;
1026
+ /** OpenSlide specific input options */
1027
+ openSlide?: OpenSlideInputOptions | undefined;
1028
+ /** JPEG 2000 specific input options */
1029
+ jp2?: Jp2InputOptions | undefined;
1030
+ /** Deprecated: use tiff.subifd instead */
1007
1031
  subifd?: number | undefined;
1008
- /** Level to extract from a multi-level input (OpenSlide), zero based. (optional, default 0) */
1009
- level?: number | undefined;
1010
- /** 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. */
1032
+ /** Deprecated: use pdf.background instead */
1011
1033
  pdfBackground?: Colour | Color | undefined;
1012
- /** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) */
1013
- jp2Oneshot?: boolean | undefined;
1034
+ /** Deprecated: use openSlide.level instead */
1035
+ level?: number | undefined;
1014
1036
  /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
1015
1037
  animated?: boolean | undefined;
1016
1038
  /** Describes raw pixel input image data. See raw() for pixel ordering. */
@@ -1053,6 +1075,8 @@ declare namespace sharp {
1053
1075
  interface CreateRaw extends Raw {
1054
1076
  /** Specifies that the raw input has already been premultiplied, set to true to avoid sharp premultiplying the image. (optional, default false) */
1055
1077
  premultiplied?: boolean | undefined;
1078
+ /** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
1079
+ pageHeight?: number | undefined;
1056
1080
  }
1057
1081
 
1058
1082
  type CreateChannels = 3 | 4;
@@ -1068,6 +1092,9 @@ declare namespace sharp {
1068
1092
  background: Colour | Color;
1069
1093
  /** Describes a noise to be created. */
1070
1094
  noise?: Noise | undefined;
1095
+ /** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
1096
+ pageHeight?: number | undefined;
1097
+
1071
1098
  }
1072
1099
 
1073
1100
  interface CreateText {
@@ -1116,6 +1143,33 @@ declare namespace sharp {
1116
1143
  valign?: VerticalAlignment | undefined;
1117
1144
  }
1118
1145
 
1146
+ interface TiffInputOptions {
1147
+ /** Sub Image File Directory to extract, defaults to main image. Use -1 for all subifds. */
1148
+ subifd?: number | undefined;
1149
+ }
1150
+
1151
+ interface SvgInputOptions {
1152
+ /** Custom CSS for SVG input, applied with a User Origin during the CSS cascade. */
1153
+ stylesheet?: string | undefined;
1154
+ /** Set to `true` to render SVG input at 32-bits per channel (128-bit) instead of 8-bits per channel (32-bit) RGBA. */
1155
+ highBitdepth?: boolean | undefined;
1156
+ }
1157
+
1158
+ interface PdfInputOptions {
1159
+ /** 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. */
1160
+ background?: Colour | Color | undefined;
1161
+ }
1162
+
1163
+ interface OpenSlideInputOptions {
1164
+ /** Level to extract from a multi-level input, zero based. (optional, default 0) */
1165
+ level?: number | undefined;
1166
+ }
1167
+
1168
+ interface Jp2InputOptions {
1169
+ /** Set to `true` to load JPEG 2000 images using [oneshot mode](https://github.com/libvips/libvips/issues/4205) */
1170
+ oneshot?: boolean | undefined;
1171
+ }
1172
+
1119
1173
  interface ExifDir {
1120
1174
  [k: string]: string;
1121
1175
  }
@@ -1200,6 +1254,8 @@ declare namespace sharp {
1200
1254
  iptc?: Buffer | undefined;
1201
1255
  /** Buffer containing raw XMP data, if present */
1202
1256
  xmp?: Buffer | undefined;
1257
+ /** String containing XMP data, if valid UTF-8 */
1258
+ xmpAsString?: string | undefined;
1203
1259
  /** Buffer containing raw TIFFTAG_PHOTOSHOP data, if present */
1204
1260
  tifftagPhotoshop?: Buffer | undefined;
1205
1261
  /** The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC) */
@@ -1392,9 +1448,11 @@ declare namespace sharp {
1392
1448
  /** Level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default 1.0) */
1393
1449
  dither?: number | undefined;
1394
1450
  /** Maximum inter-frame error for transparency, between 0 (lossless) and 32 (optional, default 0) */
1395
- interFrameMaxError?: number;
1451
+ interFrameMaxError?: number | undefined;
1396
1452
  /** Maximum inter-palette error for palette reuse, between 0 and 256 (optional, default 3) */
1397
- interPaletteMaxError?: number;
1453
+ interPaletteMaxError?: number | undefined;
1454
+ /** Keep duplicate frames in the output instead of combining them (optional, default false) */
1455
+ keepDuplicateFrames?: boolean | undefined;
1398
1456
  }
1399
1457
 
1400
1458
  interface TiffOptions extends OutputOptions {
@@ -1512,7 +1570,7 @@ declare namespace sharp {
1512
1570
 
1513
1571
  interface Noise {
1514
1572
  /** type of generated noise, currently only gaussian is supported. */
1515
- type?: 'gaussian' | undefined;
1573
+ type: 'gaussian';
1516
1574
  /** mean of pixels in generated noise. */
1517
1575
  mean?: number | undefined;
1518
1576
  /** standard deviation of pixels in generated noise. */
@@ -1729,6 +1787,8 @@ declare namespace sharp {
1729
1787
  mitchell: 'mitchell';
1730
1788
  lanczos2: 'lanczos2';
1731
1789
  lanczos3: 'lanczos3';
1790
+ mks2013: 'mks2013';
1791
+ mks2021: 'mks2021';
1732
1792
  }
1733
1793
 
1734
1794
  interface PresetEnum {
@@ -1846,6 +1906,7 @@ declare namespace sharp {
1846
1906
 
1847
1907
  interface FormatEnum {
1848
1908
  avif: AvailableFormatInfo;
1909
+ dcraw: AvailableFormatInfo;
1849
1910
  dz: AvailableFormatInfo;
1850
1911
  exr: AvailableFormatInfo;
1851
1912
  fits: AvailableFormatInfo;
package/lib/input.js CHANGED
@@ -22,14 +22,27 @@ const align = {
22
22
  high: 'high'
23
23
  };
24
24
 
25
+ const inputStreamParameters = [
26
+ // Limits and error handling
27
+ 'failOn', 'limitInputPixels', 'unlimited',
28
+ // Format-generic
29
+ 'animated', 'autoOrient', 'density', 'ignoreIcc', 'page', 'pages', 'sequentialRead',
30
+ // Format-specific
31
+ 'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
32
+ // Deprecated
33
+ 'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
34
+ ];
35
+
25
36
  /**
26
37
  * Extract input options, if any, from an object.
27
38
  * @private
28
39
  */
29
40
  function _inputOptionsFromObject (obj) {
30
- const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot } = obj;
31
- return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot].some(is.defined)
32
- ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient, jp2Oneshot }
41
+ const params = inputStreamParameters
42
+ .filter(p => is.defined(obj[p]))
43
+ .map(p => ([p, obj[p]]));
44
+ return params.length
45
+ ? Object.fromEntries(params)
33
46
  : undefined;
34
47
  }
35
48
 
@@ -172,8 +185,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
172
185
  inputDescriptor.rawWidth = inputOptions.raw.width;
173
186
  inputDescriptor.rawHeight = inputOptions.raw.height;
174
187
  inputDescriptor.rawChannels = inputOptions.raw.channels;
175
- inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
176
-
177
188
  switch (input.constructor) {
178
189
  case Uint8Array:
179
190
  case Uint8ClampedArray:
@@ -207,6 +218,25 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
207
218
  } else {
208
219
  throw new Error('Expected width, height and channels for raw pixel input');
209
220
  }
221
+ inputDescriptor.rawPremultiplied = false;
222
+ if (is.defined(inputOptions.raw.premultiplied)) {
223
+ if (is.bool(inputOptions.raw.premultiplied)) {
224
+ inputDescriptor.rawPremultiplied = inputOptions.raw.premultiplied;
225
+ } else {
226
+ throw is.invalidParameterError('raw.premultiplied', 'boolean', inputOptions.raw.premultiplied);
227
+ }
228
+ }
229
+ inputDescriptor.rawPageHeight = 0;
230
+ if (is.defined(inputOptions.raw.pageHeight)) {
231
+ if (is.integer(inputOptions.raw.pageHeight) && inputOptions.raw.pageHeight > 0 && inputOptions.raw.pageHeight <= inputOptions.raw.height) {
232
+ if (inputOptions.raw.height % inputOptions.raw.pageHeight !== 0) {
233
+ throw new Error(`Expected raw.height ${inputOptions.raw.height} to be a multiple of raw.pageHeight ${inputOptions.raw.pageHeight}`);
234
+ }
235
+ inputDescriptor.rawPageHeight = inputOptions.raw.pageHeight;
236
+ } else {
237
+ throw is.invalidParameterError('raw.pageHeight', 'positive integer', inputOptions.raw.pageHeight);
238
+ }
239
+ }
210
240
  }
211
241
  // Multi-page input (GIF, TIFF, PDF)
212
242
  if (is.defined(inputOptions.animated)) {
@@ -230,32 +260,66 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
230
260
  throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
231
261
  }
232
262
  }
233
- // Multi-level input (OpenSlide)
234
- if (is.defined(inputOptions.level)) {
263
+ // OpenSlide specific options
264
+ if (is.object(inputOptions.openSlide) && is.defined(inputOptions.openSlide.level)) {
265
+ if (is.integer(inputOptions.openSlide.level) && is.inRange(inputOptions.openSlide.level, 0, 256)) {
266
+ inputDescriptor.openSlideLevel = inputOptions.openSlide.level;
267
+ } else {
268
+ throw is.invalidParameterError('openSlide.level', 'integer between 0 and 256', inputOptions.openSlide.level);
269
+ }
270
+ } else if (is.defined(inputOptions.level)) {
271
+ // Deprecated
235
272
  if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
236
- inputDescriptor.level = inputOptions.level;
273
+ inputDescriptor.openSlideLevel = inputOptions.level;
237
274
  } else {
238
275
  throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
239
276
  }
240
277
  }
241
- // Sub Image File Directory (TIFF)
242
- if (is.defined(inputOptions.subifd)) {
278
+ // TIFF specific options
279
+ if (is.object(inputOptions.tiff) && is.defined(inputOptions.tiff.subifd)) {
280
+ if (is.integer(inputOptions.tiff.subifd) && is.inRange(inputOptions.tiff.subifd, -1, 100000)) {
281
+ inputDescriptor.tiffSubifd = inputOptions.tiff.subifd;
282
+ } else {
283
+ throw is.invalidParameterError('tiff.subifd', 'integer between -1 and 100000', inputOptions.tiff.subifd);
284
+ }
285
+ } else if (is.defined(inputOptions.subifd)) {
286
+ // Deprecated
243
287
  if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
244
- inputDescriptor.subifd = inputOptions.subifd;
288
+ inputDescriptor.tiffSubifd = inputOptions.subifd;
245
289
  } else {
246
290
  throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
247
291
  }
248
292
  }
249
- // PDF background colour
250
- if (is.defined(inputOptions.pdfBackground)) {
293
+ // SVG specific options
294
+ if (is.object(inputOptions.svg)) {
295
+ if (is.defined(inputOptions.svg.stylesheet)) {
296
+ if (is.string(inputOptions.svg.stylesheet)) {
297
+ inputDescriptor.svgStylesheet = inputOptions.svg.stylesheet;
298
+ } else {
299
+ throw is.invalidParameterError('svg.stylesheet', 'string', inputOptions.svg.stylesheet);
300
+ }
301
+ }
302
+ if (is.defined(inputOptions.svg.highBitdepth)) {
303
+ if (is.bool(inputOptions.svg.highBitdepth)) {
304
+ inputDescriptor.svgHighBitdepth = inputOptions.svg.highBitdepth;
305
+ } else {
306
+ throw is.invalidParameterError('svg.highBitdepth', 'boolean', inputOptions.svg.highBitdepth);
307
+ }
308
+ }
309
+ }
310
+ // PDF specific options
311
+ if (is.object(inputOptions.pdf) && is.defined(inputOptions.pdf.background)) {
312
+ inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdf.background);
313
+ } else if (is.defined(inputOptions.pdfBackground)) {
314
+ // Deprecated
251
315
  inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground);
252
316
  }
253
- // JP2 oneshot
254
- if (is.defined(inputOptions.jp2Oneshot)) {
255
- if (is.bool(inputOptions.jp2Oneshot)) {
256
- inputDescriptor.jp2Oneshot = inputOptions.jp2Oneshot;
317
+ // JPEG 2000 specific options
318
+ if (is.object(inputOptions.jp2) && is.defined(inputOptions.jp2.oneshot)) {
319
+ if (is.bool(inputOptions.jp2.oneshot)) {
320
+ inputDescriptor.jp2Oneshot = inputOptions.jp2.oneshot;
257
321
  } else {
258
- throw is.invalidParameterError('jp2Oneshot', 'boolean', inputOptions.jp2Oneshot);
322
+ throw is.invalidParameterError('jp2.oneshot', 'boolean', inputOptions.jp2.oneshot);
259
323
  }
260
324
  }
261
325
  // Create new image
@@ -269,27 +333,44 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
269
333
  inputDescriptor.createWidth = inputOptions.create.width;
270
334
  inputDescriptor.createHeight = inputOptions.create.height;
271
335
  inputDescriptor.createChannels = inputOptions.create.channels;
336
+ inputDescriptor.createPageHeight = 0;
337
+ if (is.defined(inputOptions.create.pageHeight)) {
338
+ if (is.integer(inputOptions.create.pageHeight) && inputOptions.create.pageHeight > 0 && inputOptions.create.pageHeight <= inputOptions.create.height) {
339
+ if (inputOptions.create.height % inputOptions.create.pageHeight !== 0) {
340
+ throw new Error(`Expected create.height ${inputOptions.create.height} to be a multiple of create.pageHeight ${inputOptions.create.pageHeight}`);
341
+ }
342
+ inputDescriptor.createPageHeight = inputOptions.create.pageHeight;
343
+ } else {
344
+ throw is.invalidParameterError('create.pageHeight', 'positive integer', inputOptions.create.pageHeight);
345
+ }
346
+ }
272
347
  // Noise
273
348
  if (is.defined(inputOptions.create.noise)) {
274
349
  if (!is.object(inputOptions.create.noise)) {
275
350
  throw new Error('Expected noise to be an object');
276
351
  }
277
- if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
352
+ if (inputOptions.create.noise.type !== 'gaussian') {
278
353
  throw new Error('Only gaussian noise is supported at the moment');
279
354
  }
355
+ inputDescriptor.createNoiseType = inputOptions.create.noise.type;
280
356
  if (!is.inRange(inputOptions.create.channels, 1, 4)) {
281
357
  throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
282
358
  }
283
- inputDescriptor.createNoiseType = inputOptions.create.noise.type;
284
- if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
285
- inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
286
- } else {
287
- throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
359
+ inputDescriptor.createNoiseMean = 128;
360
+ if (is.defined(inputOptions.create.noise.mean)) {
361
+ if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
362
+ inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
363
+ } else {
364
+ throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
365
+ }
288
366
  }
289
- if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
290
- inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
291
- } else {
292
- throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
367
+ inputDescriptor.createNoiseSigma = 30;
368
+ if (is.defined(inputOptions.create.noise.sigma)) {
369
+ if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
370
+ inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
371
+ } else {
372
+ throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
373
+ }
293
374
  }
294
375
  } else if (is.defined(inputOptions.create.background)) {
295
376
  if (!is.inRange(inputOptions.create.channels, 3, 4)) {
@@ -524,6 +605,7 @@ function _isStreamInput () {
524
605
  * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
525
606
  * - `iptc`: Buffer containing raw IPTC data, if present
526
607
  * - `xmp`: Buffer containing raw XMP data, if present
608
+ * - `xmpAsString`: String containing XMP data, if valid UTF-8.
527
609
  * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
528
610
  * - `formatMagick`: String containing format for images loaded via *magick
529
611
  * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
package/lib/output.js CHANGED
@@ -312,6 +312,59 @@ function withIccProfile (icc, options) {
312
312
  return this;
313
313
  }
314
314
 
315
+ /**
316
+ * Keep XMP metadata from the input image in the output image.
317
+ *
318
+ * @since 0.34.3
319
+ *
320
+ * @example
321
+ * const outputWithXmp = await sharp(inputWithXmp)
322
+ * .keepXmp()
323
+ * .toBuffer();
324
+ *
325
+ * @returns {Sharp}
326
+ */
327
+ function keepXmp () {
328
+ this.options.keepMetadata |= 0b00010;
329
+ return this;
330
+ }
331
+
332
+ /**
333
+ * Set XMP metadata in the output image.
334
+ *
335
+ * Supported by PNG, JPEG, WebP, and TIFF output.
336
+ *
337
+ * @since 0.34.3
338
+ *
339
+ * @example
340
+ * const xmpString = `
341
+ * <?xml version="1.0"?>
342
+ * <x:xmpmeta xmlns:x="adobe:ns:meta/">
343
+ * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
344
+ * <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
345
+ * <dc:creator><rdf:Seq><rdf:li>John Doe</rdf:li></rdf:Seq></dc:creator>
346
+ * </rdf:Description>
347
+ * </rdf:RDF>
348
+ * </x:xmpmeta>`;
349
+ *
350
+ * const data = await sharp(input)
351
+ * .withXmp(xmpString)
352
+ * .toBuffer();
353
+ *
354
+ * @param {string} xmp String containing XMP metadata to be embedded in the output image.
355
+ * @returns {Sharp}
356
+ * @throws {Error} Invalid parameters
357
+ */
358
+ function withXmp (xmp) {
359
+ if (is.string(xmp) && xmp.length > 0) {
360
+ this.options.withXmp = xmp;
361
+ this.options.keepMetadata |= 0b00010;
362
+ } else {
363
+ throw is.invalidParameterError('xmp', 'non-empty string', xmp);
364
+ }
365
+ return this;
366
+ }
367
+
315
368
  /**
316
369
  * Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
317
370
  *
@@ -729,6 +782,7 @@ function webp (options) {
729
782
  * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
730
783
  * @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
731
784
  * @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
785
+ * @param {boolean} [options.keepDuplicateFrames=false] - keep duplicate frames in the output instead of combining them
732
786
  * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
733
787
  * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
734
788
  * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
@@ -779,6 +833,13 @@ function gif (options) {
779
833
  throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
780
834
  }
781
835
  }
836
+ if (is.defined(options.keepDuplicateFrames)) {
837
+ if (is.bool(options.keepDuplicateFrames)) {
838
+ this._setBooleanOption('gifKeepDuplicateFrames', options.keepDuplicateFrames);
839
+ } else {
840
+ throw is.invalidParameterError('keepDuplicateFrames', 'boolean', options.keepDuplicateFrames);
841
+ }
842
+ }
782
843
  }
783
844
  trySetAnimationOptions(options, this.options);
784
845
  return this._updateFormatOut('gif', options);
@@ -1568,6 +1629,8 @@ module.exports = function (Sharp) {
1568
1629
  withExifMerge,
1569
1630
  keepIccProfile,
1570
1631
  withIccProfile,
1632
+ keepXmp,
1633
+ withXmp,
1571
1634
  keepMetadata,
1572
1635
  withMetadata,
1573
1636
  toFormat,
package/lib/resize.js CHANGED
@@ -150,6 +150,8 @@ function isResizeExpected (options) {
150
150
  * - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
151
151
  * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
152
152
  * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
153
+ * - `mks2013`: Use a [Magic Kernel Sharp](https://johncostella.com/magic/mks.pdf) 2013 kernel, as adopted by Facebook.
154
+ * - `mks2021`: Use a Magic Kernel Sharp 2021 kernel, with more accurate (reduced) sharpening than the 2013 version.
153
155
  *
154
156
  * When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
155
157
  * Downsampling kernels without a matching upsampling interpolator map to `cubic`.
package/lib/utility.js CHANGED
@@ -135,15 +135,9 @@ cache(true);
135
135
  * e.g. libaom manages its own 4 threads when encoding AVIF images,
136
136
  * and these are independent of the value set here.
137
137
  *
138
- * The maximum number of images that sharp can process in parallel
139
- * is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
140
- * which defaults to 4.
141
- *
142
- * https://nodejs.org/api/cli.html#uv_threadpool_sizesize
143
- *
144
- * For example, by default, a machine with 8 CPU cores will process
145
- * 4 images in parallel and use up to 8 threads per image,
146
- * so there will be up to 32 concurrent threads.
138
+ * :::note
139
+ * Further {@link /performance|control over performance} is available.
140
+ * :::
147
141
  *
148
142
  * @example
149
143
  * const threads = sharp.concurrency(); // 4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
3
  "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
4
- "version": "0.34.3-rc.0",
4
+ "version": "0.34.3",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://sharp.pixelplumbing.com",
7
7
  "contributors": [
@@ -141,50 +141,50 @@
141
141
  "semver": "^7.7.2"
142
142
  },
143
143
  "optionalDependencies": {
144
- "@img/sharp-darwin-arm64": "0.34.3-rc.0",
145
- "@img/sharp-darwin-x64": "0.34.3-rc.0",
146
- "@img/sharp-libvips-darwin-arm64": "1.2.0-rc.2",
147
- "@img/sharp-libvips-darwin-x64": "1.2.0-rc.2",
148
- "@img/sharp-libvips-linux-arm": "1.2.0-rc.2",
149
- "@img/sharp-libvips-linux-arm64": "1.2.0-rc.2",
150
- "@img/sharp-libvips-linux-ppc64": "1.2.0-rc.2",
151
- "@img/sharp-libvips-linux-s390x": "1.2.0-rc.2",
152
- "@img/sharp-libvips-linux-x64": "1.2.0-rc.2",
153
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.0-rc.2",
154
- "@img/sharp-libvips-linuxmusl-x64": "1.2.0-rc.2",
155
- "@img/sharp-linux-arm": "0.34.3-rc.0",
156
- "@img/sharp-linux-arm64": "0.34.3-rc.0",
157
- "@img/sharp-linux-ppc64": "0.34.3-rc.0",
158
- "@img/sharp-linux-s390x": "0.34.3-rc.0",
159
- "@img/sharp-linux-x64": "0.34.3-rc.0",
160
- "@img/sharp-linuxmusl-arm64": "0.34.3-rc.0",
161
- "@img/sharp-linuxmusl-x64": "0.34.3-rc.0",
162
- "@img/sharp-wasm32": "0.34.3-rc.0",
163
- "@img/sharp-win32-arm64": "0.34.3-rc.0",
164
- "@img/sharp-win32-ia32": "0.34.3-rc.0",
165
- "@img/sharp-win32-x64": "0.34.3-rc.0"
144
+ "@img/sharp-darwin-arm64": "0.34.3",
145
+ "@img/sharp-darwin-x64": "0.34.3",
146
+ "@img/sharp-libvips-darwin-arm64": "1.2.0",
147
+ "@img/sharp-libvips-darwin-x64": "1.2.0",
148
+ "@img/sharp-libvips-linux-arm": "1.2.0",
149
+ "@img/sharp-libvips-linux-arm64": "1.2.0",
150
+ "@img/sharp-libvips-linux-ppc64": "1.2.0",
151
+ "@img/sharp-libvips-linux-s390x": "1.2.0",
152
+ "@img/sharp-libvips-linux-x64": "1.2.0",
153
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.0",
154
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.0",
155
+ "@img/sharp-linux-arm": "0.34.3",
156
+ "@img/sharp-linux-arm64": "0.34.3",
157
+ "@img/sharp-linux-ppc64": "0.34.3",
158
+ "@img/sharp-linux-s390x": "0.34.3",
159
+ "@img/sharp-linux-x64": "0.34.3",
160
+ "@img/sharp-linuxmusl-arm64": "0.34.3",
161
+ "@img/sharp-linuxmusl-x64": "0.34.3",
162
+ "@img/sharp-wasm32": "0.34.3",
163
+ "@img/sharp-win32-arm64": "0.34.3",
164
+ "@img/sharp-win32-ia32": "0.34.3",
165
+ "@img/sharp-win32-x64": "0.34.3"
166
166
  },
167
167
  "devDependencies": {
168
- "@emnapi/runtime": "^1.4.3",
169
- "@img/sharp-libvips-dev": "1.2.0-rc.2",
170
- "@img/sharp-libvips-dev-wasm32": "1.2.0-rc.2",
171
- "@img/sharp-libvips-win32-arm64": "1.2.0-rc.2",
172
- "@img/sharp-libvips-win32-ia32": "1.2.0-rc.2",
173
- "@img/sharp-libvips-win32-x64": "1.2.0-rc.2",
168
+ "@emnapi/runtime": "^1.4.4",
169
+ "@img/sharp-libvips-dev": "1.2.0",
170
+ "@img/sharp-libvips-dev-wasm32": "1.2.0",
171
+ "@img/sharp-libvips-win32-arm64": "1.2.0",
172
+ "@img/sharp-libvips-win32-ia32": "1.2.0",
173
+ "@img/sharp-libvips-win32-x64": "1.2.0",
174
174
  "@types/node": "*",
175
175
  "cc": "^3.0.1",
176
- "emnapi": "^1.4.3",
176
+ "emnapi": "^1.4.4",
177
177
  "exif-reader": "^2.0.2",
178
178
  "extract-zip": "^2.0.1",
179
179
  "icc": "^3.0.0",
180
180
  "jsdoc-to-markdown": "^9.1.1",
181
181
  "license-checker": "^25.0.1",
182
- "mocha": "^11.6.0",
183
- "node-addon-api": "^8.3.1",
182
+ "mocha": "^11.7.1",
183
+ "node-addon-api": "^8.4.0",
184
184
  "node-gyp": "^11.2.0",
185
185
  "nyc": "^17.1.0",
186
186
  "semistandard": "^17.0.0",
187
- "tar-fs": "^3.0.9",
187
+ "tar-fs": "^3.1.0",
188
188
  "tsd": "^0.32.0"
189
189
  },
190
190
  "license": "Apache-2.0",
@@ -192,7 +192,7 @@
192
192
  "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
193
193
  },
194
194
  "config": {
195
- "libvips": ">=8.17.0"
195
+ "libvips": ">=8.17.1"
196
196
  },
197
197
  "funding": {
198
198
  "url": "https://opencollective.com/libvips"
package/src/common.cc CHANGED
@@ -93,6 +93,7 @@ namespace sharp {
93
93
  descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
94
94
  descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
95
95
  descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
96
+ descriptor->rawPageHeight = AttrAsUint32(input, "rawPageHeight");
96
97
  }
97
98
  // Multi-page input (GIF, TIFF, PDF)
98
99
  if (HasAttr(input, "pages")) {
@@ -101,13 +102,20 @@ namespace sharp {
101
102
  if (HasAttr(input, "page")) {
102
103
  descriptor->page = AttrAsUint32(input, "page");
103
104
  }
105
+ // SVG
106
+ if (HasAttr(input, "svgStylesheet")) {
107
+ descriptor->svgStylesheet = AttrAsStr(input, "svgStylesheet");
108
+ }
109
+ if (HasAttr(input, "svgHighBitdepth")) {
110
+ descriptor->svgHighBitdepth = AttrAsBool(input, "svgHighBitdepth");
111
+ }
104
112
  // Multi-level input (OpenSlide)
105
- if (HasAttr(input, "level")) {
106
- descriptor->level = AttrAsUint32(input, "level");
113
+ if (HasAttr(input, "openSlideLevel")) {
114
+ descriptor->openSlideLevel = AttrAsUint32(input, "openSlideLevel");
107
115
  }
108
116
  // subIFD (OME-TIFF)
109
117
  if (HasAttr(input, "subifd")) {
110
- descriptor->subifd = AttrAsInt32(input, "subifd");
118
+ descriptor->tiffSubifd = AttrAsInt32(input, "tiffSubifd");
111
119
  }
112
120
  // // PDF background color
113
121
  if (HasAttr(input, "pdfBackground")) {
@@ -122,6 +130,7 @@ namespace sharp {
122
130
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
123
131
  descriptor->createWidth = AttrAsUint32(input, "createWidth");
124
132
  descriptor->createHeight = AttrAsUint32(input, "createHeight");
133
+ descriptor->createPageHeight = AttrAsUint32(input, "createPageHeight");
125
134
  if (HasAttr(input, "createNoiseType")) {
126
135
  descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
127
136
  descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
@@ -275,6 +284,7 @@ namespace sharp {
275
284
  case ImageType::EXR: id = "exr"; break;
276
285
  case ImageType::JXL: id = "jxl"; break;
277
286
  case ImageType::RAD: id = "rad"; break;
287
+ case ImageType::DCRAW: id = "dcraw"; break;
278
288
  case ImageType::VIPS: id = "vips"; break;
279
289
  case ImageType::RAW: id = "raw"; break;
280
290
  case ImageType::UNKNOWN: id = "unknown"; break;
@@ -323,6 +333,8 @@ namespace sharp {
323
333
  { "VipsForeignLoadJxlBuffer", ImageType::JXL },
324
334
  { "VipsForeignLoadRadFile", ImageType::RAD },
325
335
  { "VipsForeignLoadRadBuffer", ImageType::RAD },
336
+ { "VipsForeignLoadDcRawFile", ImageType::DCRAW },
337
+ { "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
326
338
  { "VipsForeignLoadVips", ImageType::VIPS },
327
339
  { "VipsForeignLoadVipsFile", ImageType::VIPS },
328
340
  { "VipsForeignLoadRaw", ImageType::RAW }
@@ -387,6 +399,48 @@ namespace sharp {
387
399
  imageType == ImageType::HEIF;
388
400
  }
389
401
 
402
+ /*
403
+ Format-specific options builder
404
+ */
405
+ vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor) {
406
+ vips::VOption *option = VImage::option()
407
+ ->set("access", descriptor->access)
408
+ ->set("fail_on", descriptor->failOn);
409
+ if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
410
+ option->set("unlimited", true);
411
+ }
412
+ if (ImageTypeSupportsPage(imageType)) {
413
+ option->set("n", descriptor->pages);
414
+ option->set("page", descriptor->page);
415
+ }
416
+ switch (imageType) {
417
+ case ImageType::SVG:
418
+ option->set("dpi", descriptor->density)
419
+ ->set("stylesheet", descriptor->svgStylesheet.data())
420
+ ->set("high_bitdepth", descriptor->svgHighBitdepth);
421
+ break;
422
+ case ImageType::TIFF:
423
+ option->set("tiffSubifd", descriptor->tiffSubifd);
424
+ break;
425
+ case ImageType::PDF:
426
+ option->set("dpi", descriptor->density)
427
+ ->set("background", descriptor->pdfBackground);
428
+ break;
429
+ case ImageType::OPENSLIDE:
430
+ option->set("openSlideLevel", descriptor->openSlideLevel);
431
+ break;
432
+ case ImageType::JP2:
433
+ option->set("oneshot", descriptor->jp2Oneshot);
434
+ break;
435
+ case ImageType::MAGICK:
436
+ option->set("density", std::to_string(descriptor->density).data());
437
+ break;
438
+ default:
439
+ break;
440
+ }
441
+ return option;
442
+ }
443
+
390
444
  /*
391
445
  Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
392
446
  */
@@ -404,6 +458,10 @@ namespace sharp {
404
458
  } else {
405
459
  image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16;
406
460
  }
461
+ if (descriptor->rawPageHeight > 0) {
462
+ image.set(VIPS_META_PAGE_HEIGHT, descriptor->rawPageHeight);
463
+ image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->rawHeight / descriptor->rawPageHeight));
464
+ }
407
465
  if (descriptor->rawPremultiplied) {
408
466
  image = image.unpremultiply();
409
467
  }
@@ -413,34 +471,7 @@ namespace sharp {
413
471
  imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
414
472
  if (imageType != ImageType::UNKNOWN) {
415
473
  try {
416
- vips::VOption *option = VImage::option()
417
- ->set("access", descriptor->access)
418
- ->set("fail_on", descriptor->failOn);
419
- if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
420
- option->set("unlimited", true);
421
- }
422
- if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
423
- option->set("dpi", descriptor->density);
424
- }
425
- if (imageType == ImageType::MAGICK) {
426
- option->set("density", std::to_string(descriptor->density).data());
427
- }
428
- if (ImageTypeSupportsPage(imageType)) {
429
- option->set("n", descriptor->pages);
430
- option->set("page", descriptor->page);
431
- }
432
- if (imageType == ImageType::OPENSLIDE) {
433
- option->set("level", descriptor->level);
434
- }
435
- if (imageType == ImageType::TIFF) {
436
- option->set("subifd", descriptor->subifd);
437
- }
438
- if (imageType == ImageType::PDF) {
439
- option->set("background", descriptor->pdfBackground);
440
- }
441
- if (imageType == ImageType::JP2) {
442
- option->set("oneshot", descriptor->jp2Oneshot);
443
- }
474
+ vips::VOption *option = GetOptionsForImageType(imageType, descriptor);
444
475
  image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
445
476
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
446
477
  image = SetDensity(image, descriptor->density);
@@ -480,6 +511,10 @@ namespace sharp {
480
511
  channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
481
512
  .new_from_image(background);
482
513
  }
514
+ if (descriptor->createPageHeight > 0) {
515
+ image.set(VIPS_META_PAGE_HEIGHT, descriptor->createPageHeight);
516
+ image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->createHeight / descriptor->createPageHeight));
517
+ }
483
518
  image = image.cast(VIPS_FORMAT_UCHAR);
484
519
  imageType = ImageType::RAW;
485
520
  } else if (descriptor->textValue.length() > 0) {
@@ -523,34 +558,7 @@ namespace sharp {
523
558
  }
524
559
  if (imageType != ImageType::UNKNOWN) {
525
560
  try {
526
- vips::VOption *option = VImage::option()
527
- ->set("access", descriptor->access)
528
- ->set("fail_on", descriptor->failOn);
529
- if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
530
- option->set("unlimited", true);
531
- }
532
- if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
533
- option->set("dpi", descriptor->density);
534
- }
535
- if (imageType == ImageType::MAGICK) {
536
- option->set("density", std::to_string(descriptor->density).data());
537
- }
538
- if (ImageTypeSupportsPage(imageType)) {
539
- option->set("n", descriptor->pages);
540
- option->set("page", descriptor->page);
541
- }
542
- if (imageType == ImageType::OPENSLIDE) {
543
- option->set("level", descriptor->level);
544
- }
545
- if (imageType == ImageType::TIFF) {
546
- option->set("subifd", descriptor->subifd);
547
- }
548
- if (imageType == ImageType::PDF) {
549
- option->set("background", descriptor->pdfBackground);
550
- }
551
- if (imageType == ImageType::JP2) {
552
- option->set("oneshot", descriptor->jp2Oneshot);
553
- }
561
+ vips::VOption *option = GetOptionsForImageType(imageType, descriptor);
554
562
  image = VImage::new_from_file(descriptor->file.data(), option);
555
563
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
556
564
  image = SetDensity(image, descriptor->density);
package/src/common.h CHANGED
@@ -16,8 +16,8 @@
16
16
 
17
17
  #if (VIPS_MAJOR_VERSION < 8) || \
18
18
  (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \
19
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 0)
20
- #error "libvips version 8.17.0+ is required - please see https://sharp.pixelplumbing.com/install"
19
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 1)
20
+ #error "libvips version 8.17.1+ is required - please see https://sharp.pixelplumbing.com/install"
21
21
  #endif
22
22
 
23
23
  #if defined(__has_include)
@@ -48,13 +48,13 @@ namespace sharp {
48
48
  int rawWidth;
49
49
  int rawHeight;
50
50
  bool rawPremultiplied;
51
+ int rawPageHeight;
51
52
  int pages;
52
53
  int page;
53
- int level;
54
- int subifd;
55
54
  int createChannels;
56
55
  int createWidth;
57
56
  int createHeight;
57
+ int createPageHeight;
58
58
  std::vector<double> createBackground;
59
59
  std::string createNoiseType;
60
60
  double createNoiseMean;
@@ -77,6 +77,10 @@ namespace sharp {
77
77
  std::vector<double> joinBackground;
78
78
  VipsAlign joinHalign;
79
79
  VipsAlign joinValign;
80
+ std::string svgStylesheet;
81
+ bool svgHighBitdepth;
82
+ int tiffSubifd;
83
+ int openSlideLevel;
80
84
  std::vector<double> pdfBackground;
81
85
  bool jp2Oneshot;
82
86
 
@@ -96,13 +100,13 @@ namespace sharp {
96
100
  rawWidth(0),
97
101
  rawHeight(0),
98
102
  rawPremultiplied(false),
103
+ rawPageHeight(0),
99
104
  pages(1),
100
105
  page(0),
101
- level(0),
102
- subifd(-1),
103
106
  createChannels(0),
104
107
  createWidth(0),
105
108
  createHeight(0),
109
+ createPageHeight(0),
106
110
  createBackground{ 0.0, 0.0, 0.0, 255.0 },
107
111
  createNoiseMean(0.0),
108
112
  createNoiseSigma(0.0),
@@ -121,6 +125,9 @@ namespace sharp {
121
125
  joinBackground{ 0.0, 0.0, 0.0, 255.0 },
122
126
  joinHalign(VIPS_ALIGN_LOW),
123
127
  joinValign(VIPS_ALIGN_LOW),
128
+ svgHighBitdepth(false),
129
+ tiffSubifd(-1),
130
+ openSlideLevel(0),
124
131
  pdfBackground{ 255.0, 255.0, 255.0, 255.0 },
125
132
  jp2Oneshot(false) {}
126
133
  };
@@ -162,6 +169,7 @@ namespace sharp {
162
169
  EXR,
163
170
  JXL,
164
171
  RAD,
172
+ DCRAW,
165
173
  VIPS,
166
174
  RAW,
167
175
  UNKNOWN,
@@ -218,14 +226,9 @@ namespace sharp {
218
226
  ImageType DetermineImageType(char const *file);
219
227
 
220
228
  /*
221
- Does this image type support multiple pages?
229
+ Format-specific options builder
222
230
  */
223
- bool ImageTypeSupportsPage(ImageType imageType);
224
-
225
- /*
226
- Does this image type support removal of safety limits?
227
- */
228
- bool ImageTypeSupportsUnlimited(ImageType imageType);
231
+ vips::VOption* GetOptionsForImageType(ImageType imageType, InputDescriptor *descriptor);
229
232
 
230
233
  /*
231
234
  Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
package/src/metadata.cc CHANGED
@@ -262,6 +262,10 @@ class MetadataWorker : public Napi::AsyncWorker {
262
262
  }
263
263
  if (baton->xmpLength > 0) {
264
264
  info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
265
+ if (g_utf8_validate(static_cast<char const *>(baton->xmp), baton->xmpLength, nullptr)) {
266
+ info.Set("xmpAsString",
267
+ Napi::String::New(env, static_cast<char const *>(baton->xmp), baton->xmpLength));
268
+ }
265
269
  }
266
270
  if (baton->tifftagPhotoshopLength > 0) {
267
271
  info.Set("tifftagPhotoshop",
package/src/pipeline.cc CHANGED
@@ -241,11 +241,7 @@ class PipelineWorker : public Napi::AsyncWorker {
241
241
  // factor for jpegload*, a double scale factor for webpload*,
242
242
  // pdfload* and svgload*
243
243
  if (jpegShrinkOnLoad > 1) {
244
- vips::VOption *option = VImage::option()
245
- ->set("access", access)
246
- ->set("shrink", jpegShrinkOnLoad)
247
- ->set("unlimited", baton->input->unlimited)
248
- ->set("fail_on", baton->input->failOn);
244
+ vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad);
249
245
  if (baton->input->buffer != nullptr) {
250
246
  // Reload JPEG buffer
251
247
  VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@@ -256,14 +252,8 @@ class PipelineWorker : public Napi::AsyncWorker {
256
252
  image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
257
253
  }
258
254
  } else if (scale != 1.0) {
259
- vips::VOption *option = VImage::option()
260
- ->set("access", access)
261
- ->set("scale", scale)
262
- ->set("fail_on", baton->input->failOn);
255
+ vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale);
263
256
  if (inputImageType == sharp::ImageType::WEBP) {
264
- option->set("n", baton->input->pages);
265
- option->set("page", baton->input->page);
266
-
267
257
  if (baton->input->buffer != nullptr) {
268
258
  // Reload WebP buffer
269
259
  VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@@ -274,9 +264,6 @@ class PipelineWorker : public Napi::AsyncWorker {
274
264
  image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
275
265
  }
276
266
  } else if (inputImageType == sharp::ImageType::SVG) {
277
- option->set("unlimited", baton->input->unlimited);
278
- option->set("dpi", baton->input->density);
279
-
280
267
  if (baton->input->buffer != nullptr) {
281
268
  // Reload SVG buffer
282
269
  VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@@ -291,11 +278,6 @@ class PipelineWorker : public Napi::AsyncWorker {
291
278
  throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
292
279
  }
293
280
  } else if (inputImageType == sharp::ImageType::PDF) {
294
- option->set("n", baton->input->pages);
295
- option->set("page", baton->input->page);
296
- option->set("dpi", baton->input->density);
297
- option->set("background", baton->input->pdfBackground);
298
-
299
281
  if (baton->input->buffer != nullptr) {
300
282
  // Reload PDF buffer
301
283
  VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@@ -305,7 +287,6 @@ class PipelineWorker : public Napi::AsyncWorker {
305
287
  // Reload PDF file
306
288
  image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
307
289
  }
308
-
309
290
  sharp::SetDensity(image, baton->input->density);
310
291
  }
311
292
  } else {
@@ -895,7 +876,12 @@ class PipelineWorker : public Napi::AsyncWorker {
895
876
  image.set(s.first.data(), s.second.data());
896
877
  }
897
878
  }
898
-
879
+ // XMP buffer
880
+ if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {
881
+ image = image.copy();
882
+ image.set(VIPS_META_XMP_NAME, nullptr,
883
+ const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());
884
+ }
899
885
  // Number of channels used in output image
900
886
  baton->channels = image.bands();
901
887
  baton->width = image.width();
@@ -1006,6 +992,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1006
992
  ->set("interlace", baton->gifProgressive)
1007
993
  ->set("interframe_maxerror", baton->gifInterFrameMaxError)
1008
994
  ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
995
+ ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
1009
996
  ->set("dither", baton->gifDither)));
1010
997
  baton->bufferOut = static_cast<char*>(area->data);
1011
998
  baton->bufferOutLength = area->length;
@@ -1209,6 +1196,9 @@ class PipelineWorker : public Napi::AsyncWorker {
1209
1196
  ->set("effort", baton->gifEffort)
1210
1197
  ->set("reuse", baton->gifReuse)
1211
1198
  ->set("interlace", baton->gifProgressive)
1199
+ ->set("interframe_maxerror", baton->gifInterFrameMaxError)
1200
+ ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
1201
+ ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
1212
1202
  ->set("dither", baton->gifDither));
1213
1203
  baton->formatOut = "gif";
1214
1204
  } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@@ -1721,6 +1711,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1721
1711
  }
1722
1712
  }
1723
1713
  baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
1714
+ baton->withXmp = sharp::AttrAsStr(options, "withXmp");
1724
1715
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1725
1716
  baton->loop = sharp::AttrAsUint32(options, "loop");
1726
1717
  baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@@ -1761,6 +1752,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1761
1752
  baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
1762
1753
  baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
1763
1754
  baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
1755
+ baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
1764
1756
  baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
1765
1757
  baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
1766
1758
  baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
package/src/pipeline.h CHANGED
@@ -169,6 +169,7 @@ struct PipelineBaton {
169
169
  double gifDither;
170
170
  double gifInterFrameMaxError;
171
171
  double gifInterPaletteMaxError;
172
+ bool gifKeepDuplicateFrames;
172
173
  bool gifReuse;
173
174
  bool gifProgressive;
174
175
  int tiffQuality;
@@ -201,6 +202,7 @@ struct PipelineBaton {
201
202
  std::string withIccProfile;
202
203
  std::unordered_map<std::string, std::string> withExif;
203
204
  bool withExifMerge;
205
+ std::string withXmp;
204
206
  int timeoutSeconds;
205
207
  std::vector<double> convKernel;
206
208
  int convKernelWidth;
@@ -342,6 +344,7 @@ struct PipelineBaton {
342
344
  gifDither(1.0),
343
345
  gifInterFrameMaxError(0.0),
344
346
  gifInterPaletteMaxError(3.0),
347
+ gifKeepDuplicateFrames(false),
345
348
  gifReuse(true),
346
349
  gifProgressive(false),
347
350
  tiffQuality(80),
package/src/utilities.cc CHANGED
@@ -119,7 +119,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
119
119
  Napi::Object format = Napi::Object::New(env);
120
120
  for (std::string const f : {
121
121
  "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
122
- "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad"
122
+ "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw"
123
123
  }) {
124
124
  // Input
125
125
  const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());