sharp 0.32.6 → 0.33.4

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/lib/output.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  'use strict';
5
5
 
6
- const path = require('path');
6
+ const path = require('node:path');
7
7
  const is = require('./is');
8
8
  const sharp = require('./sharp');
9
9
 
@@ -86,7 +86,8 @@ function toFile (fileOut, callback) {
86
86
  }
87
87
  } else {
88
88
  this.options.fileOut = fileOut;
89
- return this._pipeline(callback);
89
+ const stack = Error();
90
+ return this._pipeline(callback, stack);
90
91
  }
91
92
  return this;
92
93
  }
@@ -157,43 +158,190 @@ function toBuffer (options, callback) {
157
158
  this.options.resolveWithObject = false;
158
159
  }
159
160
  this.options.fileOut = '';
160
- return this._pipeline(is.fn(options) ? options : callback);
161
+ const stack = Error();
162
+ return this._pipeline(is.fn(options) ? options : callback, stack);
161
163
  }
162
164
 
163
165
  /**
164
- * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
165
- * This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
166
- * unless a custom output profile is provided.
167
- *
168
- * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
169
- * sRGB colour space and strip all metadata, including the removal of any ICC profile.
166
+ * Keep all EXIF metadata from the input image in the output image.
170
167
  *
171
168
  * EXIF metadata is unsupported for TIFF output.
172
169
  *
170
+ * @since 0.33.0
171
+ *
173
172
  * @example
174
- * sharp('input.jpg')
175
- * .withMetadata()
176
- * .toFile('output-with-metadata.jpg')
177
- * .then(info => { ... });
173
+ * const outputWithExif = await sharp(inputWithExif)
174
+ * .keepExif()
175
+ * .toBuffer();
176
+ *
177
+ * @returns {Sharp}
178
+ */
179
+ function keepExif () {
180
+ this.options.keepMetadata |= 0b00001;
181
+ return this;
182
+ }
183
+
184
+ /**
185
+ * Set EXIF metadata in the output image, ignoring any EXIF in the input image.
186
+ *
187
+ * @since 0.33.0
178
188
  *
179
189
  * @example
180
- * // Set output EXIF metadata
181
- * const data = await sharp(input)
182
- * .withMetadata({
183
- * exif: {
184
- * IFD0: {
185
- * Copyright: 'The National Gallery'
186
- * },
187
- * IFD3: {
188
- * GPSLatitudeRef: 'N',
189
- * GPSLatitude: '51/1 30/1 3230/100',
190
- * GPSLongitudeRef: 'W',
191
- * GPSLongitude: '0/1 7/1 4366/100'
192
- * }
190
+ * const dataWithExif = await sharp(input)
191
+ * .withExif({
192
+ * IFD0: {
193
+ * Copyright: 'The National Gallery'
194
+ * },
195
+ * IFD3: {
196
+ * GPSLatitudeRef: 'N',
197
+ * GPSLatitude: '51/1 30/1 3230/100',
198
+ * GPSLongitudeRef: 'W',
199
+ * GPSLongitude: '0/1 7/1 4366/100'
193
200
  * }
194
201
  * })
195
202
  * .toBuffer();
196
203
  *
204
+ * @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
205
+ * @returns {Sharp}
206
+ * @throws {Error} Invalid parameters
207
+ */
208
+ function withExif (exif) {
209
+ if (is.object(exif)) {
210
+ for (const [ifd, entries] of Object.entries(exif)) {
211
+ if (is.object(entries)) {
212
+ for (const [k, v] of Object.entries(entries)) {
213
+ if (is.string(v)) {
214
+ this.options.withExif[`exif-${ifd.toLowerCase()}-${k}`] = v;
215
+ } else {
216
+ throw is.invalidParameterError(`${ifd}.${k}`, 'string', v);
217
+ }
218
+ }
219
+ } else {
220
+ throw is.invalidParameterError(ifd, 'object', entries);
221
+ }
222
+ }
223
+ } else {
224
+ throw is.invalidParameterError('exif', 'object', exif);
225
+ }
226
+ this.options.withExifMerge = false;
227
+ return this.keepExif();
228
+ }
229
+
230
+ /**
231
+ * Update EXIF metadata from the input image in the output image.
232
+ *
233
+ * @since 0.33.0
234
+ *
235
+ * @example
236
+ * const dataWithMergedExif = await sharp(inputWithExif)
237
+ * .withExifMerge({
238
+ * IFD0: {
239
+ * Copyright: 'The National Gallery'
240
+ * }
241
+ * })
242
+ * .toBuffer();
243
+ *
244
+ * @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
245
+ * @returns {Sharp}
246
+ * @throws {Error} Invalid parameters
247
+ */
248
+ function withExifMerge (exif) {
249
+ this.withExif(exif);
250
+ this.options.withExifMerge = true;
251
+ return this;
252
+ }
253
+
254
+ /**
255
+ * Keep ICC profile from the input image in the output image.
256
+ *
257
+ * Where necessary, will attempt to convert the output colour space to match the profile.
258
+ *
259
+ * @since 0.33.0
260
+ *
261
+ * @example
262
+ * const outputWithIccProfile = await sharp(inputWithIccProfile)
263
+ * .keepIccProfile()
264
+ * .toBuffer();
265
+ *
266
+ * @returns {Sharp}
267
+ */
268
+ function keepIccProfile () {
269
+ this.options.keepMetadata |= 0b01000;
270
+ return this;
271
+ }
272
+
273
+ /**
274
+ * Transform using an ICC profile and attach to the output image.
275
+ *
276
+ * This can either be an absolute filesystem path or
277
+ * built-in profile name (`srgb`, `p3`, `cmyk`).
278
+ *
279
+ * @since 0.33.0
280
+ *
281
+ * @example
282
+ * const outputWithP3 = await sharp(input)
283
+ * .withIccProfile('p3')
284
+ * .toBuffer();
285
+ *
286
+ * @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
287
+ * @param {Object} [options]
288
+ * @param {number} [options.attach=true] Should the ICC profile be included in the output image metadata?
289
+ * @returns {Sharp}
290
+ * @throws {Error} Invalid parameters
291
+ */
292
+ function withIccProfile (icc, options) {
293
+ if (is.string(icc)) {
294
+ this.options.withIccProfile = icc;
295
+ } else {
296
+ throw is.invalidParameterError('icc', 'string', icc);
297
+ }
298
+ this.keepIccProfile();
299
+ if (is.object(options)) {
300
+ if (is.defined(options.attach)) {
301
+ if (is.bool(options.attach)) {
302
+ if (!options.attach) {
303
+ this.options.keepMetadata &= ~0b01000;
304
+ }
305
+ } else {
306
+ throw is.invalidParameterError('attach', 'boolean', options.attach);
307
+ }
308
+ }
309
+ }
310
+ return this;
311
+ }
312
+
313
+ /**
314
+ * Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
315
+ *
316
+ * The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
317
+ * sRGB colour space and strip all metadata, including the removal of any ICC profile.
318
+ *
319
+ * @since 0.33.0
320
+ *
321
+ * @example
322
+ * const outputWithMetadata = await sharp(inputWithMetadata)
323
+ * .keepMetadata()
324
+ * .toBuffer();
325
+ *
326
+ * @returns {Sharp}
327
+ */
328
+ function keepMetadata () {
329
+ this.options.keepMetadata = 0b11111;
330
+ return this;
331
+ }
332
+
333
+ /**
334
+ * Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
335
+ *
336
+ * This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
337
+ *
338
+ * Allows orientation and density to be set or updated.
339
+ *
340
+ * @example
341
+ * const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
342
+ * .withMetadata()
343
+ * .toBuffer();
344
+ *
197
345
  * @example
198
346
  * // Set output metadata to 96 DPI
199
347
  * const data = await sharp(input)
@@ -201,15 +349,14 @@ function toBuffer (options, callback) {
201
349
  * .toBuffer();
202
350
  *
203
351
  * @param {Object} [options]
204
- * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
205
- * @param {string} [options.icc='srgb'] Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB.
206
- * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
352
+ * @param {number} [options.orientation] Used to update the EXIF `Orientation` tag, integer between 1 and 8.
207
353
  * @param {number} [options.density] Number of pixels per inch (DPI).
208
354
  * @returns {Sharp}
209
355
  * @throws {Error} Invalid parameters
210
356
  */
