sharp 0.32.5 → 0.33.0-alpha.10

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
@@ -2,10 +2,14 @@
2
2
 
3
3
  <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
4
4
 
5
- The typical use case for this high speed Node.js module
5
+ The typical use case for this high speed Node-API module
6
6
  is to convert large images in common formats to
7
7
  smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
8
8
 
9
+ It can be used with all JavaScript runtimes
10
+ that provide support for Node-API v9, including
11
+ Node.js >= 18.17.0, Deno and Bun.
12
+
9
13
  Resizing an image is typically 4x-5x faster than using the
10
14
  quickest ImageMagick and GraphicsMagick settings
11
15
  due to its use of [libvips](https://github.com/libvips/libvips).
@@ -16,7 +20,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
16
20
  As well as image resizing, operations such as
17
21
  rotation, extraction, compositing and gamma correction are available.
18
22
 
19
- Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
23
+ Most modern macOS, Windows and Linux systems
20
24
  do not require any additional install or runtime dependencies.
21
25
 
22
26
  ## Documentation
@@ -0,0 +1,36 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ 'use strict';
5
+
6
+ const { useGlobalLibvips, globalLibvipsVersion, log, spawnRebuild } = require('../lib/libvips');
7
+
8
+ const buildFromSource = (msg) => {
9
+ log(msg);
10
+ log('Attempting to build from source via node-gyp');
11
+ try {
12
+ require('node-addon-api');
13
+ log('Found node-addon-api');
14
+ } catch (err) {
15
+ log('Please add node-addon-api to your dependencies');
16
+ return;
17
+ }
18
+ try {
19
+ const gyp = require('node-gyp');
20
+ log(`Found node-gyp version ${gyp().version}`);
21
+ } catch (err) {
22
+ log('Please add node-gyp to your dependencies');
23
+ return;
24
+ }
25
+ log('See https://sharp.pixelplumbing.com/install#building-from-source');
26
+ const status = spawnRebuild();
27
+ if (status !== 0) {
28
+ process.exit(status);
29
+ }
30
+ };
31
+
32
+ if (useGlobalLibvips()) {
33
+ buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
34
+ } else if (process.env.npm_config_build_from_source) {
35
+ buildFromSource('Detected --build-from-source flag');
36
+ }
@@ -3,11 +3,10 @@
3
3
 
4
4
  'use strict';
5
5
 
6
- const util = require('util');
7
- const stream = require('stream');
6
+ const util = require('node:util');
7
+ const stream = require('node:stream');
8
8
  const is = require('./is');
9
9
 
10
- require('./libvips').hasVendoredLibvips();
11
10
  require('./sharp');
12
11
 
13
12
  // Use NODE_DEBUG=sharp to enable libvips warnings
@@ -122,7 +121,7 @@ const debuglog = util.debuglog('sharp');
122
121
  * a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
123
122
  * JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
124
123
  * @param {Object} [options] - if present, is an Object with optional attributes.
125
- * @param {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), higher levels imply lower levels, invalid metadata will always abort.
124
+ * @param {string} [options.failOn='warning'] - When to abort processing of invalid pixel data, one of (in order of sensitivity, least to most): 'none', 'truncated', 'error', 'warning'. Higher levels imply lower levels. Invalid metadata will always abort.
126
125
  * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
127
126
  * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
128
127
  * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
@@ -231,7 +230,8 @@ const Sharp = function (input, options) {
231
230
  threshold: 0,
232
231
  thresholdGrayscale: true,
233
232
  trimBackground: [],
234
- trimThreshold: 0,
233
+ trimThreshold: -1,
234
+ trimLineArt: false,
235
235
  gamma: 0,
236
236
  gammaOut: 0,
237
237
  greyscale: false,
@@ -306,6 +306,7 @@ const Sharp = function (input, options) {
306
306
  tiffCompression: 'jpeg',
307
307
  tiffPredictor: 'horizontal',
308
308
  tiffPyramid: false,
309
+ tiffMiniswhite: false,
309
310
  tiffBitdepth: 8,
310
311
  tiffTile: false,
311
312
  tiffTileHeight: 256,
package/lib/index.d.ts CHANGED
@@ -91,12 +91,6 @@ declare namespace sharp {
91
91
  zlib?: string | undefined;
92
92
  };
93
93
 
94
- /** An Object containing the platform and architecture of the current and installed vendored binaries. */
95
- const vendor: {
96
- current: string;
97
- installed: string[];
98
- };
99
-
100
94
  /** An Object containing the available interpolators and their proper values */
101
95
  const interpolators: Interpolators;
102
96
 
@@ -139,6 +133,52 @@ declare namespace sharp {
139
133
  */
140
134
  function simd(enable?: boolean): boolean;
141
135
 
136
+ /**
137
+ * Block libvips operations at runtime.
138
+ *
139
+ * This is in addition to the `VIPS_BLOCK_UNTRUSTED` environment variable,
140
+ * which when set will block all "untrusted" operations.
141
+ *
142
+ * @since 0.32.4
143
+ *
144
+ * @example <caption>Block all TIFF input.</caption>
145
+ * sharp.block({
146
+ * operation: ['VipsForeignLoadTiff']
147
+ * });
148
+ *
149
+ * @param {Object} options
150
+ * @param {Array<string>} options.operation - List of libvips low-level operation names to block.
151
+ */
152
+ function block(options: { operation: string[] }): void;
153
+
154
+ /**
155
+ * Unblock libvips operations at runtime.
156
+ *
157
+ * This is useful for defining a list of allowed operations.
158
+ *
159
+ * @since 0.32.4
160
+ *
161
+ * @example <caption>Block all input except WebP from the filesystem.</caption>
162
+ * sharp.block({
163
+ * operation: ['VipsForeignLoad']
164
+ * });
165
+ * sharp.unblock({
166
+ * operation: ['VipsForeignLoadWebpFile']
167
+ * });
168
+ *
169
+ * @example <caption>Block all input except JPEG and PNG from a Buffer or Stream.</caption>
170
+ * sharp.block({
171
+ * operation: ['VipsForeignLoad']
172
+ * });
173
+ * sharp.unblock({
174
+ * operation: ['VipsForeignLoadJpegBuffer', 'VipsForeignLoadPngBuffer']
175
+ * });
176
+ *
177
+ * @param {Object} options
178
+ * @param {Array<string>} options.operation - List of libvips low-level operation names to unblock.
179
+ */
180
+ function unblock(options: { operation: string[] }): void;
181
+
142
182
  //#endregion
143
183
 
144
184
  const gravity: GravityEnum;
@@ -600,7 +640,7 @@ declare namespace sharp {
600
640
  * @param withMetadata
601
641
  * @throws {Error} Invalid parameters.
602
642
  */
603
- withMetadata(withMetadata?: WriteableMetadata): Sharp;
643
+ withMetadata(withMetadata?: boolean | WriteableMetadata): Sharp;
604
644
 
605
645
  /**
606
646
  * Use these JPEG options for output image.
@@ -659,7 +699,6 @@ declare namespace sharp {
659
699
 
660
700
  /**
661
701
  * Use these AVIF options for output image.
662
- * Whilst it is possible to create AVIF images smaller than 16x16 pixels, most web browsers do not display these properly.
663
702
  * @param options Output options.
664
703
  * @throws {Error} Invalid options
665
704
  * @returns A sharp instance that can be used to chain operations
@@ -808,11 +847,11 @@ declare namespace sharp {
808
847
  * Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
809
848
  * Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
810
849
  * The info response Object will contain trimOffsetLeft and trimOffsetTop properties.
811
- * @param trim The specific background colour to trim, the threshold for doing so or an Object with both.
850
+ * @param options trim options
812
851
  * @throws {Error} Invalid parameters
813
852
  * @returns A sharp instance that can be used to chain operations
814
853
  */
815
- trim(trim?: string | number | TrimOptions): Sharp;
854
+ trim(options?: TrimOptions): Sharp;
816
855
 
817
856
  //#endregion
818
857
  }
@@ -1195,6 +1234,8 @@ declare namespace sharp {
1195
1234
  yres?: number | undefined;
1196
1235
  /** Reduce bitdepth to 1, 2 or 4 bit (optional, default 8) */
1197
1236
  bitdepth?: 1 | 2 | 4 | 8 | undefined;
1237
+ /** Write 1-bit images as miniswhite (optional, default false) */
1238
+ miniswhite?: boolean | undefined;
1198
1239
  /** Resolution unit options: inch, cm (optional, default 'inch') */
1199
1240
  resolutionUnit?: 'inch' | 'cm' | undefined;
1200
1241
  }
@@ -1236,10 +1277,10 @@ declare namespace sharp {
1236
1277
  }
1237
1278
 
1238
1279
  interface NormaliseOptions {
1239
- /** Percentile below which luminance values will be underexposed. */
1240
- lower?: number | undefined;
1241
- /** Percentile above which luminance values will be overexposed. */
1242
- upper?: number | undefined;
1280
+ /** Percentile below which luminance values will be underexposed. */
1281
+ lower?: number | undefined;
1282
+ /** Percentile above which luminance values will be overexposed. */
1283
+ upper?: number | undefined;
1243
1284
  }
1244
1285
 
1245
1286
  interface ResizeOptions {
@@ -1301,10 +1342,12 @@ declare namespace sharp {
1301
1342
  }
1302
1343
 
1303
1344
  interface TrimOptions {
1304
- /** background colour, parsed by the color module, defaults to that of the top-left pixel. (optional) */
1345
+ /** Background colour, parsed by the color module, defaults to that of the top-left pixel. (optional) */
1305
1346
  background?: Color | undefined;
1306
- /** the allowed difference from the above colour, a positive number. (optional, default `10`) */
1347
+ /** Allowed difference from the above colour, a positive number. (optional, default 10) */
1307
1348
  threshold?: number | undefined;
1349
+ /** Does the input more closely resemble line art (e.g. vector) rather than being photographic? (optional, default false) */
1350
+ lineArt?: boolean | undefined;
1308
1351
  }
1309
1352
 
1310
1353
  interface RawOptions {
package/lib/input.js CHANGED
@@ -483,14 +483,27 @@ function _isStreamInput () {
483
483
  * @returns {Promise<Object>|Sharp}
484
484
  */
485
485
  function metadata (callback) {
486
+ const stack = Error();
486
487
  if (is.fn(callback)) {
487
488
  if (this._isStreamInput()) {
488
489
  this.on('finish', () => {
489
490
  this._flattenBufferIn();
490
- sharp.metadata(this.options, callback);
491
+ sharp.metadata(this.options, (err, metadata) => {
492
+ if (err) {
493
+ callback(is.nativeError(err, stack));
494
+ } else {
495
+ callback(null, metadata);
496
+ }
497
+ });
491
498
  });
492
499
  } else {
493
- sharp.metadata(this.options, callback);
500
+ sharp.metadata(this.options, (err, metadata) => {
501
+ if (err) {
502
+ callback(is.nativeError(err, stack));
503
+ } else {
504
+ callback(null, metadata);
505
+ }
506
+ });
494
507
  }
495
508
  return this;
496
509
  } else {
@@ -500,7 +513,7 @@ function metadata (callback) {
500
513
  this._flattenBufferIn();
501
514
  sharp.metadata(this.options, (err, metadata) => {
502
515
  if (err) {
503
- reject(err);
516
+ reject(is.nativeError(err, stack));
504
517
  } else {
505
518
  resolve(metadata);
506
519
  }
@@ -516,7 +529,7 @@ function metadata (callback) {
516
529
  return new Promise((resolve, reject) => {
517
530
  sharp.metadata(this.options, (err, metadata) => {
518
531
  if (err) {
519
- reject(err);
532
+ reject(is.nativeError(err, stack));
520
533
  } else {
521
534
  resolve(metadata);
522
535
  }
@@ -572,14 +585,27 @@ function metadata (callback) {
572
585
  * @returns {Promise<Object>}
573
586
  */
574
587
  function stats (callback) {
588
+ const stack = Error();
575
589
  if (is.fn(callback)) {
576
590
  if (this._isStreamInput()) {
577
591
  this.on('finish', () => {
578
592
  this._flattenBufferIn();
579
- sharp.stats(this.options, callback);
593
+ sharp.stats(this.options, (err, stats) => {
594
+ if (err) {
595
+ callback(is.nativeError(err, stack));
596
+ } else {
597
+ callback(null, stats);
598
+ }
599
+ });
580
600
  });
581
601
  } else {
582
- sharp.stats(this.options, callback);
602
+ sharp.stats(this.options, (err, stats) => {
603
+ if (err) {
604
+ callback(is.nativeError(err, stack));
605
+ } else {
606
+ callback(null, stats);
607
+ }
608
+ });
583
609
  }
584
610
  return this;
585
611
  } else {
@@ -589,7 +615,7 @@ function stats (callback) {
589
615
  this._flattenBufferIn();
590
616
  sharp.stats(this.options, (err, stats) => {
591
617
  if (err) {
592
- reject(err);
618
+ reject(is.nativeError(err, stack));
593
619
  } else {
594
620
  resolve(stats);
595
621
  }
@@ -600,7 +626,7 @@ function stats (callback) {
600
626
  return new Promise((resolve, reject) => {
601
627
  sharp.stats(this.options, (err, stats) => {
602
628
  if (err) {
603
- reject(err);
629
+ reject(is.nativeError(err, stack));
604
630
  } else {
605
631
  resolve(stats);
606
632
  }
package/lib/is.js CHANGED
@@ -137,19 +137,33 @@ const invalidParameterError = function (name, expected, actual) {
137
137
  );
138
138
  };
139
139
 
140
+ /**
141
+ * Ensures an Error from C++ contains a JS stack.
142
+ *
143
+ * @param {Error} native - Error with message from C++.
144
+ * @param {Error} context - Error with stack from JS.
145
+ * @returns {Error} Error with message and stack.
146
+ * @private
147
+ */
148
+ const nativeError = function (native, context) {
149
+ context.message = native.message;
150
+ return context;
151
+ };
152
+
140
153
  module.exports = {
141
- defined: defined,
142
- object: object,
143
- plainObject: plainObject,
144
- fn: fn,
145
- bool: bool,
146
- buffer: buffer,
147
- typedArray: typedArray,
148
- arrayBuffer: arrayBuffer,
149
- string: string,
150
- number: number,
151
- integer: integer,
152
- inRange: inRange,
153
- inArray: inArray,
154
- invalidParameterError: invalidParameterError
154
+ defined,
155
+ object,
156
+ plainObject,
157
+ fn,
158
+ bool,
159
+ buffer,
160
+ typedArray,
161
+ arrayBuffer,
162
+ string,
163
+ number,
164
+ integer,
165
+ inRange,
166
+ inArray,
167
+ invalidParameterError,
168
+ nativeError
155
169
  };
package/lib/libvips.js CHANGED
@@ -3,61 +3,74 @@
3
3
 
4
4
  'use strict';
5
5
 
6
- const fs = require('fs');
7
- const os = require('os');
8
- const path = require('path');
9
- const spawnSync = require('child_process').spawnSync;
6
+ const { spawnSync } = require('node:child_process');
10
7
  const semverCoerce = require('semver/functions/coerce');
11
8
  const semverGreaterThanOrEqualTo = require('semver/functions/gte');
9
+ const detectLibc = require('detect-libc');
12
10
 
13
- const platform = require('./platform');
14
- const { config } = require('../package.json');
11
+ const { engines } = require('../package.json');
15
12
 
16
- const env = process.env;
17
- const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
18
- config.libvips;
13
+ const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */
14
+ engines.libvips;
19
15
  const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
20
16
 
17
+ const prebuiltPlatforms = [
18
+ 'darwin-arm64', 'darwin-x64',
19
+ 'linux-arm', 'linux-arm64', 'linux-x64',
20
+ 'linuxmusl-arm64', 'linuxmusl-x64',
21
+ 'win32-ia32', 'win32-x64'
22
+ ];
23
+
21
24
  const spawnSyncOptions = {
22
25
  encoding: 'utf8',
23
26
  shell: true
24
27
  };
25
28
 
26
- const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
27
-
28
- const mkdirSync = function (dirPath) {
29
- try {
30
- fs.mkdirSync(dirPath, { recursive: true });
31
- } catch (err) {
32
- /* istanbul ignore next */
33
- if (err.code !== 'EEXIST') {
34
- throw err;
35
- }
29
+ const log = (item) => {
30
+ if (item instanceof Error) {
31
+ console.error(`sharp: Installation error: ${item.message}`);
32
+ } else {
33
+ console.log(`sharp: ${item}`);
36
34
  }
37
35
  };
38
36
 
39
- const cachePath = function () {
40
- const npmCachePath = env.npm_config_cache || /* istanbul ignore next */
41
- (env.APPDATA ? path.join(env.APPDATA, 'npm-cache') : path.join(os.homedir(), '.npm'));
42
- mkdirSync(npmCachePath);
43
- const libvipsCachePath = path.join(npmCachePath, '_libvips');
44
- mkdirSync(libvipsCachePath);
45
- return libvipsCachePath;
37
+ /* istanbul ignore next */
38
+ const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '';
39
+
40
+ const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`;
41
+
42
+ /* istanbul ignore next */
43
+ const buildPlatformArch = () => {
44
+ /* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */
45
+ const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env;
46
+ return `${npm_config_platform || process.platform}${npm_config_libc || runtimeLibc()}-${npm_config_arch || process.arch}`;
46
47
  };
47
48
 
48
- const integrity = function (platformAndArch) {
49
- return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
49
+ const buildSharpLibvipsIncludeDir = () => {
50
+ try {
51
+ return require('@img/sharp-libvips-dev/include');
52
+ } catch {}
53
+ /* istanbul ignore next */
54
+ return '';
50
55
  };
51
56
 
52
- const log = function (item) {
53
- if (item instanceof Error) {
54
- console.error(`sharp: Installation error: ${item.message}`);
55
- } else {
56
- console.log(`sharp: ${item}`);
57
- }
57
+ const buildSharpLibvipsCPlusPlusDir = () => {
58
+ try {
59
+ return require('@img/sharp-libvips-dev/cplusplus');
60
+ } catch {}
61
+ /* istanbul ignore next */
62
+ return '';
63
+ };
64
+
65
+ const buildSharpLibvipsLibDir = () => {
66
+ try {
67
+ return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`);
68
+ } catch {}
69
+ /* istanbul ignore next */
70
+ return '';
58
71
  };
59
72
 
60
- const isRosetta = function () {
73
+ const isRosetta = () => {
61
74
  /* istanbul ignore next */
62
75
  if (process.platform === 'darwin' && process.arch === 'x64') {
63
76
  const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
@@ -66,12 +79,19 @@ const isRosetta = function () {
66
79
  return false;
67
80
  };
68
81
 
69
- const globalLibvipsVersion = function () {
82
+ /* istanbul ignore next */
83
+ const spawnRebuild = () =>
84
+ spawnSync('node-gyp rebuild --directory=src', {
85
+ ...spawnSyncOptions,
86
+ stdio: 'inherit'
87
+ }).status;
88
+
89
+ const globalLibvipsVersion = () => {
70
90
  if (process.platform !== 'win32') {
71
91
  const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
72
92
  ...spawnSyncOptions,
73
93
  env: {
74
- ...env,
94
+ ...process.env,
75
95
  PKG_CONFIG_PATH: pkgConfigPath()
76
96
  }
77
97
  }).stdout;
@@ -82,17 +102,8 @@ const globalLibvipsVersion = function () {
82
102
  }
83
103
  };
84
104
 
85
- const hasVendoredLibvips = function () {
86
- return fs.existsSync(vendorPath);
87
- };
88
-
89
- /* istanbul ignore next */
90
- const removeVendoredLibvips = function () {
91
- fs.rmSync(vendorPath, { recursive: true, maxRetries: 3, force: true });
92
- };
93
-
94
105
  /* istanbul ignore next */
95
- const pkgConfigPath = function () {
106
+ const pkgConfigPath = () => {
96
107
  if (process.platform !== 'win32') {
97
108
  const brewPkgConfigPath = spawnSync(
98
109
  'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
@@ -100,7 +111,7 @@ const pkgConfigPath = function () {
100
111
  ).stdout || '';
101
112
  return [
102
113
  brewPkgConfigPath.trim(),
103
- env.PKG_CONFIG_PATH,
114
+ process.env.PKG_CONFIG_PATH,
104
115
  '/usr/local/lib/pkgconfig',
105
116
  '/usr/lib/pkgconfig',
106
117
  '/usr/local/libdata/pkgconfig',
@@ -111,8 +122,9 @@ const pkgConfigPath = function () {
111
122
  }
112
123
  };
113
124
 
114
- const useGlobalLibvips = function () {
115
- if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
125
+ const useGlobalLibvips = () => {
126
+ if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
127
+ log('Detected SHARP_IGNORE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips');
116
128
  return false;
117
129
  }
118
130
  /* istanbul ignore next */
@@ -127,14 +139,15 @@ const useGlobalLibvips = function () {
127
139
 
128
140
  module.exports = {
129
141
  minimumLibvipsVersion,
130
- minimumLibvipsVersionLabelled,
131
- cachePath,
132
- integrity,
142
+ prebuiltPlatforms,
143
+ buildPlatformArch,
144
+ buildSharpLibvipsIncludeDir,
145
+ buildSharpLibvipsCPlusPlusDir,
146
+ buildSharpLibvipsLibDir,
147
+ runtimePlatformArch,
133
148
  log,
149
+ spawnRebuild,
134
150
  globalLibvipsVersion,
135
- hasVendoredLibvips,
136
- removeVendoredLibvips,
137
151
  pkgConfigPath,
138
- useGlobalLibvips,
139
- mkdirSync
152
+ useGlobalLibvips
140
153
  };
package/lib/operation.js CHANGED
@@ -19,7 +19,7 @@ const is = require('./is');
19
19
  * If no angle is provided, it is determined from the EXIF data.
20
20
  * Mirroring is supported and may infer the use of a flip operation.
21
21
  *
22
- * The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
22
+ * The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
23
23
  *
24
24
  * Only one rotation can occur per pipeline.
25
25
  * Previous calls to `rotate` in the same pipeline will be ignored.
@@ -83,8 +83,6 @@ function rotate (angle, options) {
83
83
  * Mirror the image vertically (up-down) about the x-axis.
84
84
  * This always occurs before rotation, if any.
85
85
  *
86
- * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
87
- *
88
86
  * This operation does not work correctly with multi-page images.
89
87
  *
90
88
  * @example
@@ -102,8 +100,6 @@ function flip (flip) {
102
100
  * Mirror the image horizontally (left-right) about the y-axis.
103
101
  * This always occurs before rotation, if any.
104
102
  *
105
- * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
106
- *
107
103
  * @example
108
104
  * const output = await sharp(input).flop().toBuffer();
109
105
  *