@revizly/sharp 0.33.2-revizly13
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +191 -0
- package/README.md +118 -0
- package/install/check.js +41 -0
- package/lib/channel.js +174 -0
- package/lib/colour.js +182 -0
- package/lib/composite.js +210 -0
- package/lib/constructor.js +449 -0
- package/lib/index.d.ts +1723 -0
- package/lib/index.js +16 -0
- package/lib/input.js +657 -0
- package/lib/is.js +169 -0
- package/lib/libvips.js +195 -0
- package/lib/operation.js +921 -0
- package/lib/output.js +1572 -0
- package/lib/resize.js +582 -0
- package/lib/sharp.js +116 -0
- package/lib/utility.js +286 -0
- package/package.json +201 -0
- package/src/binding.gyp +280 -0
- package/src/common.cc +1090 -0
- package/src/common.h +393 -0
- package/src/metadata.cc +287 -0
- package/src/metadata.h +82 -0
- package/src/operations.cc +471 -0
- package/src/operations.h +125 -0
- package/src/pipeline.cc +1742 -0
- package/src/pipeline.h +385 -0
- package/src/sharp.cc +40 -0
- package/src/stats.cc +183 -0
- package/src/stats.h +59 -0
- package/src/utilities.cc +269 -0
- package/src/utilities.h +19 -0
package/lib/resize.js
ADDED
@@ -0,0 +1,582 @@
|
|
1
|
+
// Copyright 2013 Lovell Fuller and others.
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
3
|
+
|
4
|
+
'use strict';
|
5
|
+
|
6
|
+
const is = require('./is');
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Weighting to apply when using contain/cover fit.
|
10
|
+
* @member
|
11
|
+
* @private
|
12
|
+
*/
|
13
|
+
const gravity = {
|
14
|
+
center: 0,
|
15
|
+
centre: 0,
|
16
|
+
north: 1,
|
17
|
+
east: 2,
|
18
|
+
south: 3,
|
19
|
+
west: 4,
|
20
|
+
northeast: 5,
|
21
|
+
southeast: 6,
|
22
|
+
southwest: 7,
|
23
|
+
northwest: 8
|
24
|
+
};
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Position to apply when using contain/cover fit.
|
28
|
+
* @member
|
29
|
+
* @private
|
30
|
+
*/
|
31
|
+
const position = {
|
32
|
+
top: 1,
|
33
|
+
right: 2,
|
34
|
+
bottom: 3,
|
35
|
+
left: 4,
|
36
|
+
'right top': 5,
|
37
|
+
'right bottom': 6,
|
38
|
+
'left bottom': 7,
|
39
|
+
'left top': 8
|
40
|
+
};
|
41
|
+
|
42
|
+
/**
|
43
|
+
* How to extend the image.
|
44
|
+
* @member
|
45
|
+
* @private
|
46
|
+
*/
|
47
|
+
const extendWith = {
|
48
|
+
background: 'background',
|
49
|
+
copy: 'copy',
|
50
|
+
repeat: 'repeat',
|
51
|
+
mirror: 'mirror'
|
52
|
+
};
|
53
|
+
|
54
|
+
/**
|
55
|
+
* Strategies for automagic cover behaviour.
|
56
|
+
* @member
|
57
|
+
* @private
|
58
|
+
*/
|
59
|
+
const strategy = {
|
60
|
+
entropy: 16,
|
61
|
+
attention: 17
|
62
|
+
};
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Reduction kernels.
|
66
|
+
* @member
|
67
|
+
* @private
|
68
|
+
*/
|
69
|
+
const kernel = {
|
70
|
+
nearest: 'nearest',
|
71
|
+
cubic: 'cubic',
|
72
|
+
mitchell: 'mitchell',
|
73
|
+
lanczos2: 'lanczos2',
|
74
|
+
lanczos3: 'lanczos3'
|
75
|
+
};
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Methods by which an image can be resized to fit the provided dimensions.
|
79
|
+
* @member
|
80
|
+
* @private
|
81
|
+
*/
|
82
|
+
const fit = {
|
83
|
+
contain: 'contain',
|
84
|
+
cover: 'cover',
|
85
|
+
fill: 'fill',
|
86
|
+
inside: 'inside',
|
87
|
+
outside: 'outside'
|
88
|
+
};
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Map external fit property to internal canvas property.
|
92
|
+
* @member
|
93
|
+
* @private
|
94
|
+
*/
|
95
|
+
const mapFitToCanvas = {
|
96
|
+
contain: 'embed',
|
97
|
+
cover: 'crop',
|
98
|
+
fill: 'ignore_aspect',
|
99
|
+
inside: 'max',
|
100
|
+
outside: 'min'
|
101
|
+
};
|
102
|
+
|
103
|
+
/**
|
104
|
+
* @private
|
105
|
+
*/
|
106
|
+
function isRotationExpected (options) {
|
107
|
+
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
|
108
|
+
}
|
109
|
+
|
110
|
+
/**
|
111
|
+
* @private
|
112
|
+
*/
|
113
|
+
function isResizeExpected (options) {
|
114
|
+
return options.width !== -1 || options.height !== -1;
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Resize image to `width`, `height` or `width x height`.
|
119
|
+
*
|
120
|
+
* When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
|
121
|
+
* - `cover`: (default) Preserving aspect ratio, attempt to ensure the image covers both provided dimensions by cropping/clipping to fit.
|
122
|
+
* - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
|
123
|
+
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
|
124
|
+
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
|
125
|
+
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
|
126
|
+
*
|
127
|
+
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
128
|
+
*
|
129
|
+
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
|
130
|
+
*
|
131
|
+
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
132
|
+
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
133
|
+
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
|
134
|
+
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
|
135
|
+
*
|
136
|
+
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
|
137
|
+
*
|
138
|
+
* The experimental strategy-based approach resizes so one dimension is at its target length
|
139
|
+
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
|
140
|
+
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
|
141
|
+
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
|
142
|
+
*
|
143
|
+
* Possible interpolation kernels are:
|
144
|
+
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
|
145
|
+
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
|
146
|
+
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
|
147
|
+
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
|
148
|
+
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
|
149
|
+
*
|
150
|
+
* Only one resize can occur per pipeline.
|
151
|
+
* Previous calls to `resize` in the same pipeline will be ignored.
|
152
|
+
*
|
153
|
+
* @example
|
154
|
+
* sharp(input)
|
155
|
+
* .resize({ width: 100 })
|
156
|
+
* .toBuffer()
|
157
|
+
* .then(data => {
|
158
|
+
* // 100 pixels wide, auto-scaled height
|
159
|
+
* });
|
160
|
+
*
|
161
|
+
* @example
|
162
|
+
* sharp(input)
|
163
|
+
* .resize({ height: 100 })
|
164
|
+
* .toBuffer()
|
165
|
+
* .then(data => {
|
166
|
+
* // 100 pixels high, auto-scaled width
|
167
|
+
* });
|
168
|
+
*
|
169
|
+
* @example
|
170
|
+
* sharp(input)
|
171
|
+
* .resize(200, 300, {
|
172
|
+
* kernel: sharp.kernel.nearest,
|
173
|
+
* fit: 'contain',
|
174
|
+
* position: 'right top',
|
175
|
+
* background: { r: 255, g: 255, b: 255, alpha: 0.5 }
|
176
|
+
* })
|
177
|
+
* .toFile('output.png')
|
178
|
+
* .then(() => {
|
179
|
+
* // output.png is a 200 pixels wide and 300 pixels high image
|
180
|
+
* // containing a nearest-neighbour scaled version
|
181
|
+
* // contained within the north-east corner of a semi-transparent white canvas
|
182
|
+
* });
|
183
|
+
*
|
184
|
+
* @example
|
185
|
+
* const transformer = sharp()
|
186
|
+
* .resize({
|
187
|
+
* width: 200,
|
188
|
+
* height: 200,
|
189
|
+
* fit: sharp.fit.cover,
|
190
|
+
* position: sharp.strategy.entropy
|
191
|
+
* });
|
192
|
+
* // Read image data from readableStream
|
193
|
+
* // Write 200px square auto-cropped image data to writableStream
|
194
|
+
* readableStream
|
195
|
+
* .pipe(transformer)
|
196
|
+
* .pipe(writableStream);
|
197
|
+
*
|
198
|
+
* @example
|
199
|
+
* sharp(input)
|
200
|
+
* .resize(200, 200, {
|
201
|
+
* fit: sharp.fit.inside,
|
202
|
+
* withoutEnlargement: true
|
203
|
+
* })
|
204
|
+
* .toFormat('jpeg')
|
205
|
+
* .toBuffer()
|
206
|
+
* .then(function(outputBuffer) {
|
207
|
+
* // outputBuffer contains JPEG image data
|
208
|
+
* // no wider and no higher than 200 pixels
|
209
|
+
* // and no larger than the input image
|
210
|
+
* });
|
211
|
+
*
|
212
|
+
* @example
|
213
|
+
* sharp(input)
|
214
|
+
* .resize(200, 200, {
|
215
|
+
* fit: sharp.fit.outside,
|
216
|
+
* withoutReduction: true
|
217
|
+
* })
|
218
|
+
* .toFormat('jpeg')
|
219
|
+
* .toBuffer()
|
220
|
+
* .then(function(outputBuffer) {
|
221
|
+
* // outputBuffer contains JPEG image data
|
222
|
+
* // of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
|
223
|
+
* // and no smaller than the input image
|
224
|
+
* });
|
225
|
+
*
|
226
|
+
* @example
|
227
|
+
* const scaleByHalf = await sharp(input)
|
228
|
+
* .metadata()
|
229
|
+
* .then(({ width }) => sharp(input)
|
230
|
+
* .resize(Math.round(width * 0.5))
|
231
|
+
* .toBuffer()
|
232
|
+
* );
|
233
|
+
*
|
234
|
+
* @param {number} [width] - How many pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
235
|
+
* @param {number} [height] - How many pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
236
|
+
* @param {Object} [options]
|
237
|
+
* @param {number} [options.width] - An alternative means of specifying `width`. If both are present this takes priority.
|
238
|
+
* @param {number} [options.height] - An alternative means of specifying `height`. If both are present this takes priority.
|
239
|
+
* @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
240
|
+
* @param {String} [options.position='centre'] - A position, gravity or strategy to use when `fit` is `cover` or `contain`.
|
241
|
+
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
242
|
+
* @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
|
243
|
+
* @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
|
244
|
+
* @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
|
245
|
+
* @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.
|
246
|
+
* @returns {Sharp}
|
247
|
+
* @throws {Error} Invalid parameters
|
248
|
+
*/
|
249
|
+
function resize (widthOrOptions, height, options) {
|
250
|
+
if (isResizeExpected(this.options)) {
|
251
|
+
this.options.debuglog('ignoring previous resize options');
|
252
|
+
}
|
253
|
+
if (this.options.widthPost !== -1) {
|
254
|
+
this.options.debuglog('operation order will be: extract, resize, extract');
|
255
|
+
}
|
256
|
+
if (is.defined(widthOrOptions)) {
|
257
|
+
if (is.object(widthOrOptions) && !is.defined(options)) {
|
258
|
+
options = widthOrOptions;
|
259
|
+
} else if (is.integer(widthOrOptions) && widthOrOptions > 0) {
|
260
|
+
this.options.width = widthOrOptions;
|
261
|
+
} else {
|
262
|
+
throw is.invalidParameterError('width', 'positive integer', widthOrOptions);
|
263
|
+
}
|
264
|
+
} else {
|
265
|
+
this.options.width = -1;
|
266
|
+
}
|
267
|
+
if (is.defined(height)) {
|
268
|
+
if (is.integer(height) && height > 0) {
|
269
|
+
this.options.height = height;
|
270
|
+
} else {
|
271
|
+
throw is.invalidParameterError('height', 'positive integer', height);
|
272
|
+
}
|
273
|
+
} else {
|
274
|
+
this.options.height = -1;
|
275
|
+
}
|
276
|
+
if (is.object(options)) {
|
277
|
+
// Width
|
278
|
+
if (is.defined(options.width)) {
|
279
|
+
if (is.integer(options.width) && options.width > 0) {
|
280
|
+
this.options.width = options.width;
|
281
|
+
} else {
|
282
|
+
throw is.invalidParameterError('width', 'positive integer', options.width);
|
283
|
+
}
|
284
|
+
}
|
285
|
+
// Height
|
286
|
+
if (is.defined(options.height)) {
|
287
|
+
if (is.integer(options.height) && options.height > 0) {
|
288
|
+
this.options.height = options.height;
|
289
|
+
} else {
|
290
|
+
throw is.invalidParameterError('height', 'positive integer', options.height);
|
291
|
+
}
|
292
|
+
}
|
293
|
+
// Fit
|
294
|
+
if (is.defined(options.fit)) {
|
295
|
+
const canvas = mapFitToCanvas[options.fit];
|
296
|
+
if (is.string(canvas)) {
|
297
|
+
this.options.canvas = canvas;
|
298
|
+
} else {
|
299
|
+
throw is.invalidParameterError('fit', 'valid fit', options.fit);
|
300
|
+
}
|
301
|
+
}
|
302
|
+
// Position
|
303
|
+
if (is.defined(options.position)) {
|
304
|
+
const pos = is.integer(options.position)
|
305
|
+
? options.position
|
306
|
+
: strategy[options.position] || position[options.position] || gravity[options.position];
|
307
|
+
if (is.integer(pos) && (is.inRange(pos, 0, 8) || is.inRange(pos, 16, 17))) {
|
308
|
+
this.options.position = pos;
|
309
|
+
} else {
|
310
|
+
throw is.invalidParameterError('position', 'valid position/gravity/strategy', options.position);
|
311
|
+
}
|
312
|
+
}
|
313
|
+
// Background
|
314
|
+
this._setBackgroundColourOption('resizeBackground', options.background);
|
315
|
+
// Kernel
|
316
|
+
if (is.defined(options.kernel)) {
|
317
|
+
if (is.string(kernel[options.kernel])) {
|
318
|
+
this.options.kernel = kernel[options.kernel];
|
319
|
+
} else {
|
320
|
+
throw is.invalidParameterError('kernel', 'valid kernel name', options.kernel);
|
321
|
+
}
|
322
|
+
}
|
323
|
+
// Without enlargement
|
324
|
+
if (is.defined(options.withoutEnlargement)) {
|
325
|
+
this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
|
326
|
+
}
|
327
|
+
// Without reduction
|
328
|
+
if (is.defined(options.withoutReduction)) {
|
329
|
+
this._setBooleanOption('withoutReduction', options.withoutReduction);
|
330
|
+
}
|
331
|
+
// Shrink on load
|
332
|
+
if (is.defined(options.fastShrinkOnLoad)) {
|
333
|
+
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
|
334
|
+
}
|
335
|
+
}
|
336
|
+
if (isRotationExpected(this.options) && isResizeExpected(this.options)) {
|
337
|
+
this.options.rotateBeforePreExtract = true;
|
338
|
+
}
|
339
|
+
return this;
|
340
|
+
}
|
341
|
+
|
342
|
+
/**
|
343
|
+
* Extend / pad / extrude one or more edges of the image with either
|
344
|
+
* the provided background colour or pixels derived from the image.
|
345
|
+
* This operation will always occur after resizing and extraction, if any.
|
346
|
+
*
|
347
|
+
* @example
|
348
|
+
* // Resize to 140 pixels wide, then add 10 transparent pixels
|
349
|
+
* // to the top, left and right edges and 20 to the bottom edge
|
350
|
+
* sharp(input)
|
351
|
+
* .resize(140)
|
352
|
+
* .extend({
|
353
|
+
* top: 10,
|
354
|
+
* bottom: 20,
|
355
|
+
* left: 10,
|
356
|
+
* right: 10,
|
357
|
+
* background: { r: 0, g: 0, b: 0, alpha: 0 }
|
358
|
+
* })
|
359
|
+
* ...
|
360
|
+
*
|
361
|
+
* @example
|
362
|
+
* // Add a row of 10 red pixels to the bottom
|
363
|
+
* sharp(input)
|
364
|
+
* .extend({
|
365
|
+
* bottom: 10,
|
366
|
+
* background: 'red'
|
367
|
+
* })
|
368
|
+
* ...
|
369
|
+
*
|
370
|
+
* @example
|
371
|
+
* // Extrude image by 8 pixels to the right, mirroring existing right hand edge
|
372
|
+
* sharp(input)
|
373
|
+
* .extend({
|
374
|
+
* right: 8,
|
375
|
+
* background: 'mirror'
|
376
|
+
* })
|
377
|
+
* ...
|
378
|
+
*
|
379
|
+
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
|
380
|
+
* @param {number} [extend.top=0]
|
381
|
+
* @param {number} [extend.left=0]
|
382
|
+
* @param {number} [extend.bottom=0]
|
383
|
+
* @param {number} [extend.right=0]
|
384
|
+
* @param {String} [extend.extendWith='background'] - populate new pixels using this method, one of: background, copy, repeat, mirror.
|
385
|
+
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
386
|
+
* @returns {Sharp}
|
387
|
+
* @throws {Error} Invalid parameters
|
388
|
+
*/
|
389
|
+
function extend (extend) {
|
390
|
+
if (is.integer(extend) && extend > 0) {
|
391
|
+
this.options.extendTop = extend;
|
392
|
+
this.options.extendBottom = extend;
|
393
|
+
this.options.extendLeft = extend;
|
394
|
+
this.options.extendRight = extend;
|
395
|
+
} else if (is.object(extend)) {
|
396
|
+
if (is.defined(extend.top)) {
|
397
|
+
if (is.integer(extend.top) && extend.top >= 0) {
|
398
|
+
this.options.extendTop = extend.top;
|
399
|
+
} else {
|
400
|
+
throw is.invalidParameterError('top', 'positive integer', extend.top);
|
401
|
+
}
|
402
|
+
}
|
403
|
+
if (is.defined(extend.bottom)) {
|
404
|
+
if (is.integer(extend.bottom) && extend.bottom >= 0) {
|
405
|
+
this.options.extendBottom = extend.bottom;
|
406
|
+
} else {
|
407
|
+
throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
|
408
|
+
}
|
409
|
+
}
|
410
|
+
if (is.defined(extend.left)) {
|
411
|
+
if (is.integer(extend.left) && extend.left >= 0) {
|
412
|
+
this.options.extendLeft = extend.left;
|
413
|
+
} else {
|
414
|
+
throw is.invalidParameterError('left', 'positive integer', extend.left);
|
415
|
+
}
|
416
|
+
}
|
417
|
+
if (is.defined(extend.right)) {
|
418
|
+
if (is.integer(extend.right) && extend.right >= 0) {
|
419
|
+
this.options.extendRight = extend.right;
|
420
|
+
} else {
|
421
|
+
throw is.invalidParameterError('right', 'positive integer', extend.right);
|
422
|
+
}
|
423
|
+
}
|
424
|
+
this._setBackgroundColourOption('extendBackground', extend.background);
|
425
|
+
if (is.defined(extend.extendWith)) {
|
426
|
+
if (is.string(extendWith[extend.extendWith])) {
|
427
|
+
this.options.extendWith = extendWith[extend.extendWith];
|
428
|
+
} else {
|
429
|
+
throw is.invalidParameterError('extendWith', 'one of: background, copy, repeat, mirror', extend.extendWith);
|
430
|
+
}
|
431
|
+
}
|
432
|
+
} else {
|
433
|
+
throw is.invalidParameterError('extend', 'integer or object', extend);
|
434
|
+
}
|
435
|
+
return this;
|
436
|
+
}
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Extract/crop a region of the image.
|
440
|
+
*
|
441
|
+
* - Use `extract` before `resize` for pre-resize extraction.
|
442
|
+
* - Use `extract` after `resize` for post-resize extraction.
|
443
|
+
* - Use `extract` twice and `resize` once for extract-then-resize-then-extract in a fixed operation order.
|
444
|
+
*
|
445
|
+
* @example
|
446
|
+
* sharp(input)
|
447
|
+
* .extract({ left: left, top: top, width: width, height: height })
|
448
|
+
* .toFile(output, function(err) {
|
449
|
+
* // Extract a region of the input image, saving in the same format.
|
450
|
+
* });
|
451
|
+
* @example
|
452
|
+
* sharp(input)
|
453
|
+
* .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre })
|
454
|
+
* .resize(width, height)
|
455
|
+
* .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost })
|
456
|
+
* .toFile(output, function(err) {
|
457
|
+
* // Extract a region, resize, then extract from the resized image
|
458
|
+
* });
|
459
|
+
*
|
460
|
+
* @param {Object} options - describes the region to extract using integral pixel values
|
461
|
+
* @param {number} options.left - zero-indexed offset from left edge
|
462
|
+
* @param {number} options.top - zero-indexed offset from top edge
|
463
|
+
* @param {number} options.width - width of region to extract
|
464
|
+
* @param {number} options.height - height of region to extract
|
465
|
+
* @returns {Sharp}
|
466
|
+
* @throws {Error} Invalid parameters
|
467
|
+
*/
|
468
|
+
function extract (options) {
|
469
|
+
const suffix = isResizeExpected(this.options) || this.options.widthPre !== -1 ? 'Post' : 'Pre';
|
470
|
+
if (this.options[`width${suffix}`] !== -1) {
|
471
|
+
this.options.debuglog('ignoring previous extract options');
|
472
|
+
}
|
473
|
+
['left', 'top', 'width', 'height'].forEach(function (name) {
|
474
|
+
const value = options[name];
|
475
|
+
if (is.integer(value) && value >= 0) {
|
476
|
+
this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
|
477
|
+
} else {
|
478
|
+
throw is.invalidParameterError(name, 'integer', value);
|
479
|
+
}
|
480
|
+
}, this);
|
481
|
+
// Ensure existing rotation occurs before pre-resize extraction
|
482
|
+
if (isRotationExpected(this.options) && !isResizeExpected(this.options)) {
|
483
|
+
if (this.options.widthPre === -1 || this.options.widthPost === -1) {
|
484
|
+
this.options.rotateBeforePreExtract = true;
|
485
|
+
}
|
486
|
+
}
|
487
|
+
return this;
|
488
|
+
}
|
489
|
+
|
490
|
+
/**
|
491
|
+
* Trim pixels from all edges that contain values similar to the given background colour, which defaults to that of the top-left pixel.
|
492
|
+
*
|
493
|
+
* Images with an alpha channel will use the combined bounding box of alpha and non-alpha channels.
|
494
|
+
*
|
495
|
+
* If the result of this operation would trim an image to nothing then no change is made.
|
496
|
+
*
|
497
|
+
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
498
|
+
*
|
499
|
+
* @example
|
500
|
+
* // Trim pixels with a colour similar to that of the top-left pixel.
|
501
|
+
* await sharp(input)
|
502
|
+
* .trim()
|
503
|
+
* .toFile(output);
|
504
|
+
*
|
505
|
+
* @example
|
506
|
+
* // Trim pixels with the exact same colour as that of the top-left pixel.
|
507
|
+
* await sharp(input)
|
508
|
+
* .trim({
|
509
|
+
* threshold: 0
|
510
|
+
* })
|
511
|
+
* .toFile(output);
|
512
|
+
*
|
513
|
+
* @example
|
514
|
+
* // Assume input is line art and trim only pixels with a similar colour to red.
|
515
|
+
* const output = await sharp(input)
|
516
|
+
* .trim({
|
517
|
+
* background: "#FF0000",
|
518
|
+
* lineArt: true
|
519
|
+
* })
|
520
|
+
* .toBuffer();
|
521
|
+
*
|
522
|
+
* @example
|
523
|
+
* // Trim all "yellow-ish" pixels, being more lenient with the higher threshold.
|
524
|
+
* const output = await sharp(input)
|
525
|
+
* .trim({
|
526
|
+
* background: "yellow",
|
527
|
+
* threshold: 42,
|
528
|
+
* })
|
529
|
+
* .toBuffer();
|
530
|
+
*
|
531
|
+
* @param {Object} [options]
|
532
|
+
* @param {string|Object} [options.background='top-left pixel'] - Background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to that of the top-left pixel.
|
533
|
+
* @param {number} [options.threshold=10] - Allowed difference from the above colour, a positive number.
|
534
|
+
* @param {boolean} [options.lineArt=false] - Does the input more closely resemble line art (e.g. vector) rather than being photographic?
|
535
|
+
* @returns {Sharp}
|
536
|
+
* @throws {Error} Invalid parameters
|
537
|
+
*/
|
538
|
+
function trim (options) {
|
539
|
+
this.options.trimThreshold = 10;
|
540
|
+
if (is.defined(options)) {
|
541
|
+
if (is.object(options)) {
|
542
|
+
if (is.defined(options.background)) {
|
543
|
+
this._setBackgroundColourOption('trimBackground', options.background);
|
544
|
+
}
|
545
|
+
if (is.defined(options.threshold)) {
|
546
|
+
if (is.number(options.threshold) && options.threshold >= 0) {
|
547
|
+
this.options.trimThreshold = options.threshold;
|
548
|
+
} else {
|
549
|
+
throw is.invalidParameterError('threshold', 'positive number', options.threshold);
|
550
|
+
}
|
551
|
+
}
|
552
|
+
if (is.defined(options.lineArt)) {
|
553
|
+
this._setBooleanOption('trimLineArt', options.lineArt);
|
554
|
+
}
|
555
|
+
} else {
|
556
|
+
throw is.invalidParameterError('trim', 'object', options);
|
557
|
+
}
|
558
|
+
}
|
559
|
+
if (isRotationExpected(this.options)) {
|
560
|
+
this.options.rotateBeforePreExtract = true;
|
561
|
+
}
|
562
|
+
return this;
|
563
|
+
}
|
564
|
+
|
565
|
+
/**
|
566
|
+
* Decorate the Sharp prototype with resize-related functions.
|
567
|
+
* @private
|
568
|
+
*/
|
569
|
+
module.exports = function (Sharp) {
|
570
|
+
Object.assign(Sharp.prototype, {
|
571
|
+
resize,
|
572
|
+
extend,
|
573
|
+
extract,
|
574
|
+
trim
|
575
|
+
});
|
576
|
+
// Class attributes
|
577
|
+
Sharp.gravity = gravity;
|
578
|
+
Sharp.strategy = strategy;
|
579
|
+
Sharp.kernel = kernel;
|
580
|
+
Sharp.fit = fit;
|
581
|
+
Sharp.position = position;
|
582
|
+
};
|
package/lib/sharp.js
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
// Copyright 2013 Lovell Fuller and others.
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
3
|
+
|
4
|
+
'use strict';
|
5
|
+
|
6
|
+
// Inspects the runtime environment and exports the relevant sharp.node binary
|
7
|
+
|
8
|
+
const { familySync, versionSync } = require('detect-libc');
|
9
|
+
|
10
|
+
const { runtimePlatformArch, isUnsupportedNodeRuntime, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
|
11
|
+
const runtimePlatform = runtimePlatformArch();
|
12
|
+
|
13
|
+
const paths = [
|
14
|
+
`../src/build/Release/sharp-${runtimePlatform}.node`,
|
15
|
+
'../src/build/Release/sharp-wasm32.node',
|
16
|
+
`@revizly/sharp-${runtimePlatform}/sharp.node`,
|
17
|
+
'@revizly/sharp-wasm32/sharp.node'
|
18
|
+
];
|
19
|
+
|
20
|
+
let sharp;
|
21
|
+
const errors = [];
|
22
|
+
for (const path of paths) {
|
23
|
+
try {
|
24
|
+
sharp = require(path);
|
25
|
+
break;
|
26
|
+
} catch (err) {
|
27
|
+
/* istanbul ignore next */
|
28
|
+
errors.push(err);
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
/* istanbul ignore next */
|
33
|
+
if (sharp) {
|
34
|
+
module.exports = sharp;
|
35
|
+
} else {
|
36
|
+
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
37
|
+
|
38
|
+
const help = [`Could not load the "sharp" module using the ${runtimePlatform} runtime`];
|
39
|
+
errors.forEach(err => {
|
40
|
+
if (err.code !== 'MODULE_NOT_FOUND') {
|
41
|
+
help.push(`${err.code}: ${err.message}`);
|
42
|
+
}
|
43
|
+
});
|
44
|
+
const messages = errors.map(err => err.message).join(' ');
|
45
|
+
help.push('Possible solutions:');
|
46
|
+
// Common error messages
|
47
|
+
if (isUnsupportedNodeRuntime()) {
|
48
|
+
const { found, expected } = isUnsupportedNodeRuntime();
|
49
|
+
help.push(
|
50
|
+
'- Please upgrade Node.js:',
|
51
|
+
` Found ${found}`,
|
52
|
+
` Requires ${expected}`
|
53
|
+
);
|
54
|
+
} else if (prebuiltPlatforms.includes(runtimePlatform)) {
|
55
|
+
const [os, cpu] = runtimePlatform.split('-');
|
56
|
+
const libc = os.endsWith('musl') ? ' --libc=musl' : '';
|
57
|
+
help.push(
|
58
|
+
'- Ensure optional dependencies can be installed:',
|
59
|
+
' npm install --include=optional sharp',
|
60
|
+
' yarn add sharp --ignore-engines',
|
61
|
+
'- Ensure your package manager supports multi-platform installation:',
|
62
|
+
' See https://sharp.pixelplumbing.com/install#cross-platform',
|
63
|
+
'- Add platform-specific dependencies:',
|
64
|
+
` npm install --os=${os.replace('musl', '')}${libc} --cpu=${cpu} @revizly/sharp`
|
65
|
+
);
|
66
|
+
} else {
|
67
|
+
help.push(
|
68
|
+
`- Manually install libvips >= ${minimumLibvipsVersion}`,
|
69
|
+
'- Add experimental WebAssembly-based dependencies:',
|
70
|
+
' npm install --cpu=wasm32 sharp',
|
71
|
+
' npm install @revizly/sharp-wasm32'
|
72
|
+
);
|
73
|
+
}
|
74
|
+
if (isLinux && /(symbol not found|CXXABI_)/i.test(messages)) {
|
75
|
+
try {
|
76
|
+
const { engines } = require(`@revizly/sharp-libvips-${runtimePlatform}/package`);
|
77
|
+
|
78
|
+
const libcFound = `${familySync()} ${versionSync()}`;
|
79
|
+
const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
|
80
|
+
help.push(
|
81
|
+
'- Update your OS:',
|
82
|
+
` Found ${libcFound}`,
|
83
|
+
` Requires ${libcRequires}`
|
84
|
+
);
|
85
|
+
} catch (errEngines) {}
|
86
|
+
}
|
87
|
+
if (isLinux && /\/snap\/core[0-9]{2}/.test(messages)) {
|
88
|
+
help.push(
|
89
|
+
'- Remove the Node.js Snap, which does not support native modules',
|
90
|
+
' snap remove node'
|
91
|
+
);
|
92
|
+
}
|
93
|
+
if (isMacOs && /Incompatible library version/.test(messages)) {
|
94
|
+
help.push(
|
95
|
+
'- Update Homebrew:',
|
96
|
+
' brew update && brew upgrade vips'
|
97
|
+
);
|
98
|
+
}
|
99
|
+
if (errors.some(err => err.code === 'ERR_DLOPEN_DISABLED')) {
|
100
|
+
help.push('- Run Node.js without using the --no-addons flag');
|
101
|
+
}
|
102
|
+
// Link to installation docs
|
103
|
+
if (isWindows && /The specified procedure could not be found/.test(messages)) {
|
104
|
+
help.push(
|
105
|
+
'- Using the canvas package on Windows?',
|
106
|
+
' See https://sharp.pixelplumbing.com/install#canvas-and-windows',
|
107
|
+
'- Check for outdated versions of sharp in the dependency tree:',
|
108
|
+
' npm ls sharp'
|
109
|
+
);
|
110
|
+
}
|
111
|
+
help.push(
|
112
|
+
'- Consult the installation documentation:',
|
113
|
+
' See https://sharp.pixelplumbing.com/install'
|
114
|
+
);
|
115
|
+
throw new Error(help.join('\n'));
|
116
|
+
}
|