211
357
  function withMetadata (options) {
212
- this.options.withMetadata = is.bool(options) ? options : true;
358
+ this.keepMetadata();
359
+ this.withIccProfile('srgb');
213
360
  if (is.object(options)) {
214
361
  if (is.defined(options.orientation)) {
215
362
  if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
@@ -226,30 +373,10 @@ function withMetadata (options) {
226
373
  }
227
374
  }
228
375
  if (is.defined(options.icc)) {
229
- if (is.string(options.icc)) {
230
- this.options.withMetadataIcc = options.icc;
231
- } else {
232
- throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
233
- }
376
+ this.withIccProfile(options.icc);
234
377
  }
235
378
  if (is.defined(options.exif)) {
236
- if (is.object(options.exif)) {
237
- for (const [ifd, entries] of Object.entries(options.exif)) {
238
- if (is.object(entries)) {
239
- for (const [k, v] of Object.entries(entries)) {
240
- if (is.string(v)) {
241
- this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
242
- } else {
243
- throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
244
- }
245
- }
246
- } else {
247
- throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
248
- }
249
- }
250
- } else {
251
- throw is.invalidParameterError('exif', 'object', options.exif);
252
- }
379
+ this.withExifMerge(options.exif);
253
380
  }
254
381
  }
255
382
  return this;
@@ -377,10 +504,14 @@ function jpeg (options) {
377
504
  /**
378
505
  * Use these PNG options for output image.
379
506
  *
380
- * By default, PNG output is full colour at 8 or 16 bits per pixel.
507
+ * By default, PNG output is full colour at 8 bits per pixel.
508
+ *
381
509
  * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
382
510
  * Set `palette` to `true` for slower, indexed PNG output.
383
511
  *
512
+ * For 16 bits per pixel output, convert to `rgb16` via
513
+ * {@link /api-colour#tocolourspace|toColourspace}.
514
+ *
384
515
  * @example
385
516
  * // Convert any input to full colour PNG output
386
517
  * const data = await sharp(input)
@@ -393,6 +524,13 @@ function jpeg (options) {
393
524
  * .png({ palette: true })
394
525
  * .toBuffer();
395
526
  *
527
+ * @example
528
+ * // Output 16 bits per pixel RGB(A)
529
+ * const data = await sharp(input)
530
+ * .toColourspace('rgb16')
531
+ * .png()
532
+ * .toBuffer();
533
+ *
396
534
  * @param {Object} [options]
397
535
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
398
536
  * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
@@ -780,6 +918,7 @@ function trySetAnimationOptions (source, target) {
780
918
  * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
781
919
  * @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
782
920
  * @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
921
+ * @param {boolean} [options.miniswhite=false] - write 1-bit images as miniswhite
783
922
  * @returns {Sharp}
784
923
  * @throws {Error} Invalid options
785
924
  */
@@ -817,6 +956,10 @@ function tiff (options) {
817
956
  throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
818
957
  }
819
958
  }
959
+ // miniswhite
960
+ if (is.defined(options.miniswhite)) {
961
+ this._setBooleanOption('tiffMiniswhite', options.miniswhite);
962
+ }
820
963
  // pyramid
821
964
  if (is.defined(options.pyramid)) {
822
965
  this._setBooleanOption('tiffPyramid', options.pyramid);
@@ -867,10 +1010,8 @@ function tiff (options) {
867
1010
  /**
868
1011
  * Use these AVIF options for output image.
869
1012
  *
870
- * Whilst it is possible to create AVIF images smaller than 16x16 pixels,
871
- * most web browsers do not display these properly.
872
- *
873
1013
  * AVIF image sequences are not supported.
1014
+ * Prebuilt binaries support a bitdepth of 8 only.
874
1015
  *
875
1016
  * @example
876
1017
  * const data = await sharp(input)
@@ -889,6 +1030,7 @@ function tiff (options) {
889
1030
  * @param {boolean} [options.lossless=false] - use lossless compression
890
1031
  * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
891
1032
  * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1033
+ * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
892
1034
  * @returns {Sharp}
893
1035
  * @throws {Error} Invalid options
894
1036
  */
@@ -909,17 +1051,23 @@ function avif (options) {
909
1051
  *
910
1052
  * @since 0.23.0
911
1053
  *
912
- * @param {Object} [options] - output options
1054
+ * @param {Object} options - output options
1055
+ * @param {string} options.compression - compression format: av1, hevc
913
1056
  * @param {number} [options.quality=50] - quality, integer 1-100
914
- * @param {string} [options.compression='av1'] - compression format: av1, hevc
915
1057
  * @param {boolean} [options.lossless=false] - use lossless compression
916
1058
  * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
917
1059
  * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1060
+ * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
918
1061
  * @returns {Sharp}
919
1062
  * @throws {Error} Invalid options
920
1063
  */
921
1064
  function heif (options) {
922
1065
  if (is.object(options)) {
1066
+ if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
1067
+ this.options.heifCompression = options.compression;
1068
+ } else {
1069
+ throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
1070
+ }
923
1071
  if (is.defined(options.quality)) {
924
1072
  if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
925
1073
  this.options.heifQuality = options.quality;
@@ -934,13 +1082,6 @@ function heif (options) {
934
1082
  throw is.invalidParameterError('lossless', 'boolean', options.lossless);
935
1083
  }
936
1084
  }
937
- if (is.defined(options.compression)) {
938
- if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
939
- this.options.heifCompression = options.compression;
940
- } else {
941
- throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
942
- }
943
- }
944
1085
  if (is.defined(options.effort)) {
945
1086
  if (is.integer(options.effort) && is.inRange(options.effort, 0, 9)) {
946
1087
  this.options.heifEffort = options.effort;
@@ -955,6 +1096,18 @@ function heif (options) {
955
1096
  throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
956
1097
  }
957
1098
  }
1099
+ if (is.defined(options.bitdepth)) {
1100
+ if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [8, 10, 12])) {
1101
+ if (options.bitdepth !== 8 && this.constructor.versions.heif) {
1102
+ throw is.invalidParameterError('bitdepth when using prebuilt binaries', 8, options.bitdepth);
1103
+ }
1104
+ this.options.heifBitdepth = options.bitdepth;
1105
+ } else {
1106
+ throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
1107
+ }
1108
+ }
1109
+ } else {
1110
+ throw is.invalidParameterError('options', 'Object', options);
958
1111
  }
959
1112
  return this._updateFormatOut('heif', options);
960
1113
  }
@@ -1104,7 +1257,7 @@ function raw (options) {
1104
1257
  * @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
1105
1258
  * @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
1106
1259
  * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
1107
- * @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
1260
+ * @param {number} [options.skipBlanks=-1] Threshold to skip tile generation. Range is 0-255 for 8-bit images, 0-65535 for 16-bit images. Default is 5 for `google` layout, -1 (no skip) otherwise.
1108
1261
  * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
1109
1262
  * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`.
1110
1263
  * @param {boolean} [options.centre=false] centre image in tile.
@@ -1285,7 +1438,8 @@ function _read () {
1285
1438
  /* istanbul ignore else */
1286
1439
  if (!this.options.streamOut) {
1287
1440
  this.options.streamOut = true;
1288
- this._pipeline();
1441
+ const stack = Error();
1442
+ this._pipeline(undefined, stack);
1289
1443
  }
1290
1444
  }
1291
1445
 
@@ -1294,18 +1448,30 @@ function _read () {
1294
1448
  * Supports callback, stream and promise variants
1295
1449
  * @private
1296
1450
  */
1297
- function _pipeline (callback) {
1451
+ function _pipeline (callback, stack) {
1298
1452
  if (typeof callback === 'function') {
1299
1453
  // output=file/buffer
1300
1454
  if (this._isStreamInput()) {
1301
1455
  // output=file/buffer, input=stream
1302
1456
  this.on('finish', () => {
1303
1457
  this._flattenBufferIn();
1304
- sharp.pipeline(this.options, callback);
1458
+ sharp.pipeline(this.options, (err, data, info) => {
1459
+ if (err) {
1460
+ callback(is.nativeError(err, stack));
1461
+ } else {
1462
+ callback(null, data, info);
1463
+ }
1464
+ });
1305
1465
  });
1306
1466
  } else {
1307
1467
  // output=file/buffer, input=file/buffer
1308
- sharp.pipeline(this.options, callback);
1468
+ sharp.pipeline(this.options, (err, data, info) => {
1469
+ if (err) {
1470
+ callback(is.nativeError(err, stack));
1471
+ } else {
1472
+ callback(null, data, info);
1473
+ }
1474
+ });
1309
1475
  }
1310
1476
  return this;
1311
1477
  } else if (this.options.streamOut) {
@@ -1316,7 +1482,7 @@ function _pipeline (callback) {
1316
1482
  this._flattenBufferIn();
1317
1483
  sharp.pipeline(this.options, (err, data, info) => {
1318
1484
  if (err) {
1319
- this.emit('error', err);
1485
+ this.emit('error', is.nativeError(err, stack));
1320
1486
  } else {
1321
1487
  this.emit('info', info);
1322
1488
  this.push(data);
@@ -1332,7 +1498,7 @@ function _pipeline (callback) {
1332
1498
  // output=stream, input=file/buffer
1333
1499
  sharp.pipeline(this.options, (err, data, info) => {
1334
1500
  if (err) {
1335
- this.emit('error', err);
1501
+ this.emit('error', is.nativeError(err, stack));
1336
1502
  } else {
1337
1503
  this.emit('info', info);
1338
1504
  this.push(data);
@@ -1351,7 +1517,7 @@ function _pipeline (callback) {
1351
1517
  this._flattenBufferIn();
1352
1518
  sharp.pipeline(this.options, (err, data, info) => {
1353
1519
  if (err) {
1354
- reject(err);
1520
+ reject(is.nativeError(err, stack));
1355
1521
  } else {
1356
1522
  if (this.options.resolveWithObject) {
1357
1523
  resolve({ data, info });
@@ -1367,10 +1533,10 @@ function _pipeline (callback) {
1367
1533
  return new Promise((resolve, reject) => {
1368
1534
  sharp.pipeline(this.options, (err, data, info) => {
1369
1535
  if (err) {
1370
- reject(err);
1536
+ reject(is.nativeError(err, stack));
1371
1537
  } else {
1372
1538
  if (this.options.resolveWithObject) {
1373
- resolve({ data: data, info: info });
1539
+ resolve({ data, info });
1374
1540
  } else {
1375
1541
  resolve(data);
1376
1542
  }
@@ -1390,6 +1556,12 @@ module.exports = function (Sharp) {
1390
1556
  // Public
1391
1557
  toFile,
1392
1558
  toBuffer,
1559
+ keepExif,
1560
+ withExif,
1561
+ withExifMerge,
1562
+ keepIccProfile,
1563
+ withIccProfile,
1564
+ keepMetadata,
1393
1565
  withMetadata,
1394
1566
  toFormat,
1395
1567
  jpeg,
package/lib/resize.js CHANGED
@@ -68,6 +68,7 @@ const strategy = {
68
68
  */
69
69
  const kernel = {
70
70
  nearest: 'nearest',
71
+ linear: 'linear',
71
72
  cubic: 'cubic',
72
73
  mitchell: 'mitchell',
73
74
  lanczos2: 'lanczos2',
@@ -135,18 +136,22 @@ function isResizeExpected (options) {
135
136
  *
136
137
  * Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
137
138
  *
138
- * The experimental strategy-based approach resizes so one dimension is at its target length
139
+ * The strategy-based approach initially resizes so one dimension is at its target length
139
140
  * then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
140
141
  * - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
141
142
  * - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
142
143
  *
143
- * Possible interpolation kernels are:
144
+ * Possible downsizing kernels are:
144
145
  * - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
146
+ * - `linear`: Use a [triangle filter](https://en.wikipedia.org/wiki/Triangular_function).
145
147
  * - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
146
148
  * - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
147
149
  * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
148
150
  * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
149
151
  *
152
+ * When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
153
+ * Downsampling kernels without a matching upsampling interpolator map to `cubic`.
154
+ *
150
155
  * Only one resize can occur per pipeline.
151
156
  * Previous calls to `resize` in the same pipeline will be ignored.
152
157
  *
@@ -239,7 +244,7 @@ function isResizeExpected (options) {
239
244
  * @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
240
245
  * @param {String} [options.position='centre'] - A position, gravity or strategy to use when `fit` is `cover` or `contain`.
241
246
  * @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
242
- * @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
247
+ * @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction and the inferred interpolator to use for upsampling. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
243
248
  * @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
244
249
  * @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
245
250
  * @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.
@@ -250,6 +255,9 @@ function resize (widthOrOptions, height, options) {
250
255
  if (isResizeExpected(this.options)) {
251
256
  this.options.debuglog('ignoring previous resize options');
252
257
  }
258
+ if (this.options.widthPost !== -1) {
259
+ this.options.debuglog('operation order will be: extract, resize, extract');
260
+ }
253
261
  if (is.defined(widthOrOptions)) {
254
262
  if (is.object(widthOrOptions) && !is.defined(options)) {
255
263
  options = widthOrOptions;
@@ -437,7 +445,7 @@ function extend (extend) {
437
445
  *
438
446
  * - Use `extract` before `resize` for pre-resize extraction.
439
447
  * - Use `extract` after `resize` for post-resize extraction.
440
- * - Use `extract` before and after for both.
448
+ * - Use `extract` twice and `resize` once for extract-then-resize-then-extract in a fixed operation order.
441
449
  *
442
450
  * @example
443
451
  * sharp(input)
@@ -491,70 +499,67 @@ function extract (options) {
491
499
  *
492
500
  * If the result of this operation would trim an image to nothing then no change is made.
493
501
  *
494
- * The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
495
- * will contain `trimOffsetLeft` and `trimOffsetTop` properties.
502
+ * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
496
503
  *
497
504
  * @example
498
505
  * // Trim pixels with a colour similar to that of the top-left pixel.
499
- * sharp(input)
506
+ * await sharp(input)
500
507
  * .trim()
501
- * .toFile(output, function(err, info) {
502
- * ...
503
- * });
508
+ * .toFile(output);
509
+ *
504
510
  * @example
505
511
  * // Trim pixels with the exact same colour as that of the top-left pixel.
506
- * sharp(input)
507
- * .trim(0)
508
- * .toFile(output, function(err, info) {
509
- * ...
510
- * });
512
+ * await sharp(input)
513
+ * .trim({
514
+ * threshold: 0
515
+ * })
516
+ * .toFile(output);
517
+ *
511
518
  * @example
512
- * // Trim only pixels with a similar colour to red.
513
- * sharp(input)
514
- * .trim("#FF0000")
515
- * .toFile(output, function(err, info) {
516
- * ...
517
- * });
519
+ * // Assume input is line art and trim only pixels with a similar colour to red.
520
+ * const output = await sharp(input)
521
+ * .trim({
522
+ * background: "#FF0000",
523
+ * lineArt: true
524
+ * })
525
+ * .toBuffer();
526
+ *
518
527
  * @example
519
528
  * // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
520
- * sharp(input)
529
+ * const output = await sharp(input)
521
530
  * .trim({
522
531
  * background: "yellow",
523
532
  * threshold: 42,
524
533
  * })
525
- * .toFile(output, function(err, info) {
526
- * ...
527
- * });
534
+ * .toBuffer();
528
535
  *
529
- * @param {string|number|Object} trim - the specific background colour to trim, the threshold for doing so or an Object with both.
530
- * @param {string|Object} [trim.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.
531
- * @param {number} [trim.threshold=10] - the allowed difference from the above colour, a positive number.
536
+ * @param {Object} [options]
537
+ * @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.
538
+ * @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number.
539
+ * @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic?
532
540
  * @returns {Sharp}
533
541
  * @throws {Error} Invalid parameters
534
542
  */
535
- function trim (trim) {
536
- if (!is.defined(trim)) {
537
- this.options.trimThreshold = 10;
538
- } else if (is.string(trim)) {
539
- this._setBackgroundColourOption('trimBackground', trim);
540
- this.options.trimThreshold = 10;
541
- } else if (is.number(trim)) {
542
- if (trim >= 0) {
543
- this.options.trimThreshold = trim;
544
- } else {
545
- throw is.invalidParameterError('threshold', 'positive number', trim);
546
- }
547
- } else if (is.object(trim)) {
548
- this._setBackgroundColourOption('trimBackground', trim.background);
549
- if (!is.defined(trim.threshold)) {
550
- this.options.trimThreshold = 10;
551
- } else if (is.number(trim.threshold) && trim.threshold >= 0) {
552
- this.options.trimThreshold = trim.threshold;
543
+ function trim (options) {
544
+ this.options.trimThreshold = 10;
545
+ if (is.defined(options)) {
546
+ if (is.object(options)) {
547
+ if (is.defined(options.background)) {
548
+ this._setBackgroundColourOption('trimBackground', options.background);
549
+ }
550
+ if (is.defined(options.threshold)) {
551
+ if (is.number(options.threshold) && options.threshold >= 0) {
552
+ this.options.trimThreshold = options.threshold;
553
+ } else {
554
+ throw is.invalidParameterError('threshold', 'positive number', options.threshold);
555
+ }
556
+ }
557
+ if (is.defined(options.lineArt)) {
558
+ this._setBooleanOption('trimLineArt', options.lineArt);
559
+ }
553
560
  } else {
554
- throw is.invalidParameterError('threshold', 'positive number', trim);
561
+ throw is.invalidParameterError('trim', 'object', options);
555
562
  }
556
- } else {
557
- throw is.invalidParameterError('trim', 'string, number or object', trim);
558
563
  }
559
564
  if (isRotationExpected(this.options)) {
560
565
  this.options.rotateBeforePreExtract = true;