sharp 0.25.4 → 0.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/binding.gyp CHANGED
@@ -1,4 +1,8 @@
1
1
  {
2
+ 'variables': {
3
+ 'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
4
+ 'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
5
+ },
2
6
  'targets': [{
3
7
  'target_name': 'libvips-cpp',
4
8
  'conditions': [
@@ -16,15 +20,18 @@
16
20
  'src/libvips/cplusplus/VImage.cpp'
17
21
  ],
18
22
  'include_dirs': [
19
- 'vendor/include',
20
- 'vendor/include/glib-2.0',
21
- 'vendor/lib/glib-2.0/include'
22
- ],
23
- 'libraries': [
24
- '../vendor/lib/libvips.lib',
25
- '../vendor/lib/libglib-2.0.lib',
26
- '../vendor/lib/libgobject-2.0.lib'
23
+ '<(sharp_vendor_dir)/include',
24
+ '<(sharp_vendor_dir)/include/glib-2.0',
25
+ '<(sharp_vendor_dir)/lib/glib-2.0/include'
27
26
  ],
27
+ 'link_settings': {
28
+ 'library_dirs': ['<(sharp_vendor_dir)/lib'],
29
+ 'libraries': [
30
+ 'libvips.lib',
31
+ 'libglib-2.0.lib',
32
+ 'libgobject-2.0.lib'
33
+ ],
34
+ },
28
35
  'configurations': {
29
36
  'Release': {
30
37
  'msvs_settings': {
@@ -70,8 +77,8 @@
70
77
  'runtime_link%': 'shared',
71
78
  'conditions': [
72
79
  ['OS != "win"', {
73
- 'pkg_config_path': '<!(node -e "console.log(require(\'./lib/libvips\').pkgConfigPath())")',
74
- 'use_global_libvips': '<!(node -e "console.log(Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString())")'
80
+ 'pkg_config_path': '<!(node -p "require(\'./lib/libvips\').pkgConfigPath()")',
81
+ 'use_global_libvips': '<!(node -p "Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString()")'
75
82
  }, {
76
83
  'pkg_config_path': '',
77
84
  'use_global_libvips': ''
@@ -88,7 +95,7 @@
88
95
  'src/sharp.cc'
89
96
  ],
90
97
  'include_dirs': [
91
- '<!@(node -p "require(\'node-addon-api\').include")',
98
+ '<!(node -p "require(\'node-addon-api\').include_dir")',
92
99
  ],
93
100
  'conditions': [
94
101
  ['use_global_libvips == "true"', {
@@ -110,9 +117,9 @@
110
117
  }, {
111
118
  # Use pre-built libvips stored locally within node_modules
112
119
  'include_dirs': [
113
- 'vendor/include',
114
- 'vendor/include/glib-2.0',
115
- 'vendor/lib/glib-2.0/include'
120
+ '<(sharp_vendor_dir)/include',
121
+ '<(sharp_vendor_dir)/include/glib-2.0',
122
+ '<(sharp_vendor_dir)/lib/glib-2.0/include'
116
123
  ],
117
124
  'conditions': [
118
125
  ['OS == "win"', {
@@ -120,64 +127,45 @@
120
127
  '_ALLOW_KEYWORD_MACROS',
121
128
  '_FILE_OFFSET_BITS=64'
122
129
  ],
123
- 'libraries': [
124
- '../vendor/lib/libvips.lib',
125
- '../vendor/lib/libglib-2.0.lib',
126
- '../vendor/lib/libgobject-2.0.lib'
127
- ]
130
+ 'link_settings': {
131
+ 'library_dirs': ['<(sharp_vendor_dir)/lib'],
132
+ 'libraries': [
133
+ 'libvips.lib',
134
+ 'libglib-2.0.lib',
135
+ 'libgobject-2.0.lib'
136
+ ]
137
+ }
128
138
  }],
129
139
  ['OS == "mac"', {
130
- 'libraries': [
131
- '../vendor/lib/libvips-cpp.42.dylib',
132
- '../vendor/lib/libvips.42.dylib',
133
- '../vendor/lib/libglib-2.0.0.dylib',
134
- '../vendor/lib/libgobject-2.0.0.dylib',
135
- # Ensure runtime linking is relative to sharp.node
136
- '-Wl,-s -rpath \'@loader_path/../../vendor/lib\''
137
- ]
140
+ 'link_settings': {
141
+ 'library_dirs': ['<(sharp_vendor_dir)/lib'],
142
+ 'libraries': [
143
+ 'libvips-cpp.42.dylib',
144
+ 'libvips.42.dylib'
145
+ ]
146
+ },
147
+ 'xcode_settings': {
148
+ 'OTHER_LDFLAGS': [
149
+ # Ensure runtime linking is relative to sharp.node
150
+ '-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
151
+ ]
152
+ }
138
153
  }],
139
154
  ['OS == "linux"', {
140
155
  'defines': [
141
156
  '_GLIBCXX_USE_CXX11_ABI=0'
142
157
  ],
143
- 'libraries': [
144
- '../vendor/lib/libvips-cpp.so',
145
- '../vendor/lib/libvips.so',
146
- '../vendor/lib/libglib-2.0.so',
147
- '../vendor/lib/libgobject-2.0.so',
148
- # Dependencies of dependencies, included for openSUSE support
149
- '../vendor/lib/libcairo.so',
150
- '../vendor/lib/libexif.so',
151
- '../vendor/lib/libexpat.so',
152
- '../vendor/lib/libffi.so',
153
- '../vendor/lib/libfontconfig.so',
154
- '../vendor/lib/libfreetype.so',
155
- '../vendor/lib/libfribidi.so',
156
- '../vendor/lib/libgdk_pixbuf-2.0.so',
157
- '../vendor/lib/libgif.so',
158
- '../vendor/lib/libgio-2.0.so',
159
- '../vendor/lib/libgmodule-2.0.so',
160
- '../vendor/lib/libgsf-1.so',
161
- '../vendor/lib/libgthread-2.0.so',
162
- '../vendor/lib/libharfbuzz.so',
163
- '../vendor/lib/libjpeg.so',
164
- '../vendor/lib/liblcms2.so',
165
- '../vendor/lib/liborc-0.4.so',
166
- '../vendor/lib/libpango-1.0.so',
167
- '../vendor/lib/libpangocairo-1.0.so',
168
- '../vendor/lib/libpangoft2-1.0.so',
169
- '../vendor/lib/libpixman-1.so',
170
- '../vendor/lib/libpng.so',
171
- '../vendor/lib/librsvg-2.so',
172
- '../vendor/lib/libtiff.so',
173
- '../vendor/lib/libwebp.so',
174
- '../vendor/lib/libwebpdemux.so',
175
- '../vendor/lib/libwebpmux.so',
176
- '../vendor/lib/libxml2.so',
177
- '../vendor/lib/libz.so',
178
- # Ensure runtime linking is relative to sharp.node
179
- '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../vendor/lib\''
180
- ]
158
+ 'link_settings': {
159
+ 'library_dirs': ['<(sharp_vendor_dir)/lib'],
160
+ 'libraries': [
161
+ '-l:libvips-cpp.so.42',
162
+ '-l:libvips.so.42'
163
+ ],
164
+ 'ldflags': [
165
+ # Ensure runtime linking is relative to sharp.node
166
+ '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
167
+ ]
168
+ }
181
169
  }]
182
170
  ]
183
171
  }]
@@ -190,8 +178,7 @@
190
178
  ],
191
179
  'xcode_settings': {
192
180
  'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
193
- 'CLANG_CXX_LIBRARY': 'libc++',
194
- 'MACOSX_DEPLOYMENT_TARGET': '10.7',
181
+ 'MACOSX_DEPLOYMENT_TARGET': '10.9',
195
182
  'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
196
183
  'GCC_ENABLE_CPP_RTTI': 'YES',
197
184
  'OTHER_CPLUSPLUSFLAGS': [
@@ -6,6 +6,8 @@ const path = require('path');
6
6
  const libvips = require('../lib/libvips');
7
7
  const npmLog = require('npmlog');
8
8
 
9
+ const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
10
+
9
11
  const platform = process.env.npm_config_platform || process.platform;
10
12
  if (platform === 'win32') {
11
13
  const buildDir = path.join(__dirname, '..', 'build');
@@ -15,7 +17,7 @@ if (platform === 'win32') {
15
17
  libvips.mkdirSync(buildDir);
16
18
  libvips.mkdirSync(buildReleaseDir);
17
19
  } catch (err) {}
18
- const vendorLibDir = path.join(__dirname, '..', 'vendor', 'lib');
20
+ const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
19
21
  npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
20
22
  try {
21
23
  fs
@@ -3,12 +3,14 @@
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
+ const stream = require('stream');
7
+ const zlib = require('zlib');
6
8
 
7
9
  const detectLibc = require('detect-libc');
8
10
  const npmLog = require('npmlog');
9
11
  const semver = require('semver');
10
12
  const simpleGet = require('simple-get');
11
- const tar = require('tar');
13
+ const tarFs = require('tar-fs');
12
14
 
13
15
  const agent = require('../lib/agent');
14
16
  const libvips = require('../lib/libvips');
@@ -23,6 +25,7 @@ const minimumGlibcVersionByArch = {
23
25
  const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
24
26
  const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
25
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);
26
29
 
27
30
  const fail = function (err) {
28
31
  npmLog.error('sharp', err.message);
@@ -37,22 +40,26 @@ const fail = function (err) {
37
40
  const extractTarball = function (tarPath) {
38
41
  const vendorPath = path.join(__dirname, '..', 'vendor');
39
42
  libvips.mkdirSync(vendorPath);
40
- tar
41
- .extract({
42
- file: tarPath,
43
- cwd: vendorPath,
44
- strict: true
45
- })
46
- .catch(function (err) {
47
- if (/unexpected end of file/.test(err.message)) {
48
- npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
43
+ const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
44
+ libvips.mkdirSync(versionedVendorPath);
45
+ stream.pipeline(
46
+ fs.createReadStream(tarPath),
47
+ supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
48
+ tarFs.extract(versionedVendorPath),
49
+ function (err) {
50
+ if (err) {
51
+ if (/unexpected end of file/.test(err.message)) {
52
+ npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
53
+ }
54
+ fail(err);
49
55
  }
50
- fail(err);
51
- });
56
+ }
57
+ );
52
58
  };
53
59
 
54
60
  try {
55
61
  const useGlobalLibvips = libvips.useGlobalLibvips();
62
+
56
63
  if (useGlobalLibvips) {
57
64
  const globalLibvipsVersion = libvips.globalLibvipsVersion();
58
65
  npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
@@ -75,8 +82,16 @@ try {
75
82
  throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
76
83
  }
77
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
+
78
93
  // Download to per-process temporary file
79
- const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz';
94
+ const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
80
95
  const tarPathCache = path.join(libvips.cachePath(), tarFilename);
81
96
  if (fs.existsSync(tarPathCache)) {
82
97
  npmLog.info('sharp', `Using cached ${tarPathCache}`);
@@ -86,6 +86,10 @@ const debuglog = util.debuglog('sharp');
86
86
  * .toBuffer()
87
87
  * .then( ... );
88
88
  *
89
+ * @example
90
+ * // Convert an animated GIF to an animated WebP
91
+ * await sharp('in.gif', { animated: true }).toFile('out.webp');
92
+ *
89
93
  * @param {(Buffer|string)} [input] - if present, can be
90
94
  * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
91
95
  * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
@@ -98,10 +102,11 @@ const debuglog = util.debuglog('sharp');
98
102
  * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
99
103
  * @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
100
104
  * This can reduce memory usage and might improve performance on some systems.
101
- * @param {number} [options.density=72] - number representing the DPI for vector images.
105
+ * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
102
106
  * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
103
107
  * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
104
108
  * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
109
+ * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
105
110
  * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
106
111
  * @param {number} [options.raw.width]
107
112
  * @param {number} [options.raw.height]
@@ -150,6 +155,13 @@ const Sharp = function (input, options) {
150
155
  extendRight: 0,
151
156
  extendBackground: [0, 0, 0, 255],
152
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,
153
165
  kernel: 'lanczos3',
154
166
  fastShrinkOnLoad: true,
155
167
  // operations
@@ -187,6 +199,7 @@ const Sharp = function (input, options) {
187
199
  streamOut: false,
188
200
  withMetadata: false,
189
201
  withMetadataOrientation: -1,
202
+ withMetadataIcc: '',
190
203
  resolveWithObject: false,
191
204
  // output format
192
205
  jpegQuality: 80,
@@ -214,7 +227,7 @@ const Sharp = function (input, options) {
214
227
  tiffCompression: 'jpeg',
215
228
  tiffPredictor: 'horizontal',
216
229
  tiffPyramid: false,
217
- tiffSquash: false,
230
+ tiffBitdepth: 8,
218
231
  tiffTile: false,
219
232
  tiffTileHeight: 256,
220
233
  tiffTileWidth: 256,
@@ -232,6 +245,7 @@ const Sharp = function (input, options) {
232
245
  tileAngle: 0,
233
246
  tileSkipBlanks: -1,
234
247
  tileBackground: [255, 255, 255, 255],
248
+ tileCentre: false,
235
249
  linearA: 1,
236
250
  linearB: 0,
237
251
  // 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
@@ -99,6 +99,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
99
99
  }
100
100
  }
101
101
  // Multi-page input (GIF, TIFF, PDF)
102
+ if (is.defined(inputOptions.animated)) {
103
+ if (is.bool(inputOptions.animated)) {
104
+ inputDescriptor.pages = inputOptions.animated ? -1 : 1;
105
+ } else {
106
+ throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
107
+ }
108
+ }
102
109
  if (is.defined(inputOptions.pages)) {
103
110
  if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
104
111
  inputDescriptor.pages = inputOptions.pages;
@@ -300,6 +307,7 @@ function metadata (callback) {
300
307
  * - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
301
308
  * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
302
309
  * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
310
+ * - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
303
311
  *
304
312
  * @example
305
313
  * const image = sharp(inputJpg);
@@ -310,7 +318,8 @@ function metadata (callback) {
310
318
  * });
311
319
  *
312
320
  * @example
313
- * const { entropy, sharpness } = await sharp(input).stats();
321
+ * const { entropy, sharpness, dominant } = await sharp(input).stats();
322
+ * const { r, g, b } = dominant;
314
323
  *
315
324
  * @param {Function} [callback] - called with the arguments `(err, stats)`
316
325
  * @returns {Promise<Object>}
package/lib/libvips.js CHANGED
@@ -48,17 +48,11 @@ const globalLibvipsVersion = function () {
48
48
 
49
49
  const hasVendoredLibvips = function () {
50
50
  const currentPlatformId = platform();
51
- const vendorPath = path.join(__dirname, '..', 'vendor');
52
- let vendorVersionId;
51
+ const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion);
53
52
  let vendorPlatformId;
54
53
  try {
55
- vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
56
54
  vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
57
55
  } catch (err) {}
58
- /* istanbul ignore if */
59
- if (vendorVersionId && vendorVersionId !== minimumLibvipsVersion) {
60
- throw new Error(`Found vendored libvips v${vendorVersionId} but require v${minimumLibvipsVersion}. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
61
- }
62
56
  /* istanbul ignore else */
63
57
  if (vendorPlatformId) {
64
58
  /* istanbul ignore else */
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,