sharp 0.34.5-rc.1 → 0.35.0-rc.0

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions
8
8
 
9
9
  It can be used with all JavaScript runtimes
10
10
  that provide support for Node-API v9, including
11
- Node.js (^18.17.0 or >= 20.3.0), Deno and Bun.
11
+ Node.js (>= 20.9.0), Deno and Bun.
12
12
 
13
13
  Resizing an image is typically 4x-5x faster than using the
14
14
  quickest ImageMagick and GraphicsMagick settings
package/install/build.js CHANGED
@@ -10,7 +10,7 @@ const {
10
10
  spawnRebuild,
11
11
  } = require('../lib/libvips');
12
12
 
13
- log('Attempting to build from source via node-gyp');
13
+ log('Building from source');
14
14
  log('See https://sharp.pixelplumbing.com/install#building-from-source');
15
15
 
16
16
  try {
@@ -29,7 +29,7 @@ try {
29
29
  }
30
30
 
31
31
  if (useGlobalLibvips(log)) {
32
- log(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
32
+ log(`Found globally-installed libvips v${globalLibvipsVersion()}`);
33
33
  }
34
34
 
35
35
  const status = spawnRebuild();
@@ -278,6 +278,7 @@ const Sharp = function (input, options) {
278
278
  trimBackground: [],
279
279
  trimThreshold: -1,
280
280
  trimLineArt: false,
281
+ trimMargin: 0,
281
282
  dilateWidth: 0,
282
283
  erodeWidth: 0,
283
284
  gamma: 0,
@@ -306,6 +307,7 @@ const Sharp = function (input, options) {
306
307
  fileOut: '',
307
308
  formatOut: 'input',
308
309
  streamOut: false,
310
+ typedArrayOut: false,
309
311
  keepMetadata: 0,
310
312
  withMetadataOrientation: -1,
311
313
  withMetadataDensity: 0,
@@ -313,6 +315,7 @@ const Sharp = function (input, options) {
313
315
  withExif: {},
314
316
  withExifMerge: true,
315
317
  withXmp: '',
318
+ withGainMap: false,
316
319
  resolveWithObject: false,
317
320
  loop: -1,
318
321
  delay: [],
@@ -348,6 +351,7 @@ const Sharp = function (input, options) {
348
351
  webpEffort: 4,
349
352
  webpMinSize: false,
350
353
  webpMixed: false,
354
+ webpExact: false,
351
355
  gifBitdepth: 8,
352
356
  gifEffort: 7,
353
357
  gifDither: 1,
@@ -375,6 +379,7 @@ const Sharp = function (input, options) {
375
379
  heifEffort: 4,
376
380
  heifChromaSubsampling: '4:4:4',
377
381
  heifBitdepth: 8,
382
+ heifTune: 'ssim',
378
383
  jxlDistance: 1,
379
384
  jxlDecodingTier: 0,
380
385
  jxlEffort: 7,
package/lib/index.d.ts CHANGED
@@ -259,7 +259,6 @@ declare namespace sharp {
259
259
  * Set the pipeline colourspace.
260
260
  * The input image will be converted to the provided colourspace at the start of the pipeline.
261
261
  * All operations will use this colourspace before converting to the output colourspace, as defined by toColourspace.
262
- * This feature is experimental and has not yet been fully-tested with all operations.
263
262
  *
264
263
  * @param colourspace pipeline colourspace e.g. rgb16, scrgb, lab, grey16 ...
265
264
  * @throws {Error} Invalid parameters
@@ -470,21 +469,6 @@ declare namespace sharp {
470
469
  */
471
470
  sharpen(options?: SharpenOptions): Sharp;
472
471
 
473
- /**
474
- * Sharpen the image.
475
- * When used without parameters, performs a fast, mild sharpen of the output image.
476
- * When a sigma is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
477
- * Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
478
- * @param sigma the sigma of the Gaussian mask, where sigma = 1 + radius / 2.
479
- * @param flat the level of sharpening to apply to "flat" areas. (optional, default 1.0)
480
- * @param jagged the level of sharpening to apply to "jagged" areas. (optional, default 2.0)
481
- * @throws {Error} Invalid parameters
482
- * @returns A sharp instance that can be used to chain operations
483
- *
484
- * @deprecated Use the object parameter `sharpen({sigma, m1, m2, x1, y2, y3})` instead
485
- */
486
- sharpen(sigma?: number, flat?: number, jagged?: number): Sharp;
487
-
488
472
  /**
489
473
  * Apply median filter. When used without parameters the default window is 3x3.
490
474
  * @param size square mask size: size x size (optional, default 3)
@@ -693,6 +677,13 @@ declare namespace sharp {
693
677
  */
694
678
  toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
695
679
 
680
+ /**
681
+ * Write output to a Uint8Array backed by a transferable ArrayBuffer. JPEG, PNG, WebP, AVIF, TIFF, GIF and RAW output are supported.
682
+ * By default, the format will match the input image, except SVG input which becomes PNG output.
683
+ * @returns A promise that resolves with an object containing the Uint8Array data and an info object containing the output image format, size (bytes), width, height and channels
684
+ */
685
+ toUint8Array(): Promise<{ data: Uint8Array; info: OutputInfo }>;
686
+
696
687
  /**
697
688
  * Keep all EXIF metadata from the input image in the output image.
698
689
  * EXIF metadata is unsupported for TIFF output.
@@ -849,7 +840,7 @@ declare namespace sharp {
849
840
  * @returns A sharp instance that can be used to chain operations
850
841
  */
851
842
  toFormat(
852
- format: keyof FormatEnum | AvailableFormatInfo,
843
+ format: keyof FormatEnum | AvailableFormatInfo | "avif",
853
844
  options?:
854
845
  | OutputOptions
855
846
  | JpegOptions
@@ -903,7 +894,7 @@ declare namespace sharp {
903
894
  * - sharp.gravity: north, northeast, east, southeast, south, southwest, west, northwest, center or centre.
904
895
  * - sharp.strategy: cover only, dynamically crop using either the entropy or attention strategy. Some of these values are based on the object-position CSS property.
905
896
  *
906
- * The experimental strategy-based approach resizes so one dimension is at its target length then repeatedly ranks edge regions,
897
+ * The strategy-based approach resizes so one dimension is at its target length then repeatedly ranks edge regions,
907
898
  * discarding the edge with the lowest score based on the selected strategy.
908
899
  * - entropy: focus on the region with the highest Shannon entropy.
909
900
  * - attention: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
@@ -992,14 +983,6 @@ declare namespace sharp {
992
983
  * 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning')
993
984
  */
994
985
  failOn?: FailOnOptions | undefined;
995
- /**
996
- * By default halt processing and raise an error when loading invalid images.
997
- * Set this flag to false if you'd rather apply a "best effort" to decode images,
998
- * even if the data is corrupt or invalid. (optional, default true)
999
- *
1000
- * @deprecated Use `failOn` instead
1001
- */
1002
- failOnError?: boolean | undefined;
1003
986
  /**
1004
987
  * Do not process input images where the number of pixels (width x height) exceeds this limit.
1005
988
  * Assumes image dimensions contained in the input metadata can be trusted.
@@ -1028,11 +1011,11 @@ declare namespace sharp {
1028
1011
  openSlide?: OpenSlideInputOptions | undefined;
1029
1012
  /** JPEG 2000 specific input options */
1030
1013
  jp2?: Jp2InputOptions | undefined;
1031
- /** Deprecated: use tiff.subifd instead */
1014
+ /** @deprecated Use {@link SharpOptions.tiff} instead */
1032
1015
  subifd?: number | undefined;
1033
- /** Deprecated: use pdf.background instead */
1016
+ /** @deprecated Use {@link SharpOptions.pdf} instead */
1034
1017
  pdfBackground?: Colour | Color | undefined;
1035
- /** Deprecated: use openSlide.level instead */
1018
+ /** @deprecated Use {@link SharpOptions.openSlide} instead */
1036
1019
  level?: number | undefined;
1037
1020
  /** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default false) */
1038
1021
  animated?: boolean | undefined;
@@ -1184,6 +1167,8 @@ declare namespace sharp {
1184
1167
 
1185
1168
  type HeifCompression = 'av1' | 'hevc';
1186
1169
 
1170
+ type HeifTune = 'iq' | 'ssim' | 'psnr';
1171
+
1187
1172
  type Unit = 'inch' | 'cm';
1188
1173
 
1189
1174
  interface WriteableMetadata {
@@ -1277,6 +1262,8 @@ declare namespace sharp {
1277
1262
  formatMagick?: string | undefined;
1278
1263
  /** Array of keyword/text pairs representing PNG text blocks, if present. */
1279
1264
  comments?: CommentsMetadata[] | undefined;
1265
+ /** HDR gain map, if present */
1266
+ gainMap?: GainMapMetadata | undefined;
1280
1267
  }
1281
1268
 
1282
1269
  interface LevelMetadata {
@@ -1289,16 +1276,21 @@ declare namespace sharp {
1289
1276
  text: string;
1290
1277
  }
1291
1278
 
1279
+ interface GainMapMetadata {
1280
+ /** JPEG image */
1281
+ image: Buffer;
1282
+ }
1283
+
1292
1284
  interface Stats {
1293
1285
  /** Array of channel statistics for each channel in the image. */
1294
1286
  channels: ChannelStats[];
1295
1287
  /** Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel */
1296
1288
  isOpaque: boolean;
1297
- /** Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental) */
1289
+ /** Histogram-based estimation of greyscale entropy, discarding alpha channel if any */
1298
1290
  entropy: number;
1299
- /** Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental) */
1291
+ /** Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any */
1300
1292
  sharpness: number;
1301
- /** Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental) */
1293
+ /** Object containing most dominant sRGB colour based on a 4096-bin 3D histogram */
1302
1294
  dominant: { r: number; g: number; b: number };
1303
1295
  }
1304
1296
 
@@ -1404,11 +1396,13 @@ declare namespace sharp {
1404
1396
  /** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
1405
1397
  effort?: number | undefined;
1406
1398
  /** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
1407
- minSize?: boolean;
1399
+ minSize?: boolean | undefined;
1408
1400
  /** Allow mixture of lossy and lossless animation frames (slow) (optional, default false) */
1409
- mixed?: boolean;
1401
+ mixed?: boolean | undefined;
1410
1402
  /** Preset options: one of default, photo, picture, drawing, icon, text (optional, default 'default') */
1411
1403
  preset?: keyof PresetEnum | undefined;
1404
+ /** Preserve the colour data in transparent pixels (optional, default false) */
1405
+ exact?: boolean | undefined;
1412
1406
  }
1413
1407
 
1414
1408
  interface AvifOptions extends OutputOptions {
@@ -1422,6 +1416,8 @@ declare namespace sharp {
1422
1416
  chromaSubsampling?: string | undefined;
1423
1417
  /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
1424
1418
  bitdepth?: 8 | 10 | 12 | undefined;
1419
+ /** Tune output for a quality metric, one of 'iq', 'ssim' or 'psnr' (optional, default 'iq') */
1420
+ tune?: HeifTune | undefined;
1425
1421
  }
1426
1422
 
1427
1423
  interface HeifOptions extends OutputOptions {
@@ -1437,6 +1433,8 @@ declare namespace sharp {
1437
1433
  chromaSubsampling?: string | undefined;
1438
1434
  /** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
1439
1435
  bitdepth?: 8 | 10 | 12 | undefined;
1436
+ /** Tune output for a quality metric, one of 'ssim', 'psnr' or 'iq' (optional, default 'ssim') */
1437
+ tune?: HeifTune | undefined;
1440
1438
  }
1441
1439
 
1442
1440
  interface GifOptions extends OutputOptions, AnimationOptions {
@@ -1608,6 +1606,8 @@ declare namespace sharp {
1608
1606
  threshold?: number | undefined;
1609
1607
  /** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */
1610
1608
  lineArt?: boolean | undefined;
1609
+ /** Leave a margin around trimmed content, value is in pixels. (optional, default 0) */
1610
+ margin?: number | undefined;
1611
1611
  }
1612
1612
 
1613
1613
  interface RawOptions {
@@ -1913,16 +1913,13 @@ declare namespace sharp {
1913
1913
  }
1914
1914
 
1915
1915
  interface FormatEnum {
1916
- avif: AvailableFormatInfo;
1917
1916
  dcraw: AvailableFormatInfo;
1918
1917
  dz: AvailableFormatInfo;
1919
1918
  exr: AvailableFormatInfo;
1920
1919
  fits: AvailableFormatInfo;
1921
1920
  gif: AvailableFormatInfo;
1922
1921
  heif: AvailableFormatInfo;
1923
- input: AvailableFormatInfo;
1924
1922
  jpeg: AvailableFormatInfo;
1925
- jpg: AvailableFormatInfo;
1926
1923
  jp2: AvailableFormatInfo;
1927
1924
  jxl: AvailableFormatInfo;
1928
1925
  magick: AvailableFormatInfo;
@@ -1934,8 +1931,7 @@ declare namespace sharp {
1934
1931
  raw: AvailableFormatInfo;
1935
1932
  svg: AvailableFormatInfo;
1936
1933
  tiff: AvailableFormatInfo;
1937
- tif: AvailableFormatInfo;
1938
- v: AvailableFormatInfo;
1934
+ vips: AvailableFormatInfo;
1939
1935
  webp: AvailableFormatInfo;
1940
1936
  }
1941
1937
 
package/lib/input.js CHANGED
@@ -30,7 +30,7 @@ const inputStreamParameters = [
30
30
  // Format-specific
31
31
  'jp2', 'openSlide', 'pdf', 'raw', 'svg', 'tiff',
32
32
  // Deprecated
33
- 'failOnError', 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
33
+ 'openSlideLevel', 'pdfBackground', 'tiffSubifd'
34
34
  ];
35
35
 
36
36
  /**
@@ -106,14 +106,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
106
106
  }`);
107
107
  }
108
108
  if (is.object(inputOptions)) {
109
- // Deprecated: failOnError
110
- if (is.defined(inputOptions.failOnError)) {
111
- if (is.bool(inputOptions.failOnError)) {
112
- inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
113
- } else {
114
- throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
115
- }
116
- }
117
109
  // failOn
118
110
  if (is.defined(inputOptions.failOn)) {
119
111
  if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
@@ -574,7 +566,7 @@ function _isStreamInput () {
574
566
  *
575
567
  * A `Promise` is returned when `callback` is not provided.
576
568
  *
577
- * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
569
+ * - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
578
570
  * - `size`: Total size of image in bytes, for Stream and Buffer input only
579
571
  * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
580
572
  * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
@@ -607,6 +599,7 @@ function _isStreamInput () {
607
599
  * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
608
600
  * - `formatMagick`: String containing format for images loaded via *magick
609
601
  * - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
602
+ * - `gainMap.image`: HDR gain map, if present, as compressed JPEG image.
610
603
  *
611
604
  * @example
612
605
  * const metadata = await sharp(input).metadata();
package/lib/operation.js CHANGED
@@ -259,45 +259,18 @@ function affine (matrix, options) {
259
259
  * })
260
260
  * .toBuffer();
261
261
  *
262
- * @param {Object|number} [options] - if present, is an Object with attributes
262
+ * @param {Object} [options] - if present, is an Object with attributes
263
263
  * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
264
264
  * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
265
265
  * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
266
266
  * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
267
267
  * @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
268
268
  * @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
269
- * @param {number} [flat] - (deprecated) see `options.m1`.
270
- * @param {number} [jagged] - (deprecated) see `options.m2`.
271
269
  * @returns {Sharp}
272
270
  * @throws {Error} Invalid parameters
273
271
  */
274
- function sharpen (options, flat, jagged) {
275
- if (!is.defined(options)) {
276
- // No arguments: default to mild sharpen
277
- this.options.sharpenSigma = -1;
278
- } else if (is.bool(options)) {
279
- // Deprecated boolean argument: apply mild sharpen?
280
- this.options.sharpenSigma = options ? -1 : 0;
281
- } else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
282
- // Deprecated numeric argument: specific sigma
283
- this.options.sharpenSigma = options;
284
- // Deprecated control over flat areas
285
- if (is.defined(flat)) {
286
- if (is.number(flat) && is.inRange(flat, 0, 10000)) {
287
- this.options.sharpenM1 = flat;
288
- } else {
289
- throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
290
- }
291
- }
292
- // Deprecated control over jagged areas
293
- if (is.defined(jagged)) {
294
- if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
295
- this.options.sharpenM2 = jagged;
296
- } else {
297
- throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
298
- }
299
- }
300
- } else if (is.plainObject(options)) {
272
+ function sharpen (options) {
273
+ if (is.plainObject(options)) {
301
274
  if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
302
275
  this.options.sharpenSigma = options.sigma;
303
276
  } else {
@@ -339,7 +312,7 @@ function sharpen (options, flat, jagged) {
339
312
  }
340
313
  }
341
314
  } else {
342
- throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
315
+ this.options.sharpenSigma = -1;
343
316
  }
344
317
  return this;
345
318
  }
@@ -510,8 +483,6 @@ function flatten (options) {
510
483
  *
511
484
  * Existing alpha channel values for non-white pixels remain unchanged.
512
485
  *
513
- * This feature is experimental and the API may change.
514
- *
515
486
  * @since 0.32.1
516
487
  *
517
488
  * @example
package/lib/output.js CHANGED
@@ -76,7 +76,7 @@ function toFile (fileOut, callback) {
76
76
  err = new Error('Missing output file path');
77
77
  } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
78
78
  err = new Error('Cannot use same file for input and output');
79
- } else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
79
+ } else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2.output.file) {
80
80
  err = errJp2Save();
81
81
  }
82
82
  if (err) {
@@ -164,6 +164,41 @@ function toBuffer (options, callback) {
164
164
  return this._pipeline(is.fn(options) ? options : callback, stack);
165
165
  }
166
166
 
167
+ /**
168
+ * Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
169
+ * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
170
+ *
171
+ * Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
172
+ *
173
+ * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
174
+ *
175
+ * By default all metadata will be removed, which includes EXIF-based orientation.
176
+ * See {@link #keepexif keepExif} and similar methods for control over this.
177
+ *
178
+ * Resolves with an `Object` containing:
179
+ * - `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
180
+ * - `info` contains properties relating to the output image such as `width` and `height`.
181
+ *
182
+ * @since v0.35.0
183
+ *
184
+ * @example
185
+ * const { data, info } = await sharp(input).toUint8Array();
186
+ *
187
+ * @example
188
+ * const { data } = await sharp(input)
189
+ * .avif()
190
+ * .toUint8Array();
191
+ * const base64String = data.toBase64();
192
+ *
193
+ * @returns {Promise<{ data: Uint8Array, info: Object }>}
194
+ */
195
+ function toUint8Array () {
196
+ this.options.resolveWithObject = true;
197
+ this.options.typedArrayOut = true;
198
+ const stack = Error();
199
+ return this._pipeline(null, stack);
200
+ }
201
+
167
202
  /**
168
203
  * Keep all EXIF metadata from the input image in the output image.
169
204
  *
@@ -319,6 +354,30 @@ function withIccProfile (icc, options) {
319
354
  return this;
320
355
  }
321
356
 
357
+ /**
358
+ * If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
359
+ * The input gain map is discarded.
360
+ *
361
+ * If the output is JPEG, generate and attach a new ISO 21496-1 gain map.
362
+ * JPEG output options other than `quality` are ignored.
363
+ *
364
+ * This feature is experimental and the API may change.
365
+ *
366
+ * @since 0.35.0
367
+ *
368
+ * @example
369
+ * const outputWithGainMap = await sharp(inputWithGainMap)
370
+ * .withGainMap()
371
+ * .toBuffer();
372
+ *
373
+ * @returns {Sharp}
374
+ */
375
+ function withGainMap() {
376
+ this.options.withGainMap = true;
377
+ this.options.colourspace = 'scrgb';
378
+ return this;
379
+ }
380
+
322
381
  /**
323
382
  * Keep XMP metadata from the input image in the output image.
324
383
  *
@@ -690,6 +749,7 @@ function png (options) {
690
749
  * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
691
750
  * @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
692
751
  * @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
752
+ * @param {boolean} [options.exact=false] - preserve the colour data in transparent pixels
693
753
  * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
694
754
  * @returns {Sharp}
695
755
  * @throws {Error} Invalid options
@@ -742,6 +802,9 @@ function webp (options) {
742
802
  if (is.defined(options.mixed)) {
743
803
  this._setBooleanOption('webpMixed', options.mixed);
744
804
  }
805
+ if (is.defined(options.exact)) {
806
+ this._setBooleanOption('webpExact', options.exact);
807
+ }
745
808
  }
746
809
  trySetAnimationOptions(options, this.options);
747
810
  return this._updateFormatOut('webp', options);
@@ -887,7 +950,7 @@ function gif (options) {
887
950
  */
888
951
  function jp2 (options) {
889
952
  /* node:coverage ignore next 41 */
890
- if (!this.constructor.format.jp2k.output.buffer) {
953
+ if (!this.constructor.format.jp2.output.buffer) {
891
954
  throw errJp2Save();
892
955
  }
893
956
  if (is.object(options)) {
@@ -1092,8 +1155,7 @@ function tiff (options) {
1092
1155
  * AVIF image sequences are not supported.
1093
1156
  * Prebuilt binaries support a bitdepth of 8 only.
1094
1157
  *
1095
- * This feature is experimental on the Windows ARM64 platform
1096
- * and requires a CPU with ARM64v8.4 or later.
1158
+ * When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
1097
1159
  *
1098
1160
  * @example
1099
1161
  * const data = await sharp(input)
@@ -1113,11 +1175,13 @@ function tiff (options) {
1113
1175
  * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
1114
1176
  * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1115
1177
  * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
1178
+ * @param {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' or 'psnr'
1116
1179
  * @returns {Sharp}
1117
1180
  * @throws {Error} Invalid options
1118
1181
  */
1119
1182
  function avif (options) {
1120
- return this.heif({ ...options, compression: 'av1' });
1183
+ const tune = is.object(options) && is.defined(options.tune) ? options.tune : 'iq';
1184
+ return this.heif({ ...options, compression: 'av1', tune });
1121
1185
  }
1122
1186
 
1123
1187
  /**
@@ -1140,6 +1204,7 @@ function avif (options) {
1140
1204
  * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
1141
1205
  * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1142
1206
  * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
1207
+ * @param {string} [options.tune='ssim'] - tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq'
1143
1208
  * @returns {Sharp}
1144
1209
  * @throws {Error} Invalid options
1145
1210
  */
@@ -1188,6 +1253,13 @@ function heif (options) {
1188
1253
  throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
1189
1254
  }
1190
1255
  }
1256
+ if (is.defined(options.tune)) {
1257
+ if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
1258
+ this.options.heifTune = options.tune;
1259
+ } else {
1260
+ throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
1261
+ }
1262
+ }
1191
1263
  } else {
1192
1264
  throw is.invalidParameterError('options', 'Object', options);
1193
1265
  }
@@ -1635,11 +1707,13 @@ module.exports = (Sharp) => {
1635
1707
  // Public
1636
1708
  toFile,
1637
1709
  toBuffer,
1710
+ toUint8Array,
1638
1711
  keepExif,
1639
1712
  withExif,
1640
1713
  withExifMerge,
1641
1714
  keepIccProfile,
1642
1715
  withIccProfile,
1716
+ withGainMap,
1643
1717
  keepXmp,
1644
1718
  withXmp,
1645
1719
  keepMetadata,
package/lib/resize.js CHANGED
@@ -540,10 +540,19 @@ function extract (options) {
540
540
  * })
541
541
  * .toBuffer();
542
542
  *
543
+ * @example
544
+ * // Trim image leaving (up to) a 10 pixel margin around the trimmed content.
545
+ * const output = await sharp(input)
546
+ * .trim({
547
+ * margin: 10
548
+ * })
549
+ * .toBuffer();
550
+ *
543
551
  * @param {Object} [options]
544
552
  * @param {string|Object} [options.background='top-left pixel'] - Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
545
553
  * @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number.
546
554
  * @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic?
555
+ * @param {number} [options.margin=0] - Leave a margin around trimmed content, value is in pixels.
547
556
  * @returns {Sharp}
548
557
  * @throws {Error} Invalid parameters
549
558
  */
@@ -564,6 +573,13 @@ function trim (options) {
564
573
  if (is.defined(options.lineArt)) {
565
574
  this._setBooleanOption('trimLineArt', options.lineArt);
566
575
  }
576
+ if (is.defined(options.margin)) {
577
+ if (is.integer(options.margin) && options.margin >= 0) {
578
+ this.options.trimMargin = options.margin;
579
+ } else {
580
+ throw is.invalidParameterError('margin', 'positive integer', options.margin);
581
+ }
582
+ }
567
583
  } else {
568
584
  throw is.invalidParameterError('trim', 'object', options);
569
585
  }
package/lib/sharp.js CHANGED
@@ -7,12 +7,13 @@
7
7
 
8
8
  const { familySync, versionSync } = require('detect-libc');
9
9
 
10
+ const { version } = require('../package.json');
10
11
  const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
11
12
  const runtimePlatform = runtimePlatformArch();
12
13
 
13
14
  const paths = [
14
- `../src/build/Release/sharp-${runtimePlatform}.node`,
15
- '../src/build/Release/sharp-wasm32.node',
15
+ `../src/build/Release/sharp-${runtimePlatform}-${version}.node`,
16
+ `../src/build/Release/sharp-wasm32-${version}.node`,
16
17
  `@img/sharp-${runtimePlatform}/sharp.node`,
17
18
  '@img/sharp-wasm32/sharp.node'
18
19
  ];
@@ -72,6 +73,7 @@ if (sharp) {
72
73
  } else {
73
74
  help.push(
74
75
  `- Manually install libvips >= ${minimumLibvipsVersion}`,
76
+ ' See https://sharp.pixelplumbing.com/install#building-from-source',
75
77
  '- Add experimental WebAssembly-based dependencies:',
76
78
  ' npm install --cpu=wasm32 sharp',
77
79
  ' npm install @img/sharp-wasm32'
package/lib/utility.js CHANGED
@@ -24,7 +24,7 @@ const format = sharp.format();
24
24
  format.heif.output.alias = ['avif', 'heic'];
25
25
  format.jpeg.output.alias = ['jpe', 'jpg'];
26
26
  format.tiff.output.alias = ['tif'];
27
- format.jp2k.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
27
+ format.jp2.output.alias = ['j2c', 'j2k', 'jp2', 'jpx'];
28
28
 
29
29
  /**
30
30
  * An Object containing the available interpolators and their proper values
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.5-rc.1",
4
+ "version": "0.35.0-rc.0",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://sharp.pixelplumbing.com",
7
7
  "contributors": [
@@ -89,12 +89,12 @@
89
89
  "Lachlan Newman <lachnewman007@gmail.com>",
90
90
  "Dennis Beatty <dennis@dcbeatty.com>",
91
91
  "Ingvar Stepanyan <me@rreverser.com>",
92
- "Don Denton <don@happycollision.com>"
92
+ "Don Denton <don@happycollision.com>",
93
+ "Dmytro Tiapukhin <cool.gegeg@gmail.com>"
93
94
  ],
94
95
  "scripts": {
95
96
  "build": "node install/build.js",
96
- "install": "node install/check.js || npm run build",
97
- "clean": "rm -rf src/build/ .nyc_output/ coverage/ test/fixtures/output.*",
97
+ "clean": "rm -rf src/build/ test/fixtures/output.*",
98
98
  "test": "npm run lint && npm run test-unit",
99
99
  "lint": "npm run lint-cpp && npm run lint-js && npm run lint-types",
100
100
  "lint-cpp": "cpplint --quiet src/*.h src/*.cc",
@@ -144,65 +144,58 @@
144
144
  "semver": "^7.7.3"
145
145
  },
146
146
  "optionalDependencies": {
147
- "@img/sharp-darwin-arm64": "0.34.5-rc.1",
148
- "@img/sharp-darwin-x64": "0.34.5-rc.1",
149
- "@img/sharp-libvips-darwin-arm64": "1.2.4",
150
- "@img/sharp-libvips-darwin-x64": "1.2.4",
151
- "@img/sharp-libvips-linux-arm": "1.2.4",
152
- "@img/sharp-libvips-linux-arm64": "1.2.4",
153
- "@img/sharp-libvips-linux-ppc64": "1.2.4",
154
- "@img/sharp-libvips-linux-riscv64": "1.2.4",
155
- "@img/sharp-libvips-linux-s390x": "1.2.4",
156
- "@img/sharp-libvips-linux-x64": "1.2.4",
157
- "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
158
- "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
159
- "@img/sharp-linux-arm": "0.34.5-rc.1",
160
- "@img/sharp-linux-arm64": "0.34.5-rc.1",
161
- "@img/sharp-linux-ppc64": "0.34.5-rc.1",
162
- "@img/sharp-linux-riscv64": "0.34.5-rc.1",
163
- "@img/sharp-linux-s390x": "0.34.5-rc.1",
164
- "@img/sharp-linux-x64": "0.34.5-rc.1",
165
- "@img/sharp-linuxmusl-arm64": "0.34.5-rc.1",
166
- "@img/sharp-linuxmusl-x64": "0.34.5-rc.1",
167
- "@img/sharp-wasm32": "0.34.5-rc.1",
168
- "@img/sharp-win32-arm64": "0.34.5-rc.1",
169
- "@img/sharp-win32-ia32": "0.34.5-rc.1",
170
- "@img/sharp-win32-x64": "0.34.5-rc.1"
147
+ "@img/sharp-darwin-arm64": "0.35.0-rc.0",
148
+ "@img/sharp-darwin-x64": "0.35.0-rc.0",
149
+ "@img/sharp-libvips-darwin-arm64": "1.3.0-rc.2",
150
+ "@img/sharp-libvips-darwin-x64": "1.3.0-rc.2",
151
+ "@img/sharp-libvips-linux-arm": "1.3.0-rc.2",
152
+ "@img/sharp-libvips-linux-arm64": "1.3.0-rc.2",
153
+ "@img/sharp-libvips-linux-ppc64": "1.3.0-rc.2",
154
+ "@img/sharp-libvips-linux-riscv64": "1.3.0-rc.2",
155
+ "@img/sharp-libvips-linux-s390x": "1.3.0-rc.2",
156
+ "@img/sharp-libvips-linux-x64": "1.3.0-rc.2",
157
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.0-rc.2",
158
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.0-rc.2",
159
+ "@img/sharp-linux-arm": "0.35.0-rc.0",
160
+ "@img/sharp-linux-arm64": "0.35.0-rc.0",
161
+ "@img/sharp-linux-ppc64": "0.35.0-rc.0",
162
+ "@img/sharp-linux-riscv64": "0.35.0-rc.0",
163
+ "@img/sharp-linux-s390x": "0.35.0-rc.0",
164
+ "@img/sharp-linux-x64": "0.35.0-rc.0",
165
+ "@img/sharp-linuxmusl-arm64": "0.35.0-rc.0",
166
+ "@img/sharp-linuxmusl-x64": "0.35.0-rc.0",
167
+ "@img/sharp-wasm32": "0.35.0-rc.0",
168
+ "@img/sharp-win32-arm64": "0.35.0-rc.0",
169
+ "@img/sharp-win32-ia32": "0.35.0-rc.0",
170
+ "@img/sharp-win32-x64": "0.35.0-rc.0"
171
171
  },
172
172
  "devDependencies": {
173
- "@biomejs/biome": "^2.3.4",
173
+ "@biomejs/biome": "^2.3.10",
174
174
  "@cpplint/cli": "^0.1.0",
175
- "@emnapi/runtime": "^1.7.0",
176
- "@img/sharp-libvips-dev": "1.2.4",
177
- "@img/sharp-libvips-dev-wasm32": "1.2.4",
178
- "@img/sharp-libvips-win32-arm64": "1.2.4",
179
- "@img/sharp-libvips-win32-ia32": "1.2.4",
180
- "@img/sharp-libvips-win32-x64": "1.2.4",
175
+ "@emnapi/runtime": "^1.7.1",
176
+ "@img/sharp-libvips-dev": "1.3.0-rc.2",
177
+ "@img/sharp-libvips-dev-wasm32": "1.3.0-rc.2",
178
+ "@img/sharp-libvips-win32-arm64": "1.3.0-rc.2",
179
+ "@img/sharp-libvips-win32-ia32": "1.3.0-rc.2",
180
+ "@img/sharp-libvips-win32-x64": "1.3.0-rc.2",
181
181
  "@types/node": "*",
182
- "emnapi": "^1.7.0",
183
- "exif-reader": "^2.0.2",
182
+ "emnapi": "^1.7.1",
183
+ "exif-reader": "^2.0.3",
184
184
  "extract-zip": "^2.0.1",
185
185
  "icc": "^3.0.0",
186
- "jsdoc-to-markdown": "^9.1.3",
187
186
  "node-addon-api": "^8.5.0",
188
- "node-gyp": "^11.5.0",
187
+ "node-gyp": "^12.1.0",
189
188
  "tar-fs": "^3.1.1",
190
189
  "tsd": "^0.33.0"
191
190
  },
192
191
  "license": "Apache-2.0",
193
192
  "engines": {
194
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
193
+ "node": ">=20.9.0"
195
194
  },
196
195
  "config": {
197
- "libvips": ">=8.17.3"
196
+ "libvips": ">=8.18.0"
198
197
  },
199
198
  "funding": {
200
199
  "url": "https://opencollective.com/libvips"
201
- },
202
- "cc": {
203
- "linelength": "120",
204
- "filter": [
205
- "build/include"
206
- ]
207
200
  }
208
201
  }
package/src/binding.gyp CHANGED
@@ -5,6 +5,7 @@
5
5
  'variables': {
6
6
  'vips_version': '<!(node -p "require(\'../lib/libvips\').minimumLibvipsVersion")',
7
7
  'platform_and_arch': '<!(node -p "require(\'../lib/libvips\').buildPlatformArch()")',
8
+ 'sharp_version': '<!(node -p "require(\'../package.json\').version")',
8
9
  'sharp_libvips_version': '<!(node -p "require(\'../package.json\').optionalDependencies[\'@img/sharp-libvips-<(platform_and_arch)\']")',
9
10
  'sharp_libvips_yarn_locator': '<!(node -p "require(\'../lib/libvips\').yarnLocator()")',
10
11
  'sharp_libvips_include_dir': '<!(node -p "require(\'../lib/libvips\').buildSharpLibvipsIncludeDir()")',
@@ -81,7 +82,7 @@
81
82
  }]
82
83
  ]
83
84
  }, {
84
- 'target_name': 'sharp-<(platform_and_arch)',
85
+ 'target_name': 'sharp-<(platform_and_arch)-<(sharp_version)',
85
86
  'defines': [
86
87
  'G_DISABLE_ASSERT',
87
88
  'G_DISABLE_CAST_CHECKS',
@@ -120,7 +121,7 @@
120
121
  'conditions': [
121
122
  ['use_global_libvips == "true"', {
122
123
  # Use pkg-config for include and lib
123
- 'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s\/-I//g)'],
124
+ 'include_dirs': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags-only-I vips-cpp vips glib-2.0 | sed s/-I//g)'],
124
125
  'libraries': ['<!@(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips-cpp)'],
125
126
  'defines': [
126
127
  'SHARP_USE_GLOBAL_LIBVIPS'
@@ -282,7 +283,7 @@
282
283
  'target_name': 'copy-dll',
283
284
  'type': 'none',
284
285
  'dependencies': [
285
- 'sharp-<(platform_and_arch)'
286
+ 'sharp-<(platform_and_arch)-<(sharp_version)'
286
287
  ],
287
288
  'conditions': [
288
289
  ['OS == "win"', {
package/src/common.cc CHANGED
@@ -289,6 +289,7 @@ namespace sharp {
289
289
  case ImageType::JXL: id = "jxl"; break;
290
290
  case ImageType::RAD: id = "rad"; break;
291
291
  case ImageType::DCRAW: id = "dcraw"; break;
292
+ case ImageType::UHDR: id = "uhdr"; break;
292
293
  case ImageType::VIPS: id = "vips"; break;
293
294
  case ImageType::RAW: id = "raw"; break;
294
295
  case ImageType::UNKNOWN: id = "unknown"; break;
@@ -339,6 +340,9 @@ namespace sharp {
339
340
  { "VipsForeignLoadRadBuffer", ImageType::RAD },
340
341
  { "VipsForeignLoadDcRawFile", ImageType::DCRAW },
341
342
  { "VipsForeignLoadDcRawBuffer", ImageType::DCRAW },
343
+ { "VipsForeignLoadUhdr", ImageType::UHDR },
344
+ { "VipsForeignLoadUhdrFile", ImageType::UHDR },
345
+ { "VipsForeignLoadUhdrBuffer", ImageType::UHDR },
342
346
  { "VipsForeignLoadVips", ImageType::VIPS },
343
347
  { "VipsForeignLoadVipsFile", ImageType::VIPS },
344
348
  { "VipsForeignLoadRaw", ImageType::RAW }
@@ -356,6 +360,9 @@ namespace sharp {
356
360
  imageType = it->second;
357
361
  }
358
362
  }
363
+ if (imageType == ImageType::UHDR) {
364
+ imageType = ImageType::JPEG;
365
+ }
359
366
  return imageType;
360
367
  }
361
368
 
@@ -375,6 +382,9 @@ namespace sharp {
375
382
  imageType = ImageType::MISSING;
376
383
  }
377
384
  }
385
+ if (imageType == ImageType::UHDR) {
386
+ imageType = ImageType::JPEG;
387
+ }
378
388
  return imageType;
379
389
  }
380
390
 
@@ -1127,4 +1137,20 @@ namespace sharp {
1127
1137
  }
1128
1138
  return image;
1129
1139
  }
1140
+
1141
+ /*
1142
+ Does this image have a gain map?
1143
+ */
1144
+ bool HasGainMap(VImage image) {
1145
+ return image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB;
1146
+ }
1147
+
1148
+ /*
1149
+ Removes gain map, if any.
1150
+ */
1151
+ VImage RemoveGainMap(VImage image) {
1152
+ VImage copy = image.copy();
1153
+ copy.remove("gainmap-data");
1154
+ return copy;
1155
+ }
1130
1156
  } // namespace sharp
package/src/common.h CHANGED
@@ -18,9 +18,9 @@
18
18
  // Verify platform and compiler compatibility
19
19
 
20
20
  #if (VIPS_MAJOR_VERSION < 8) || \
21
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 17) || \
22
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 17 && VIPS_MICRO_VERSION < 3)
23
- #error "libvips version 8.17.3+ is required - please see https://sharp.pixelplumbing.com/install"
21
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 18) || \
22
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 18 && VIPS_MICRO_VERSION < 0)
23
+ #error "libvips version 8.18.0+ is required - please see https://sharp.pixelplumbing.com/install"
24
24
  #endif
25
25
 
26
26
  #if defined(__has_include)
@@ -173,6 +173,7 @@ namespace sharp {
173
173
  JXL,
174
174
  RAD,
175
175
  DCRAW,
176
+ UHDR,
176
177
  VIPS,
177
178
  RAW,
178
179
  UNKNOWN,
@@ -397,6 +398,16 @@ namespace sharp {
397
398
  */
398
399
  VImage StaySequential(VImage image, bool condition = true);
399
400
 
401
+ /*
402
+ Does this image have a gain map?
403
+ */
404
+ bool HasGainMap(VImage image);
405
+
406
+ /*
407
+ Removes gain map, if any.
408
+ */
409
+ VImage RemoveGainMap(VImage image);
410
+
400
411
  } // namespace sharp
401
412
 
402
413
  #endif // SRC_COMMON_H_
package/src/metadata.cc CHANGED
@@ -141,6 +141,14 @@ class MetadataWorker : public Napi::AsyncWorker {
141
141
  memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
142
142
  baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
143
143
  }
144
+ // Gain Map
145
+ if (image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB) {
146
+ size_t gainMapLength;
147
+ void const *gainMap = image.get_blob("gainmap-data", &gainMapLength);
148
+ baton->gainMap = static_cast<char *>(g_malloc(gainMapLength));
149
+ memcpy(baton->gainMap, gainMap, gainMapLength);
150
+ baton->gainMapLength = gainMapLength;
151
+ }
144
152
  // PNG comments
145
153
  vips_image_map(image.get_image(), readPNGComment, &baton->comments);
146
154
  }
@@ -182,10 +190,6 @@ class MetadataWorker : public Napi::AsyncWorker {
182
190
  info.Set("isPalette", baton->isPalette);
183
191
  if (baton->bitsPerSample > 0) {
184
192
  info.Set("bitsPerSample", baton->bitsPerSample);
185
- if (baton->isPalette) {
186
- // Deprecated, remove with libvips 8.17.0
187
- info.Set("paletteBitDepth", baton->bitsPerSample);
188
- }
189
193
  }
190
194
  if (baton->pages > 0) {
191
195
  info.Set("pages", baton->pages);
@@ -276,6 +280,12 @@ class MetadataWorker : public Napi::AsyncWorker {
276
280
  Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
277
281
  baton->tifftagPhotoshopLength, sharp::FreeCallback));
278
282
  }
283
+ if (baton->gainMapLength > 0) {
284
+ Napi::Object gainMap = Napi::Object::New(env);
285
+ info.Set("gainMap", gainMap);
286
+ gainMap.Set("image",
287
+ Napi::Buffer<char>::NewOrCopy(env, baton->gainMap, baton->gainMapLength, sharp::FreeCallback));
288
+ }
279
289
  if (baton->comments.size() > 0) {
280
290
  int i = 0;
281
291
  Napi::Array comments = Napi::Array::New(env, baton->comments.size());
package/src/metadata.h CHANGED
@@ -53,6 +53,8 @@ struct MetadataBaton {
53
53
  size_t xmpLength;
54
54
  char *tifftagPhotoshop;
55
55
  size_t tifftagPhotoshopLength;
56
+ char *gainMap;
57
+ size_t gainMapLength;
56
58
  MetadataComments comments;
57
59
  std::string err;
58
60
 
@@ -82,7 +84,9 @@ struct MetadataBaton {
82
84
  xmp(nullptr),
83
85
  xmpLength(0),
84
86
  tifftagPhotoshop(nullptr),
85
- tifftagPhotoshopLength(0) {}
87
+ tifftagPhotoshopLength(0),
88
+ gainMap(nullptr),
89
+ gainMapLength(0) {}
86
90
  };
87
91
 
88
92
  Napi::Value metadata(const Napi::CallbackInfo& info);
package/src/operations.cc CHANGED
@@ -285,7 +285,7 @@ namespace sharp {
285
285
  /*
286
286
  Trim an image
287
287
  */
288
- VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt) {
288
+ VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin) {
289
289
  if (image.width() < 3 && image.height() < 3) {
290
290
  throw VError("Image to trim must be at least 3x3 pixels");
291
291
  }
@@ -320,18 +320,36 @@ namespace sharp {
320
320
  if (widthA > 0 && heightA > 0) {
321
321
  if (width > 0 && height > 0) {
322
322
  // Combined bounding box (B)
323
- int const leftB = std::min(left, leftA);
324
- int const topB = std::min(top, topA);
325
- int const widthB = std::max(left + width, leftA + widthA) - leftB;
326
- int const heightB = std::max(top + height, topA + heightA) - topB;
323
+ int leftB = std::min(left, leftA);
324
+ int topB = std::min(top, topA);
325
+ int widthB = std::max(left + width, leftA + widthA) - leftB;
326
+ int heightB = std::max(top + height, topA + heightA) - topB;
327
+ if (margin > 0) {
328
+ leftB = std::max(0, leftB - margin);
329
+ topB = std::max(0, topB - margin);
330
+ widthB = std::min(image.width() - leftB, widthB + 2 * margin);
331
+ heightB = std::min(image.height() - topB, heightB + 2 * margin);
332
+ }
327
333
  return image.extract_area(leftB, topB, widthB, heightB);
328
334
  } else {
329
335
  // Use alpha only
336
+ if (margin > 0) {
337
+ leftA = std::max(0, leftA - margin);
338
+ topA = std::max(0, topA - margin);
339
+ widthA = std::min(image.width() - leftA, widthA + 2 * margin);
340
+ heightA = std::min(image.height() - topA, heightA + 2 * margin);
341
+ }
330
342
  return image.extract_area(leftA, topA, widthA, heightA);
331
343
  }
332
344
  }
333
345
  }
334
346
  if (width > 0 && height > 0) {
347
+ if (margin > 0) {
348
+ left = std::max(0, left - margin);
349
+ top = std::max(0, top - margin);
350
+ width = std::min(image.width() - left, width + 2 * margin);
351
+ height = std::min(image.height() - top, height + 2 * margin);
352
+ }
335
353
  return image.extract_area(left, top, width, height);
336
354
  }
337
355
  return image;
package/src/operations.h CHANGED
@@ -82,7 +82,7 @@ namespace sharp {
82
82
  /*
83
83
  Trim an image
84
84
  */
85
- VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt);
85
+ VImage Trim(VImage image, std::vector<double> background, double threshold, bool const lineArt, int const margin);
86
86
 
87
87
  /*
88
88
  * Linear adjustment (a * in + b)
package/src/pipeline.cc CHANGED
@@ -153,7 +153,7 @@ class PipelineWorker : public Napi::AsyncWorker {
153
153
  if (baton->trimThreshold >= 0.0) {
154
154
  MultiPageUnsupported(nPages, "Trim");
155
155
  image = sharp::StaySequential(image);
156
- image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
156
+ image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);
157
157
  baton->trimOffsetLeft = image.xoffset();
158
158
  baton->trimOffsetTop = image.yoffset();
159
159
  }
@@ -296,6 +296,14 @@ class PipelineWorker : public Napi::AsyncWorker {
296
296
  if (baton->input->autoOrient) {
297
297
  image = sharp::RemoveExifOrientation(image);
298
298
  }
299
+ if (sharp::HasGainMap(image)) {
300
+ if (baton->withGainMap) {
301
+ image = image.uhdr2scRGB();
302
+ }
303
+ image = sharp::RemoveGainMap(image);
304
+ } else {
305
+ baton->withGainMap = false;
306
+ }
299
307
 
300
308
  // Any pre-shrinking may already have been done
301
309
  inputWidth = image.width();
@@ -335,7 +343,7 @@ class PipelineWorker : public Napi::AsyncWorker {
335
343
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
336
344
  image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
337
345
  baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
338
- !baton->input->ignoreIcc
346
+ !baton->input->ignoreIcc && !baton->withGainMap
339
347
  ) {
340
348
  // Convert to sRGB/P3 using embedded profile
341
349
  try {
@@ -957,6 +965,7 @@ class PipelineWorker : public Napi::AsyncWorker {
957
965
  ->set("effort", baton->webpEffort)
958
966
  ->set("min_size", baton->webpMinSize)
959
967
  ->set("mixed", baton->webpMixed)
968
+ ->set("exact", baton->webpExact)
960
969
  ->set("alpha_q", baton->webpAlphaQuality)));
961
970
  baton->bufferOut = static_cast<char*>(area->data);
962
971
  baton->bufferOutLength = area->length;
@@ -1024,6 +1033,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1024
1033
  ->set("compression", baton->heifCompression)
1025
1034
  ->set("effort", baton->heifEffort)
1026
1035
  ->set("bitdepth", baton->heifBitdepth)
1036
+ ->set("tune", baton->heifTune.c_str())
1027
1037
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1028
1038
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1029
1039
  ->set("lossless", baton->heifLossless)));
@@ -1168,6 +1178,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1168
1178
  ->set("effort", baton->webpEffort)
1169
1179
  ->set("min_size", baton->webpMinSize)
1170
1180
  ->set("mixed", baton->webpMixed)
1181
+ ->set("exact", baton->webpExact)
1171
1182
  ->set("alpha_q", baton->webpAlphaQuality));
1172
1183
  baton->formatOut = "webp";
1173
1184
  } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1223,6 +1234,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1223
1234
  ->set("compression", baton->heifCompression)
1224
1235
  ->set("effort", baton->heifEffort)
1225
1236
  ->set("bitdepth", baton->heifBitdepth)
1237
+ ->set("tune", baton->heifTune.c_str())
1226
1238
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1227
1239
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1228
1240
  ->set("lossless", baton->heifLossless));
@@ -1337,12 +1349,21 @@ class PipelineWorker : public Napi::AsyncWorker {
1337
1349
  }
1338
1350
 
1339
1351
  if (baton->bufferOutLength > 0) {
1340
- // Add buffer size to info
1341
1352
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
1342
- // Pass ownership of output data to Buffer instance
1343
- Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1344
- baton->bufferOutLength, sharp::FreeCallback);
1345
- Callback().Call(Receiver().Value(), { env.Null(), data, info });
1353
+ if (baton->typedArrayOut) {
1354
+ // ECMAScript ArrayBuffer with Uint8Array view
1355
+ Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
1356
+ memcpy(ab.Data(), baton->bufferOut, baton->bufferOutLength);
1357
+ sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
1358
+ Napi::TypedArrayOf<uint8_t> data = Napi::TypedArrayOf<uint8_t>::New(env,
1359
+ baton->bufferOutLength, ab, 0, napi_uint8_array);
1360
+ Callback().Call(Receiver().Value(), { env.Null(), data, info });
1361
+ } else {
1362
+ // Node.js Buffer
1363
+ Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1364
+ baton->bufferOutLength, sharp::FreeCallback);
1365
+ Callback().Call(Receiver().Value(), { env.Null(), data, info });
1366
+ }
1346
1367
  } else {
1347
1368
  // Add file size to info
1348
1369
  if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
@@ -1469,6 +1490,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1469
1490
  {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
1470
1491
  {"min_size", baton->webpMinSize ? "true" : "false"},
1471
1492
  {"mixed", baton->webpMixed ? "true" : "false"},
1493
+ {"exact", baton->webpExact ? "true" : "false"},
1472
1494
  {"effort", std::to_string(baton->webpEffort)}
1473
1495
  };
1474
1496
  suffix = AssembleSuffixString(".webp", options);
@@ -1615,6 +1637,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1615
1637
  baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
1616
1638
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
1617
1639
  baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
1640
+ baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
1618
1641
  baton->gamma = sharp::AttrAsDouble(options, "gamma");
1619
1642
  baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
1620
1643
  baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
@@ -1692,6 +1715,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1692
1715
  // Output
1693
1716
  baton->formatOut = sharp::AttrAsStr(options, "formatOut");
1694
1717
  baton->fileOut = sharp::AttrAsStr(options, "fileOut");
1718
+ baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
1695
1719
  baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
1696
1720
  baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
1697
1721
  baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
@@ -1706,6 +1730,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1706
1730
  }
1707
1731
  baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
1708
1732
  baton->withXmp = sharp::AttrAsStr(options, "withXmp");
1733
+ baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
1709
1734
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1710
1735
  baton->loop = sharp::AttrAsUint32(options, "loop");
1711
1736
  baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@@ -1741,6 +1766,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1741
1766
  baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
1742
1767
  baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
1743
1768
  baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
1769
+ baton->webpExact = sharp::AttrAsBool(options, "webpExact");
1744
1770
  baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
1745
1771
  baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
1746
1772
  baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
@@ -1775,6 +1801,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1775
1801
  baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
1776
1802
  baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1777
1803
  baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
1804
+ baton->heifTune = sharp::AttrAsStr(options, "heifTune");
1778
1805
  baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
1779
1806
  baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
1780
1807
  baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
package/src/pipeline.h CHANGED
@@ -48,6 +48,7 @@ struct PipelineBaton {
48
48
  size_t bufferOutLength;
49
49
  int pageHeightOut;
50
50
  int pagesOut;
51
+ bool typedArrayOut;
51
52
  std::vector<Composite *> composite;
52
53
  std::vector<sharp::InputDescriptor *> joinChannelIn;
53
54
  int topOffsetPre;
@@ -101,6 +102,7 @@ struct PipelineBaton {
101
102
  bool trimLineArt;
102
103
  int trimOffsetLeft;
103
104
  int trimOffsetTop;
105
+ int trimMargin;
104
106
  std::vector<double> linearA;
105
107
  std::vector<double> linearB;
106
108
  int dilateWidth;
@@ -167,6 +169,7 @@ struct PipelineBaton {
167
169
  int webpEffort;
168
170
  bool webpMinSize;
169
171
  bool webpMixed;
172
+ bool webpExact;
170
173
  int gifBitdepth;
171
174
  int gifEffort;
172
175
  double gifDither;
@@ -194,6 +197,7 @@ struct PipelineBaton {
194
197
  std::string heifChromaSubsampling;
195
198
  bool heifLossless;
196
199
  int heifBitdepth;
200
+ std::string heifTune;
197
201
  double jxlDistance;
198
202
  int jxlDecodingTier;
199
203
  int jxlEffort;
@@ -208,6 +212,7 @@ struct PipelineBaton {
208
212
  std::unordered_map<std::string, std::string> withExif;
209
213
  bool withExifMerge;
210
214
  std::string withXmp;
215
+ bool withGainMap;
211
216
  int timeoutSeconds;
212
217
  std::vector<double> convKernel;
213
218
  int convKernelWidth;
@@ -242,6 +247,7 @@ struct PipelineBaton {
242
247
  bufferOutLength(0),
243
248
  pageHeightOut(0),
244
249
  pagesOut(0),
250
+ typedArrayOut(false),
245
251
  topOffsetPre(-1),
246
252
  topOffsetPost(-1),
247
253
  channels(0),
@@ -281,6 +287,7 @@ struct PipelineBaton {
281
287
  trimLineArt(false),
282
288
  trimOffsetLeft(0),
283
289
  trimOffsetTop(0),
290
+ trimMargin(0),
284
291
  linearA{},
285
292
  linearB{},
286
293
  dilateWidth(0),
@@ -344,6 +351,7 @@ struct PipelineBaton {
344
351
  webpEffort(4),
345
352
  webpMinSize(false),
346
353
  webpMixed(false),
354
+ webpExact(false),
347
355
  gifBitdepth(8),
348
356
  gifEffort(7),
349
357
  gifDither(1.0),
@@ -371,6 +379,7 @@ struct PipelineBaton {
371
379
  heifChromaSubsampling("4:4:4"),
372
380
  heifLossless(false),
373
381
  heifBitdepth(8),
382
+ heifTune("ssim"),
374
383
  jxlDistance(1.0),
375
384
  jxlDecodingTier(0),
376
385
  jxlEffort(7),
@@ -381,6 +390,7 @@ struct PipelineBaton {
381
390
  withMetadataOrientation(-1),
382
391
  withMetadataDensity(0.0),
383
392
  withExifMerge(true),
393
+ withGainMap(false),
384
394
  timeoutSeconds(0),
385
395
  convKernelWidth(0),
386
396
  convKernelHeight(0),
package/src/utilities.cc CHANGED
@@ -123,6 +123,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
123
123
  "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
124
124
  "ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad", "dcraw"
125
125
  }) {
126
+ std::string id = f == "jp2k" ? "jp2" : f;
126
127
  // Input
127
128
  const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
128
129
  Napi::Boolean hasInputFile = Napi::Boolean::New(env, oc);
@@ -154,11 +155,11 @@ Napi::Value format(const Napi::CallbackInfo& info) {
154
155
  output.Set("stream", hasOutputBuffer);
155
156
  // Other attributes
156
157
  Napi::Object container = Napi::Object::New(env);
157
- container.Set("id", f);
158
+ container.Set("id", id);
158
159
  container.Set("input", input);
159
160
  container.Set("output", output);
160
161
  // Add to set of formats
161
- format.Set(f, container);
162
+ format.Set(id, container);
162
163
  }
163
164
 
164
165
  // Raw, uncompressed data
package/install/check.js DELETED
@@ -1,14 +0,0 @@
1
- /*!
2
- Copyright 2013 Lovell Fuller and others.
3
- SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- try {
7
- const { useGlobalLibvips } = require('../lib/libvips');
8
- if (useGlobalLibvips() || process.env.npm_config_build_from_source) {
9
- process.exit(1);
10
- }
11
- } catch (err) {
12
- const summary = err.message.split(/\n/).slice(0, 1);
13
- console.log(`sharp: skipping install check: ${summary}`);
14
- }