sharp 0.26.0 → 0.27.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
@@ -4,7 +4,7 @@
4
4
 
5
5
  The typical use case for this high speed Node.js module
6
6
  is to convert large images in common formats to
7
- smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
7
+ smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
8
8
 
9
9
  Resizing an image is typically 4x-5x faster than using the
10
10
  quickest ImageMagick and GraphicsMagick settings
@@ -16,7 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
16
16
  As well as image resizing, operations such as
17
17
  rotation, extraction, compositing and gamma correction are available.
18
18
 
19
- Most modern macOS, Windows and Linux systems running Node.js v10.16.0+
19
+ Most modern macOS, Windows and Linux systems running Node.js v10+
20
20
  do not require any additional install or runtime dependencies.
21
21
 
22
22
  ## Examples
package/binding.gyp CHANGED
@@ -95,7 +95,7 @@
95
95
  'src/sharp.cc'
96
96
  ],
97
97
  'include_dirs': [
98
- '<!@(node -p "require(\'node-addon-api\').include")',
98
+ '<!(node -p "require(\'node-addon-api\').include_dir")',
99
99
  ],
100
100
  'conditions': [
101
101
  ['use_global_libvips == "true"', {
@@ -25,6 +25,7 @@ const minimumGlibcVersionByArch = {
25
25
  const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
26
26
  const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
27
27
  const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
28
+ const supportsBrotli = ('BrotliDecompress' in zlib);
28
29
 
29
30
  const fail = function (err) {
30
31
  npmLog.error('sharp', err.message);
@@ -43,7 +44,7 @@ const extractTarball = function (tarPath) {
43
44
  libvips.mkdirSync(versionedVendorPath);
44
45
  stream.pipeline(
45
46
  fs.createReadStream(tarPath),
46
- new zlib.BrotliDecompress(),
47
+ supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
47
48
  tarFs.extract(versionedVendorPath),
48
49
  function (err) {
49
50
  if (err) {
@@ -58,6 +59,7 @@ const extractTarball = function (tarPath) {
58
59
 
59
60
  try {
60
61
  const useGlobalLibvips = libvips.useGlobalLibvips();
62
+
61
63
  if (useGlobalLibvips) {
62
64
  const globalLibvipsVersion = libvips.globalLibvipsVersion();
63
65
  npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
@@ -80,8 +82,16 @@ try {
80
82
  throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
81
83
  }
82
84
  }
85
+
86
+ const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
87
+ if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
88
+ throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
89
+ }
90
+
91
+ const extension = supportsBrotli ? 'br' : 'gz';
92
+
83
93
  // Download to per-process temporary file
84
- const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
94
+ const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
85
95
  const tarPathCache = path.join(libvips.cachePath(), tarFilename);
86
96
  if (fs.existsSync(tarPathCache)) {
87
97
  npmLog.info('sharp', `Using cached ${tarPathCache}`);
package/lib/channel.js CHANGED
@@ -85,7 +85,7 @@ function extractChannel (channel) {
85
85
  * - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
86
86
  * - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
87
87
  *
88
- * Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
88
+ * Buffers may be any of the image formats supported by sharp.
89
89
  * For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
90
90
  *
91
91
  * @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
package/lib/composite.js CHANGED
@@ -105,8 +105,9 @@ function composite (images) {
105
105
  input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
106
106
  blend: 'over',
107
107
  tile: false,
108
- left: -1,
109
- top: -1,
108
+ left: 0,
109
+ top: 0,
110
+ hasOffset: false,
110
111
  gravity: 0,
111
112
  premultiplied: false
112
113
  };
@@ -125,21 +126,23 @@ function composite (images) {
125
126
  }
126
127
  }
127
128
  if (is.defined(image.left)) {
128
- if (is.integer(image.left) && image.left >= 0) {
129
+ if (is.integer(image.left)) {
129
130
  composite.left = image.left;
130
131
  } else {
131
- throw is.invalidParameterError('left', 'positive integer', image.left);
132
+ throw is.invalidParameterError('left', 'integer', image.left);
132
133
  }
133
134
  }
134
135
  if (is.defined(image.top)) {
135
- if (is.integer(image.top) && image.top >= 0) {
136
+ if (is.integer(image.top)) {
136
137
  composite.top = image.top;
137
138
  } else {
138
- throw is.invalidParameterError('top', 'positive integer', image.top);
139
+ throw is.invalidParameterError('top', 'integer', image.top);
139
140
  }
140
141
  }
141
- if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) {
142
+ if (is.defined(image.top) !== is.defined(image.left)) {
142
143
  throw new Error('Expected both left and top to be set');
144
+ } else {
145
+ composite.hasOffset = is.integer(image.top) && is.integer(image.left);
143
146
  }
144
147
  if (is.defined(image.gravity)) {
145
148
  if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
@@ -40,7 +40,7 @@ const debuglog = util.debuglog('sharp');
40
40
  /**
41
41
  * Constructor factory to create an instance of `sharp`, to which further methods are chained.
42
42
  *
43
- * JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
43
+ * JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
44
44
  * When using Stream based output, derived attributes are available from the `info` event.
45
45
  *
46
46
  * Non-critical problems encountered during processing are emitted as `warning` events.
@@ -91,9 +91,9 @@ const debuglog = util.debuglog('sharp');
91
91
  * await sharp('in.gif', { animated: true }).toFile('out.webp');
92
92
  *
93
93
  * @param {(Buffer|string)} [input] - if present, can be
94
- * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
95
- * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
96
- * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
94
+ * a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
95
+ * a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
96
+ * JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
97
97
  * @param {Object} [options] - if present, is an Object with optional attributes.
98
98
  * @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
99
99
  * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
@@ -102,9 +102,9 @@ const debuglog = util.debuglog('sharp');
102
102
  * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
103
103
  * @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
104
104
  * This can reduce memory usage and might improve performance on some systems.
105
- * @param {number} [options.density=72] - number representing the DPI for vector images.
106
- * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
107
- * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
105
+ * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
106
+ * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
107
+ * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
108
108
  * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
109
109
  * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
110
110
  * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
@@ -155,6 +155,13 @@ const Sharp = function (input, options) {
155
155
  extendRight: 0,
156
156
  extendBackground: [0, 0, 0, 255],
157
157
  withoutEnlargement: false,
158
+ affineMatrix: [],
159
+ affineBackground: [0, 0, 0, 255],
160
+ affineIdx: 0,
161
+ affineIdy: 0,
162
+ affineOdx: 0,
163
+ affineOdy: 0,
164
+ affineInterpolator: this.constructor.interpolators.bilinear,
158
165
  kernel: 'lanczos3',
159
166
  fastShrinkOnLoad: true,
160
167
  // operations
@@ -226,9 +233,10 @@ const Sharp = function (input, options) {
226
233
  tiffTileWidth: 256,
227
234
  tiffXres: 1.0,
228
235
  tiffYres: 1.0,
229
- heifQuality: 80,
236
+ heifQuality: 50,
230
237
  heifLossless: false,
231
- heifCompression: 'hevc',
238
+ heifCompression: 'av1',
239
+ heifSpeed: 5,
232
240
  tileSize: 256,
233
241
  tileOverlap: 0,
234
242
  tileContainer: 'fs',
@@ -238,6 +246,7 @@ const Sharp = function (input, options) {
238
246
  tileAngle: 0,
239
247
  tileSkipBlanks: -1,
240
248
  tileBackground: [255, 255, 255, 255],
249
+ tileCentre: false,
241
250
  linearA: 1,
242
251
  linearB: 0,
243
252
  // Function to notify of libvips warnings
package/lib/input.js CHANGED
@@ -9,9 +9,9 @@ const sharp = require('../build/Release/sharp.node');
9
9
  * @private
10
10
  */
11
11
  function _inputOptionsFromObject (obj) {
12
- const { raw, density, limitInputPixels, sequentialRead, failOnError } = obj;
13
- return [raw, density, limitInputPixels, sequentialRead, failOnError].some(is.defined)
14
- ? { raw, density, limitInputPixels, sequentialRead, failOnError }
12
+ const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
13
+ return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
14
+ ? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
15
15
  : undefined;
16
16
  }
17
17
 
@@ -57,10 +57,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
57
57
  }
58
58
  // Density
59
59
  if (is.defined(inputOptions.density)) {
60
- if (is.inRange(inputOptions.density, 1, 2400)) {
60
+ if (is.inRange(inputOptions.density, 1, 100000)) {
61
61
  inputDescriptor.density = inputOptions.density;
62
62
  } else {
63
- throw is.invalidParameterError('density', 'number between 1 and 2400', inputOptions.density);
63
+ throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
64
64
  }
65
65
  }
66
66
  // limitInputPixels
package/lib/operation.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { flatten: flattenArray } = require('array-flatten');
3
4
  const color = require('color');
4
5
  const is = require('./is');
5
6
 
@@ -82,6 +83,103 @@ function flop (flop) {
82
83
  return this;
83
84
  }
84
85
 
86
+ /**
87
+ * Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
88
+ *
89
+ * You must provide an array of length 4 or a 2x2 affine transformation matrix.
90
+ * By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
91
+ * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`.
92
+ *
93
+ * In the case of a 2x2 matrix, the transform is:
94
+ * - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
95
+ * - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
96
+ *
97
+ * where:
98
+ * - x and y are the coordinates in input image.
99
+ * - X and Y are the coordinates in output image.
100
+ * - (0,0) is the upper left corner.
101
+ *
102
+ * @since 0.27.0
103
+ *
104
+ * @example
105
+ * const pipeline = sharp()
106
+ * .affine([[1, 0.3], [0.1, 0.7]], {
107
+ * background: 'white',
108
+ * interpolate: sharp.interpolators.nohalo
109
+ * })
110
+ * .toBuffer((err, outputBuffer, info) => {
111
+ * // outputBuffer contains the transformed image
112
+ * // info.width and info.height contain the new dimensions
113
+ * });
114
+ *
115
+ * inputStream
116
+ * .pipe(pipeline);
117
+ *
118
+ * @param {Array<Array<number>>|Array<number>} matrix - affine transformation matrix
119
+ * @param {Object} [options] - if present, is an Object with optional attributes.
120
+ * @param {String|Object} [options.background="#000000"] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
121
+ * @param {Number} [options.idx=0] - input horizontal offset
122
+ * @param {Number} [options.idy=0] - input vertical offset
123
+ * @param {Number} [options.odx=0] - output horizontal offset
124
+ * @param {Number} [options.ody=0] - output vertical offset
125
+ * @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator
126
+ * @returns {Sharp}
127
+ * @throws {Error} Invalid parameters
128
+ */
129
+ function affine (matrix, options) {
130
+ const flatMatrix = flattenArray(matrix);
131
+ if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
132
+ this.options.affineMatrix = flatMatrix;
133
+ } else {
134
+ throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
135
+ }
136
+
137
+ if (is.defined(options)) {
138
+ if (is.object(options)) {
139
+ this._setBackgroundColourOption('affineBackground', options.background);
140
+ if (is.defined(options.idx)) {
141
+ if (is.number(options.idx)) {
142
+ this.options.affineIdx = options.idx;
143
+ } else {
144
+ throw is.invalidParameterError('options.idx', 'number', options.idx);
145
+ }
146
+ }
147
+ if (is.defined(options.idy)) {
148
+ if (is.number(options.idy)) {
149
+ this.options.affineIdy = options.idy;
150
+ } else {
151
+ throw is.invalidParameterError('options.idy', 'number', options.idy);
152
+ }
153
+ }
154
+ if (is.defined(options.odx)) {
155
+ if (is.number(options.odx)) {
156
+ this.options.affineOdx = options.odx;
157
+ } else {
158
+ throw is.invalidParameterError('options.odx', 'number', options.odx);
159
+ }
160
+ }
161
+ if (is.defined(options.ody)) {
162
+ if (is.number(options.ody)) {
163
+ this.options.affineOdy = options.ody;
164
+ } else {
165
+ throw is.invalidParameterError('options.ody', 'number', options.ody);
166
+ }
167
+ }
168
+ if (is.defined(options.interpolator)) {
169
+ if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
170
+ this.options.affineInterpolator = options.interpolator;
171
+ } else {
172
+ throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
173
+ }
174
+ }
175
+ } else {
176
+ throw is.invalidParameterError('options', 'object', options);
177
+ }
178
+ }
179
+
180
+ return this;
181
+ }
182
+
85
183
  /**
86
184
  * Sharpen the image.
87
185
  * When used without parameters, performs a fast, mild sharpen of the output image.
@@ -482,6 +580,7 @@ module.exports = function (Sharp) {
482
580
  rotate,
483
581
  flip,
484
582
  flop,
583
+ affine,
485
584
  sharpen,
486
585
  median,
487
586
  blur,
package/lib/output.js CHANGED
@@ -6,6 +6,7 @@ const sharp = require('../build/Release/sharp.node');
6
6
  const formats = new Map([
7
7
  ['heic', 'heif'],
8
8
  ['heif', 'heif'],
9
+ ['avif', 'avif'],
9
10
  ['jpeg', 'jpeg'],
10
11
  ['jpg', 'jpeg'],
11
12
  ['png', 'png'],
@@ -19,7 +20,7 @@ const formats = new Map([
19
20
  * Write output image data to a file.
20
21
  *
21
22
  * If an explicit output format is not selected, it will be inferred from the extension,
22
- * with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
23
+ * with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
23
24
  * Note that raw pixel data is only supported for buffer output.
24
25
  *
25
26
  * By default all metadata will be removed, which includes EXIF-based orientation.
@@ -71,7 +72,7 @@ function toFile (fileOut, callback) {
71
72
 
72
73
  /**
73
74
  * Write output to a Buffer.
74
- * JPEG, PNG, WebP, TIFF and RAW output are supported.
75
+ * JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
75
76
  *
76
77
  * If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
77
78
  *
@@ -557,28 +558,41 @@ function tiff (options) {
557
558
  }
558
559
 
559
560
  /**
560
- * Use these HEIF options for output image.
561
+ * Use these AVIF options for output image.
561
562
  *
562
- * Support for HEIF (HEIC/AVIF) is experimental.
563
- * Do not use this in production systems.
563
+ * Whilst it is possible to create AVIF images smaller than 16x16 pixels,
564
+ * most web browsers do not display these properly.
564
565
  *
565
- * Requires a custom, globally-installed libvips compiled with support for libheif.
566
+ * @since 0.27.0
566
567
  *
567
- * Most versions of libheif support only the patent-encumbered HEVC compression format.
568
+ * @param {Object} [options] - output options
569
+ * @param {number} [options.quality=50] - quality, integer 1-100
570
+ * @param {boolean} [options.lossless=false] - use lossless compression
571
+ * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
572
+ * @returns {Sharp}
573
+ * @throws {Error} Invalid options
574
+ */
575
+ function avif (options) {
576
+ return this.heif({ ...options, compression: 'av1' });
577
+ }
578
+
579
+ /**
580
+ * Use these HEIF options for output image.
581
+ *
582
+ * Support for patent-encumbered HEIC images requires the use of a
583
+ * globally-installed libvips compiled with support for libheif, libde265 and x265.
568
584
  *
569
585
  * @since 0.23.0
570
586
  *
571
587
  * @param {Object} [options] - output options
572
- * @param {number} [options.quality=80] - quality, integer 1-100
573
- * @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
588
+ * @param {number} [options.quality=50] - quality, integer 1-100
589
+ * @param {boolean} [options.compression='av1'] - compression format: av1, hevc
574
590
  * @param {boolean} [options.lossless=false] - use lossless compression
591
+ * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
575
592
  * @returns {Sharp}
576
593
  * @throws {Error} Invalid options
577
594
  */
578
595
  function heif (options) {
579
- if (!this.constructor.format.heif.output.buffer) {
580
- throw new Error('The heif operation requires libvips to have been installed with support for libheif');
581
- }
582
596
  if (is.object(options)) {
583
597
  if (is.defined(options.quality)) {
584
598
  if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
@@ -595,10 +609,17 @@ function heif (options) {
595
609
  }
596
610
  }
597
611
  if (is.defined(options.compression)) {
598
- if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) {
612
+ if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
599
613
  this.options.heifCompression = options.compression;
600
614
  } else {
601
- throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression);
615
+ throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
616
+ }
617
+ }
618
+ if (is.defined(options.speed)) {
619
+ if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
620
+ this.options.heifSpeed = options.speed;
621
+ } else {
622
+ throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
602
623
  }
603
624
  }
604
625
  }
@@ -658,6 +679,8 @@ function raw () {
658
679
  * @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
659
680
  * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
660
681
  * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
682
+ * @param {boolean} [options.centre=false] centre image in tile.
683
+ * @param {boolean} [options.center=false] alternative spelling of centre.
661
684
  * @returns {Sharp}
662
685
  * @throws {Error} Invalid parameters
663
686
  */
@@ -726,6 +749,11 @@ function tile (options) {
726
749
  } else if (is.defined(options.layout) && options.layout === 'google') {
727
750
  this.options.tileSkipBlanks = 5;
728
751
  }
752
+ // Center image in tile
753
+ const centre = is.bool(options.center) ? options.center : options.centre;
754
+ if (is.defined(centre)) {
755
+ this._setBooleanOption('tileCentre', centre);
756
+ }
729
757
  }
730
758
  // Format
731
759
  if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
@@ -884,6 +912,7 @@ module.exports = function (Sharp) {
884
912
  png,
885
913
  webp,
886
914
  tiff,
915
+ avif,
887
916
  heif,
888
917
  gif,
889
918
  raw,
package/lib/resize.js CHANGED
@@ -386,7 +386,8 @@ function extract (options) {
386
386
  * Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
387
387
  * Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
388
388
  *
389
- * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
389
+ * The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
390
+ * will contain `trimOffsetLeft` and `trimOffsetTop` properties.
390
391
  *
391
392
  * @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
392
393
  * @returns {Sharp}
package/lib/utility.js CHANGED
@@ -13,6 +13,26 @@ const sharp = require('../build/Release/sharp.node');
13
13
  */
14
14
  const format = sharp.format();
15
15
 
16
+ /**
17
+ * An Object containing the available interpolators and their proper values
18
+ * @readonly
19
+ * @enum {string}
20
+ */
21
+ const interpolators = {
22
+ /** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
23
+ nearest: 'nearest',
24
+ /** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
25
+ bilinear: 'bilinear',
26
+ /** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
27
+ bicubic: 'bicubic',
28
+ /** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
29
+ locallyBoundedBicubic: 'lbb',
30
+ /** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
31
+ nohalo: 'nohalo',
32
+ /** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
33
+ vertexSplitQuadraticBasisSpline: 'vsqbs'
34
+ };
35
+
16
36
  /**
17
37
  * An Object containing the version numbers of libvips and its dependencies.
18
38
  * @member
@@ -146,6 +166,7 @@ module.exports = function (Sharp) {
146
166
  Sharp[f.name] = f;
147
167
  });
148
168
  Sharp.format = format;
169
+ Sharp.interpolators = interpolators;
149
170
  Sharp.versions = versions;
150
171
  Sharp.queue = queue;
151
172
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
- "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
4
- "version": "0.26.0",
3
+ "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
4
+ "version": "0.27.0",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -69,12 +69,15 @@
69
69
  "Edward Silverton <e.silverton@gmail.com>",
70
70
  "Roman Malieiev <aromaleev@gmail.com>",
71
71
  "Tomas Szabo <tomas.szabo@deftomat.com>",
72
- "Robert O'Rourke <robert@o-rourke.org>"
72
+ "Robert O'Rourke <robert@o-rourke.org>",
73
+ "Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
74
+ "Christian Flintrup <chr@gigahost.dk>",
75
+ "Manan Jadhav <manan@motionden.com>"
73
76
  ],
74
77
  "scripts": {
75
78
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
76
79
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
77
- "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
80
+ "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
78
81
  "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
79
82
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
80
83
  "test-coverage": "./test/coverage/report.sh",
@@ -87,7 +90,6 @@
87
90
  "files": [
88
91
  "binding.gyp",
89
92
  "install/**",
90
- "!install/prebuild-ci.js",
91
93
  "lib/**",
92
94
  "src/**"
93
95
  ],
@@ -99,6 +101,7 @@
99
101
  "jpeg",
100
102
  "png",
101
103
  "webp",
104
+ "avif",
102
105
  "tiff",
103
106
  "gif",
104
107
  "svg",
@@ -112,39 +115,40 @@
112
115
  "vips"
113
116
  ],
114
117
  "dependencies": {
115
- "color": "^3.1.2",
118
+ "array-flatten": "^3.0.0",
119
+ "color": "^3.1.3",
116
120
  "detect-libc": "^1.0.3",
117
- "node-addon-api": "^3.0.0",
121
+ "node-addon-api": "^3.1.0",
118
122
  "npmlog": "^4.1.2",
119
- "prebuild-install": "^5.3.5",
120
- "semver": "^7.3.2",
123
+ "prebuild-install": "^6.0.0",
124
+ "semver": "^7.3.4",
121
125
  "simple-get": "^4.0.0",
122
- "tar-fs": "^2.1.0",
126
+ "tar-fs": "^2.1.1",
123
127
  "tunnel-agent": "^0.6.0"
124
128
  },
125
129
  "devDependencies": {
126
130
  "async": "^3.2.0",
127
- "cc": "^2.0.1",
131
+ "cc": "^3.0.1",
128
132
  "decompress-zip": "^0.3.2",
129
- "documentation": "^13.0.2",
133
+ "documentation": "^13.1.0",
130
134
  "exif-reader": "^1.0.3",
131
135
  "icc": "^2.0.0",
132
136
  "license-checker": "^25.0.1",
133
- "mocha": "^8.1.1",
137
+ "mocha": "^8.2.1",
134
138
  "mock-fs": "^4.13.0",
135
139
  "nyc": "^15.1.0",
136
140
  "prebuild": "^10.0.1",
137
141
  "rimraf": "^3.0.2",
138
- "semistandard": "^14.2.3"
142
+ "semistandard": "^16.0.0"
139
143
  },
140
144
  "license": "Apache-2.0",
141
145
  "config": {
142
- "libvips": "8.10.0",
146
+ "libvips": "8.10.5",
143
147
  "runtime": "napi",
144
148
  "target": 3
145
149
  },
146
150
  "engines": {
147
- "node": ">=10.16.0"
151
+ "node": ">=10"
148
152
  },
149
153
  "funding": {
150
154
  "url": "https://opencollective.com/libvips"
package/src/common.cc CHANGED
@@ -54,13 +54,13 @@ namespace sharp {
54
54
  bool AttrAsBool(Napi::Object obj, std::string attr) {
55
55
  return obj.Get(attr).As<Napi::Boolean>().Value();
56
56
  }
57
- std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
58
- Napi::Array background = obj.Get(attr).As<Napi::Array>();
59
- std::vector<double> rgba(background.Length());
60
- for (unsigned int i = 0; i < background.Length(); i++) {
61
- rgba[i] = AttrAsDouble(background, i);
57
+ std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
58
+ Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
59
+ std::vector<double> vectorOfDouble(napiArray.Length());
60
+ for (unsigned int i = 0; i < napiArray.Length(); i++) {
61
+ vectorOfDouble[i] = AttrAsDouble(napiArray, i);
62
62
  }
63
- return rgba;
63
+ return vectorOfDouble;
64
64
  }
65
65
  std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
66
66
  Napi::Array array = obj.Get(attr).As<Napi::Array>();
@@ -109,7 +109,7 @@ namespace sharp {
109
109
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
110
110
  descriptor->createWidth = AttrAsUint32(input, "createWidth");
111
111
  descriptor->createHeight = AttrAsUint32(input, "createHeight");
112
- descriptor->createBackground = AttrAsRgba(input, "createBackground");
112
+ descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
113
113
  }
114
114
  // Limit input images to a given number of pixels, where pixels = width * height
115
115
  descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
@@ -180,6 +180,7 @@ namespace sharp {
180
180
  case ImageType::OPENSLIDE: id = "openslide"; break;
181
181
  case ImageType::PPM: id = "ppm"; break;
182
182
  case ImageType::FITS: id = "fits"; break;
183
+ case ImageType::EXR: id = "exr"; break;
183
184
  case ImageType::VIPS: id = "vips"; break;
184
185
  case ImageType::RAW: id = "raw"; break;
185
186
  case ImageType::UNKNOWN: id = "unknown"; break;
@@ -210,6 +211,7 @@ namespace sharp {
210
211
  { "openslideload", ImageType::OPENSLIDE },
211
212
  { "ppmload", ImageType::PPM },
212
213
  { "fitsload", ImageType::FITS },
214
+ { "openexrload", ImageType::EXR },
213
215
  { "vipsload", ImageType::VIPS },
214
216
  { "rawload", ImageType::RAW }
215
217
  };
@@ -485,8 +487,8 @@ namespace sharp {
485
487
  Check the proposed format supports the current dimensions.
486
488
  */
487
489
  void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
488
- const int height = image.get_typeof("pageHeight") == G_TYPE_INT
489
- ? image.get_int("pageHeight")
490
+ const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
491
+ ? image.get_int(VIPS_META_PAGE_HEIGHT)
490
492
  : image.height();
491
493
  if (imageType == ImageType::JPEG) {
492
494
  if (image.width() > 65535 || height > 65535) {
@@ -656,26 +658,18 @@ namespace sharp {
656
658
  int top = 0;
657
659
 
658
660
  // assign only if valid
659
- if (x >= 0 && x < (inWidth - outWidth)) {
661
+ if (x < (inWidth - outWidth)) {
660
662
  left = x;
661
663
  } else if (x >= (inWidth - outWidth)) {
662
664
  left = inWidth - outWidth;
663
665
  }
664
666
 
665
- if (y >= 0 && y < (inHeight - outHeight)) {
667
+ if (y < (inHeight - outHeight)) {
666
668
  top = y;
667
669
  } else if (y >= (inHeight - outHeight)) {
668
670
  top = inHeight - outHeight;
669
671
  }
670
672
 
671
- // the resulting left and top could have been outside the image after calculation from bottom/right edges
672
- if (left < 0) {
673
- left = 0;
674
- }
675
- if (top < 0) {
676
- top = 0;
677
- }
678
-
679
673
  return std::make_tuple(left, top);
680
674
  }
681
675
 
package/src/common.h CHANGED
@@ -24,8 +24,10 @@
24
24
 
25
25
  // Verify platform and compiler compatibility
26
26
 
27
- #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10))
28
- #error "libvips version 8.10.0+ is required - please see https://sharp.pixelplumbing.com/install"
27
+ #if (VIPS_MAJOR_VERSION < 8) || \
28
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
29
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
30
+ #error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
29
31
  #endif
30
32
 
31
33
  #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -92,7 +94,7 @@ namespace sharp {
92
94
  double AttrAsDouble(Napi::Object obj, std::string attr);
93
95
  double AttrAsDouble(Napi::Object obj, unsigned int const attr);
94
96
  bool AttrAsBool(Napi::Object obj, std::string attr);
95
- std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
97
+ std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
96
98
  std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
97
99
 
98
100
  // Create an InputDescriptor instance from a Napi::Object describing an input image
@@ -111,6 +113,7 @@ namespace sharp {
111
113
  OPENSLIDE,
112
114
  PPM,
113
115
  FITS,
116
+ EXR,
114
117
  VIPS,
115
118
  RAW,
116
119
  UNKNOWN,
package/src/pipeline.cc CHANGED
@@ -485,6 +485,18 @@ class PipelineWorker : public Napi::AsyncWorker {
485
485
  baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
486
486
  }
487
487
 
488
+ // Affine transform
489
+ if (baton->affineMatrix.size() > 0) {
490
+ std::vector<double> background;
491
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
492
+ image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
493
+ ->set("idx", baton->affineIdx)
494
+ ->set("idy", baton->affineIdy)
495
+ ->set("odx", baton->affineOdx)
496
+ ->set("ody", baton->affineOdy)
497
+ ->set("interpolate", baton->affineInterpolator));
498
+ }
499
+
488
500
  // Extend edges
489
501
  if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
490
502
  std::vector<double> background;
@@ -558,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
558
570
  int left;
559
571
  int top;
560
572
  compositeImage = compositeImage.replicate(across, down);
561
- if (composite->left >= 0 && composite->top >= 0) {
573
+ if (composite->hasOffset) {
562
574
  std::tie(left, top) = sharp::CalculateCrop(
563
575
  compositeImage.width(), compositeImage.height(), image.width(), image.height(),
564
576
  composite->left, composite->top);
@@ -580,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
580
592
  // Calculate position
581
593
  int left;
582
594
  int top;
583
- if (composite->left >= 0 && composite->top >= 0) {
595
+ if (composite->hasOffset) {
584
596
  // Composite image at given offsets
585
597
  std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
586
598
  compositeImage.width(), compositeImage.height(), composite->left, composite->top);
@@ -782,6 +794,8 @@ class PipelineWorker : public Napi::AsyncWorker {
782
794
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
783
795
  VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
784
796
  ->set("strip", !baton->withMetadata)
797
+ ->set("optimize_gif_frames", TRUE)
798
+ ->set("optimize_gif_transparency", TRUE)
785
799
  ->set("format", "gif")));
786
800
  baton->bufferOut = static_cast<char*>(area->data);
787
801
  baton->bufferOutLength = area->length;
@@ -823,6 +837,7 @@ class PipelineWorker : public Napi::AsyncWorker {
823
837
  ->set("strip", !baton->withMetadata)
824
838
  ->set("compression", baton->heifCompression)
825
839
  ->set("Q", baton->heifQuality)
840
+ ->set("speed", baton->heifSpeed)
826
841
  ->set("lossless", baton->heifLossless)));
827
842
  baton->bufferOut = static_cast<char*>(area->data);
828
843
  baton->bufferOutLength = area->length;
@@ -871,7 +886,7 @@ class PipelineWorker : public Napi::AsyncWorker {
871
886
  bool const isV = sharp::IsV(baton->fileOut);
872
887
  bool const mightMatchInput = baton->formatOut == "input";
873
888
  bool const willMatchInput = mightMatchInput &&
874
- !(isJpeg || isPng || isWebp || isGif || isTiff || isDz || isDzZip || isV);
889
+ !(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
875
890
 
876
891
  if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
877
892
  (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
@@ -925,6 +940,8 @@ class PipelineWorker : public Napi::AsyncWorker {
925
940
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
926
941
  image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
927
942
  ->set("strip", !baton->withMetadata)
943
+ ->set("optimize_gif_frames", TRUE)
944
+ ->set("optimize_gif_transparency", TRUE)
928
945
  ->set("format", "gif"));
929
946
  baton->formatOut = "gif";
930
947
  } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@@ -950,13 +967,11 @@ class PipelineWorker : public Napi::AsyncWorker {
950
967
  } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
951
968
  (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
952
969
  // Write HEIF to file
953
- if (sharp::IsAvif(baton->fileOut)) {
954
- baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
955
- }
956
970
  image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
957
971
  ->set("strip", !baton->withMetadata)
958
972
  ->set("Q", baton->heifQuality)
959
973
  ->set("compression", baton->heifCompression)
974
+ ->set("speed", baton->heifSpeed)
960
975
  ->set("lossless", baton->heifLossless));
961
976
  baton->formatOut = "heif";
962
977
  } else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -1010,6 +1025,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1010
1025
  ->set("suffix", const_cast<char*>(suffix.data()))
1011
1026
  ->set("angle", CalculateAngleRotation(baton->tileAngle))
1012
1027
  ->set("background", baton->tileBackground)
1028
+ ->set("centre", baton->tileCentre)
1013
1029
  ->set("skip_blanks", baton->tileSkipBlanks);
1014
1030
  // libvips chooses a default depth based on layout. Instead of replicating that logic here by
1015
1031
  // not passing anything - libvips will handle choice
@@ -1237,6 +1253,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1237
1253
  composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
1238
1254
  composite->left = sharp::AttrAsInt32(compositeObject, "left");
1239
1255
  composite->top = sharp::AttrAsInt32(compositeObject, "top");
1256
+ composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
1240
1257
  composite->tile = sharp::AttrAsBool(compositeObject, "tile");
1241
1258
  composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
1242
1259
  baton->composite.push_back(composite);
@@ -1244,7 +1261,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1244
1261
  // Resize options
1245
1262
  baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
1246
1263
  baton->position = sharp::AttrAsInt32(options, "position");
1247
- baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
1264
+ baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
1248
1265
  baton->kernel = sharp::AttrAsStr(options, "kernel");
1249
1266
  baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
1250
1267
  // Join Channel Options
@@ -1257,7 +1274,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1257
1274
  }
1258
1275
  // Operators
1259
1276
  baton->flatten = sharp::AttrAsBool(options, "flatten");
1260
- baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
1277
+ baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
1261
1278
  baton->negate = sharp::AttrAsBool(options, "negate");
1262
1279
  baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
1263
1280
  baton->brightness = sharp::AttrAsDouble(options, "brightness");
@@ -1279,7 +1296,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1279
1296
  baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
1280
1297
  baton->angle = sharp::AttrAsInt32(options, "angle");
1281
1298
  baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
1282
- baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
1299
+ baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
1283
1300
  baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
1284
1301
  baton->flip = sharp::AttrAsBool(options, "flip");
1285
1302
  baton->flop = sharp::AttrAsBool(options, "flop");
@@ -1287,8 +1304,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1287
1304
  baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
1288
1305
  baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
1289
1306
  baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
1290
- baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
1307
+ baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground");
1291
1308
  baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
1309
+ baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix");
1310
+ baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground");
1311
+ baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx");
1312
+ baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
1313
+ baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
1314
+ baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
1315
+ baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
1292
1316
 
1293
1317
  baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
1294
1318
  baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
@@ -1371,6 +1395,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1371
1395
  baton->heifCompression = static_cast<VipsForeignHeifCompression>(
1372
1396
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
1373
1397
  sharp::AttrAsStr(options, "heifCompression").data()));
1398
+ baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
1374
1399
 
1375
1400
  // Animated output
1376
1401
  if (sharp::HasAttr(options, "pageHeight")) {
@@ -1387,7 +1412,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1387
1412
  baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
1388
1413
  baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
1389
1414
  baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
1390
- baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
1415
+ baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
1391
1416
  baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
1392
1417
  baton->tileContainer = static_cast<VipsForeignDzContainer>(
1393
1418
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
@@ -1399,6 +1424,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1399
1424
  baton->tileDepth = static_cast<VipsForeignDzDepth>(
1400
1425
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
1401
1426
  sharp::AttrAsStr(options, "tileDepth").data()));
1427
+ baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1402
1428
 
1403
1429
  // Force random access for certain operations
1404
1430
  if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
package/src/pipeline.h CHANGED
@@ -40,6 +40,7 @@ struct Composite {
40
40
  int gravity;
41
41
  int left;
42
42
  int top;
43
+ bool hasOffset;
43
44
  bool tile;
44
45
  bool premultiplied;
45
46
 
@@ -47,8 +48,9 @@ struct Composite {
47
48
  input(nullptr),
48
49
  mode(VIPS_BLEND_MODE_OVER),
49
50
  gravity(0),
50
- left(-1),
51
- top(-1),
51
+ left(0),
52
+ top(0),
53
+ hasOffset(false),
52
54
  tile(false),
53
55
  premultiplied(false) {}
54
56
  };
@@ -79,6 +81,7 @@ struct PipelineBaton {
79
81
  int cropOffsetLeft;
80
82
  int cropOffsetTop;
81
83
  bool premultiplied;
84
+ bool tileCentre;
82
85
  std::string kernel;
83
86
  bool fastShrinkOnLoad;
84
87
  double tintA;
@@ -118,6 +121,13 @@ struct PipelineBaton {
118
121
  int extendRight;
119
122
  std::vector<double> extendBackground;
120
123
  bool withoutEnlargement;
124
+ std::vector<double> affineMatrix;
125
+ std::vector<double> affineBackground;
126
+ double affineIdx;
127
+ double affineIdy;
128
+ double affineOdx;
129
+ double affineOdy;
130
+ vips::VInterpolate affineInterpolator;
121
131
  int jpegQuality;
122
132
  bool jpegProgressive;
123
133
  std::string jpegChromaSubsampling;
@@ -151,6 +161,7 @@ struct PipelineBaton {
151
161
  double tiffYres;
152
162
  int heifQuality;
153
163
  VipsForeignHeifCompression heifCompression;
164
+ int heifSpeed;
154
165
  bool heifLossless;
155
166
  std::string err;
156
167
  bool withMetadata;
@@ -230,6 +241,13 @@ struct PipelineBaton {
230
241
  extendRight(0),
231
242
  extendBackground{ 0.0, 0.0, 0.0, 255.0 },
232
243
  withoutEnlargement(false),
244
+ affineMatrix{ 1.0, 0.0, 0.0, 1.0 },
245
+ affineBackground{ 0.0, 0.0, 0.0, 255.0 },
246
+ affineIdx(0),
247
+ affineIdy(0),
248
+ affineOdx(0),
249
+ affineOdy(0),
250
+ affineInterpolator(vips::VInterpolate::new_from_name("bicubic")),
233
251
  jpegQuality(80),
234
252
  jpegProgressive(false),
235
253
  jpegChromaSubsampling("4:2:0"),
@@ -261,8 +279,9 @@ struct PipelineBaton {
261
279
  tiffTileWidth(256),
262
280
  tiffXres(1.0),
263
281
  tiffYres(1.0),
264
- heifQuality(80),
265
- heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
282
+ heifQuality(50),
283
+ heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
284
+ heifSpeed(5),
266
285
  heifLossless(false),
267
286
  withMetadata(false),
268
287
  withMetadataOrientation(-1),
package/src/sharp.cc CHANGED
@@ -22,6 +22,8 @@
22
22
  #include "stats.h"
23
23
 
24
24
  static void* sharp_vips_init(void*) {
25
+ g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
26
+ g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
25
27
  vips_init("sharp");
26
28
  return nullptr;
27
29
  }
package/src/stats.cc CHANGED
@@ -80,12 +80,14 @@ class StatsWorker : public Napi::AsyncWorker {
80
80
  // Estimate entropy via histogram of greyscale value frequency
81
81
  baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
82
82
  // Estimate sharpness via standard deviation of greyscale laplacian
83
- VImage laplacian = VImage::new_matrixv(3, 3,
84
- 0.0, 1.0, 0.0,
85
- 1.0, -4.0, 1.0,
86
- 0.0, 1.0, 0.0);
87
- laplacian.set("scale", 9.0);
88
- baton->sharpness = greyscale.conv(laplacian).deviate();
83
+ if (image.width() > 1 || image.height() > 1) {
84
+ VImage laplacian = VImage::new_matrixv(3, 3,
85
+ 0.0, 1.0, 0.0,
86
+ 1.0, -4.0, 1.0,
87
+ 0.0, 1.0, 0.0);
88
+ laplacian.set("scale", 9.0);
89
+ baton->sharpness = greyscale.conv(laplacian).deviate();
90
+ }
89
91
  // Most dominant sRGB colour via 4096-bin 3D histogram
90
92
  vips::VImage hist = sharp::RemoveAlpha(image)
91
93
  .colourspace(VIPS_INTERPRETATION_sRGB)