@revizly/sharp 0.34.0-revizly2 → 0.34.0-revizly4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/channel.js +1 -1
- package/lib/composite.js +3 -2
- package/lib/constructor.js +25 -2
- package/lib/index.d.ts +122 -36
- package/lib/input.js +83 -13
- package/lib/operation.js +42 -24
- package/lib/output.js +0 -4
- package/lib/resize.js +1 -1
- package/package.json +14 -14
- package/src/common.cc +32 -15
- package/src/common.h +17 -8
- package/src/metadata.cc +10 -1
- package/src/operations.cc +9 -9
- package/src/pipeline.cc +80 -22
- package/src/pipeline.h +1 -2
- package/src/stats.cc +1 -1
- package/src/utilities.cc +3 -3
package/lib/channel.js
CHANGED
@@ -16,7 +16,7 @@ const bool = {
|
|
16
16
|
};
|
17
17
|
|
18
18
|
/**
|
19
|
-
* Remove alpha
|
19
|
+
* Remove alpha channels, if any. This is a no-op if the image does not have an alpha channel.
|
20
20
|
*
|
21
21
|
* See also {@link /api-operation#flatten|flatten}.
|
22
22
|
*
|
package/lib/composite.js
CHANGED
@@ -46,8 +46,8 @@ const blend = {
|
|
46
46
|
* The images to composite must be the same size or smaller than the processed image.
|
47
47
|
* If both `top` and `left` options are provided, they take precedence over `gravity`.
|
48
48
|
*
|
49
|
-
*
|
50
|
-
* will always be applied to the input image before composition.
|
49
|
+
* Other operations in the same processing pipeline (e.g. resize, rotate, flip,
|
50
|
+
* flop, extract) will always be applied to the input image before composition.
|
51
51
|
*
|
52
52
|
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
53
53
|
* `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
@@ -110,6 +110,7 @@ const blend = {
|
|
110
110
|
* @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
|
111
111
|
* @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for Pango markup features like `<span foreground="red">Red!</span>`.
|
112
112
|
* @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
113
|
+
* @param {Boolean} [images[].autoOrient=false] - set to true to use EXIF orientation data, if present, to orient the image.
|
113
114
|
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
|
114
115
|
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
|
115
116
|
* @param {Number} [images[].top] - the pixel offset from the top edge.
|
package/lib/constructor.js
CHANGED
@@ -121,10 +121,25 @@ const debuglog = util.debuglog('sharp');
|
|
121
121
|
* }
|
122
122
|
* }).toFile('text_rgba.png');
|
123
123
|
*
|
124
|
-
* @
|
124
|
+
* @example
|
125
|
+
* // Join four input images as a 2x2 grid with a 4 pixel gutter
|
126
|
+
* const data = await sharp(
|
127
|
+
* [image1, image2, image3, image4],
|
128
|
+
* { join: { across: 2, shim: 4 } }
|
129
|
+
* ).toBuffer();
|
130
|
+
*
|
131
|
+
* @example
|
132
|
+
* // Generate a two-frame animated image from emoji
|
133
|
+
* const images = ['😀', '😛'].map(text => ({
|
134
|
+
* text: { text, width: 64, height: 64, channels: 4, rgba: true }
|
135
|
+
* }));
|
136
|
+
* await sharp(images, { join: { animated: true } }).toFile('out.gif');
|
137
|
+
*
|
138
|
+
* @param {(Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string|Array)} [input] - if present, can be
|
125
139
|
* a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
|
126
140
|
* a TypedArray containing raw pixel image data, or
|
127
141
|
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
142
|
+
* An array of inputs can be provided, and these will be joined together.
|
128
143
|
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
129
144
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
130
145
|
* @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.
|
@@ -132,6 +147,7 @@ const debuglog = util.debuglog('sharp');
|
|
132
147
|
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
|
133
148
|
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
134
149
|
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF).
|
150
|
+
* @param {boolean} [options.autoOrient=false] - Set this to `true` to rotate/flip the image to match EXIF `Orientation`, if any.
|
135
151
|
* @param {boolean} [options.sequentialRead=true] - Set this to `false` to use random access rather than sequential read. Some operations will do this automatically.
|
136
152
|
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
|
137
153
|
* @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
|
@@ -168,6 +184,14 @@ const debuglog = util.debuglog('sharp');
|
|
168
184
|
* @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
|
169
185
|
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
|
170
186
|
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none'.
|
187
|
+
* @param {Object} [options.join] - describes how an array of input images should be joined.
|
188
|
+
* @param {number} [options.join.across=1] - number of images to join horizontally.
|
189
|
+
* @param {boolean} [options.join.animated=false] - set this to `true` to join the images as an animated image.
|
190
|
+
* @param {number} [options.join.shim=0] - number of pixels to insert between joined images.
|
191
|
+
* @param {string|Object} [options.join.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
192
|
+
* @param {string} [options.join.halign='left'] - horizontal alignment style for images joined horizontally (`'left'`, `'centre'`, `'center'`, `'right'`).
|
193
|
+
* @param {string} [options.join.valign='top'] - vertical alignment style for images joined vertically (`'top'`, `'centre'`, `'center'`, `'bottom'`).
|
194
|
+
*
|
171
195
|
* @returns {Sharp}
|
172
196
|
* @throws {Error} Invalid parameters
|
173
197
|
*/
|
@@ -194,7 +218,6 @@ const Sharp = function (input, options) {
|
|
194
218
|
canvas: 'crop',
|
195
219
|
position: 0,
|
196
220
|
resizeBackground: [0, 0, 0, 255],
|
197
|
-
useExifOrientation: false,
|
198
221
|
angle: 0,
|
199
222
|
rotationAngle: 0,
|
200
223
|
rotationBackground: [0, 0, 0, 255],
|
package/lib/index.d.ts
CHANGED
@@ -40,19 +40,7 @@ import { Duplex } from 'stream';
|
|
40
40
|
*/
|
41
41
|
declare function sharp(options?: sharp.SharpOptions): sharp.Sharp;
|
42
42
|
declare function sharp(
|
43
|
-
input?:
|
44
|
-
| Buffer
|
45
|
-
| ArrayBuffer
|
46
|
-
| Uint8Array
|
47
|
-
| Uint8ClampedArray
|
48
|
-
| Int8Array
|
49
|
-
| Uint16Array
|
50
|
-
| Int16Array
|
51
|
-
| Uint32Array
|
52
|
-
| Int32Array
|
53
|
-
| Float32Array
|
54
|
-
| Float64Array
|
55
|
-
| string,
|
43
|
+
input?: sharp.SharpInput | Array<sharp.SharpInput>,
|
56
44
|
options?: sharp.SharpOptions,
|
57
45
|
): sharp.Sharp;
|
58
46
|
|
@@ -62,33 +50,35 @@ declare namespace sharp {
|
|
62
50
|
|
63
51
|
/** An Object containing the version numbers of sharp, libvips and its dependencies. */
|
64
52
|
const versions: {
|
65
|
-
|
53
|
+
aom?: string | undefined;
|
54
|
+
archive?: string | undefined;
|
66
55
|
cairo?: string | undefined;
|
67
|
-
|
56
|
+
cgif?: string | undefined;
|
68
57
|
exif?: string | undefined;
|
69
58
|
expat?: string | undefined;
|
70
59
|
ffi?: string | undefined;
|
71
60
|
fontconfig?: string | undefined;
|
72
61
|
freetype?: string | undefined;
|
73
|
-
|
74
|
-
gif?: string | undefined;
|
62
|
+
fribidi?: string | undefined;
|
75
63
|
glib?: string | undefined;
|
76
|
-
gsf?: string | undefined;
|
77
64
|
harfbuzz?: string | undefined;
|
78
|
-
|
65
|
+
heif?: string | undefined;
|
66
|
+
highway?: string | undefined;
|
67
|
+
imagequant?: string | undefined;
|
79
68
|
lcms?: string | undefined;
|
80
|
-
|
69
|
+
mozjpeg?: string | undefined;
|
81
70
|
pango?: string | undefined;
|
82
71
|
pixman?: string | undefined;
|
83
72
|
png?: string | undefined;
|
84
|
-
|
85
|
-
|
73
|
+
"proxy-libintl"?: string | undefined;
|
74
|
+
rsvg?: string | undefined;
|
75
|
+
sharp: string;
|
76
|
+
spng?: string | undefined;
|
86
77
|
tiff?: string | undefined;
|
78
|
+
vips: string;
|
87
79
|
webp?: string | undefined;
|
88
|
-
avif?: string | undefined;
|
89
|
-
heif?: string | undefined;
|
90
80
|
xml?: string | undefined;
|
91
|
-
zlib?: string | undefined;
|
81
|
+
"zlib-ng"?: string | undefined;
|
92
82
|
};
|
93
83
|
|
94
84
|
/** An Object containing the available interpolators and their proper values */
|
@@ -364,24 +354,72 @@ declare namespace sharp {
|
|
364
354
|
//#region Operation functions
|
365
355
|
|
366
356
|
/**
|
367
|
-
* Rotate the output image by either an explicit angle
|
357
|
+
* Rotate the output image by either an explicit angle
|
358
|
+
* or auto-orient based on the EXIF `Orientation` tag.
|
368
359
|
*
|
369
|
-
* If an angle is provided, it is converted to a valid positive degree rotation.
|
360
|
+
* If an angle is provided, it is converted to a valid positive degree rotation.
|
361
|
+
* For example, `-450` will produce a 270 degree rotation.
|
370
362
|
*
|
371
|
-
* When rotating by an angle other than a multiple of 90,
|
363
|
+
* When rotating by an angle other than a multiple of 90,
|
364
|
+
* the background colour can be provided with the `background` option.
|
372
365
|
*
|
373
|
-
* If no angle is provided, it is determined from the EXIF data.
|
366
|
+
* If no angle is provided, it is determined from the EXIF data.
|
367
|
+
* Mirroring is supported and may infer the use of a flip operation.
|
374
368
|
*
|
375
|
-
* The use of rotate
|
369
|
+
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
|
376
370
|
*
|
377
|
-
*
|
378
|
-
*
|
379
|
-
*
|
371
|
+
* Only one rotation can occur per pipeline (aside from an initial call without
|
372
|
+
* arguments to orient via EXIF data). Previous calls to `rotate` in the same
|
373
|
+
* pipeline will be ignored.
|
374
|
+
*
|
375
|
+
* Multi-page images can only be rotated by 180 degrees.
|
376
|
+
*
|
377
|
+
* Method order is important when rotating, resizing and/or extracting regions,
|
378
|
+
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
379
|
+
*
|
380
|
+
* @example
|
381
|
+
* const pipeline = sharp()
|
382
|
+
* .rotate()
|
383
|
+
* .resize(null, 200)
|
384
|
+
* .toBuffer(function (err, outputBuffer, info) {
|
385
|
+
* // outputBuffer contains 200px high JPEG image data,
|
386
|
+
* // auto-rotated using EXIF Orientation tag
|
387
|
+
* // info.width and info.height contain the dimensions of the resized image
|
388
|
+
* });
|
389
|
+
* readableStream.pipe(pipeline);
|
390
|
+
*
|
391
|
+
* @example
|
392
|
+
* const rotateThenResize = await sharp(input)
|
393
|
+
* .rotate(90)
|
394
|
+
* .resize({ width: 16, height: 8, fit: 'fill' })
|
395
|
+
* .toBuffer();
|
396
|
+
* const resizeThenRotate = await sharp(input)
|
397
|
+
* .resize({ width: 16, height: 8, fit: 'fill' })
|
398
|
+
* .rotate(90)
|
399
|
+
* .toBuffer();
|
400
|
+
*
|
401
|
+
* @param {number} [angle=auto] angle of rotation.
|
402
|
+
* @param {Object} [options] - if present, is an Object with optional attributes.
|
403
|
+
* @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.
|
404
|
+
* @returns {Sharp}
|
380
405
|
* @throws {Error} Invalid parameters
|
381
|
-
* @returns A sharp instance that can be used to chain operations
|
382
406
|
*/
|
383
407
|
rotate(angle?: number, options?: RotateOptions): Sharp;
|
384
408
|
|
409
|
+
/**
|
410
|
+
* Alias for calling `rotate()` with no arguments, which orients the image based
|
411
|
+
* on EXIF orientsion.
|
412
|
+
*
|
413
|
+
* This operation is aliased to emphasize its purpose, helping to remove any
|
414
|
+
* confusion between rotation and orientation.
|
415
|
+
*
|
416
|
+
* @example
|
417
|
+
* const output = await sharp(input).autoOrient().toBuffer();
|
418
|
+
*
|
419
|
+
* @returns {Sharp}
|
420
|
+
*/
|
421
|
+
autoOrient(): Sharp
|
422
|
+
|
385
423
|
/**
|
386
424
|
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
387
425
|
* The use of flip implies the removal of the EXIF Orientation tag, if any.
|
@@ -799,8 +837,6 @@ declare namespace sharp {
|
|
799
837
|
* Use tile-based deep zoom (image pyramid) output.
|
800
838
|
* Set the format and options for tile images via the toFormat, jpeg, png or webp functions.
|
801
839
|
* Use a .zip or .szi file extension with toFile to write to a compressed archive file format.
|
802
|
-
*
|
803
|
-
* Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
|
804
840
|
* @param tile tile options
|
805
841
|
* @throws {Error} Invalid options
|
806
842
|
* @returns A sharp instance that can be used to chain operations
|
@@ -899,7 +935,27 @@ declare namespace sharp {
|
|
899
935
|
//#endregion
|
900
936
|
}
|
901
937
|
|
938
|
+
type SharpInput = Buffer
|
939
|
+
| ArrayBuffer
|
940
|
+
| Uint8Array
|
941
|
+
| Uint8ClampedArray
|
942
|
+
| Int8Array
|
943
|
+
| Uint16Array
|
944
|
+
| Int16Array
|
945
|
+
| Uint32Array
|
946
|
+
| Int32Array
|
947
|
+
| Float32Array
|
948
|
+
| Float64Array
|
949
|
+
| string;
|
950
|
+
|
902
951
|
interface SharpOptions {
|
952
|
+
/**
|
953
|
+
* Auto-orient based on the EXIF `Orientation` tag, if present.
|
954
|
+
* Mirroring is supported and may infer the use of a flip operation.
|
955
|
+
*
|
956
|
+
* Using this option will remove the EXIF `Orientation` tag, if any.
|
957
|
+
*/
|
958
|
+
autoOrient?: boolean;
|
903
959
|
/**
|
904
960
|
* When to abort processing of invalid pixel data, one of (in order of sensitivity):
|
905
961
|
* 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning')
|
@@ -945,6 +1001,8 @@ declare namespace sharp {
|
|
945
1001
|
create?: Create | undefined;
|
946
1002
|
/** Describes a new text image to be created. */
|
947
1003
|
text?: CreateText | undefined;
|
1004
|
+
/** Describes how array of input images should be joined. */
|
1005
|
+
join?: Join | undefined;
|
948
1006
|
}
|
949
1007
|
|
950
1008
|
interface CacheOptions {
|
@@ -1025,6 +1083,21 @@ declare namespace sharp {
|
|
1025
1083
|
wrap?: TextWrap;
|
1026
1084
|
}
|
1027
1085
|
|
1086
|
+
interface Join {
|
1087
|
+
/** Number of images per row. */
|
1088
|
+
across?: number | undefined;
|
1089
|
+
/** Treat input as frames of an animated image. */
|
1090
|
+
animated?: boolean | undefined;
|
1091
|
+
/** Space between images, in pixels. */
|
1092
|
+
shim?: number | undefined;
|
1093
|
+
/** Background colour. */
|
1094
|
+
background?: Colour | Color | undefined;
|
1095
|
+
/** Horizontal alignment. */
|
1096
|
+
halign?: HorizontalAlignment | undefined;
|
1097
|
+
/** Vertical alignment. */
|
1098
|
+
valign?: VerticalAlignment | undefined;
|
1099
|
+
}
|
1100
|
+
|
1028
1101
|
interface ExifDir {
|
1029
1102
|
[k: string]: string;
|
1030
1103
|
}
|
@@ -1064,6 +1137,13 @@ declare namespace sharp {
|
|
1064
1137
|
width?: number | undefined;
|
1065
1138
|
/** Number of pixels high (EXIF orientation is not taken into consideration) */
|
1066
1139
|
height?: number | undefined;
|
1140
|
+
/** Any changed metadata after the image orientation is applied. */
|
1141
|
+
autoOrient: {
|
1142
|
+
/** Number of pixels wide (EXIF orientation is taken into consideration) */
|
1143
|
+
width: number;
|
1144
|
+
/** Number of pixels high (EXIF orientation is taken into consideration) */
|
1145
|
+
height: number;
|
1146
|
+
};
|
1067
1147
|
/** Name of colour space interpretation */
|
1068
1148
|
space?: keyof ColourspaceEnum | undefined;
|
1069
1149
|
/** Number of bands e.g. 3 for sRGB, 4 for CMYK */
|
@@ -1514,6 +1594,8 @@ declare namespace sharp {
|
|
1514
1594
|
failOn?: FailOnOptions | undefined;
|
1515
1595
|
/** see sharp() constructor, (optional, default 268402689) */
|
1516
1596
|
limitInputPixels?: number | boolean | undefined;
|
1597
|
+
/** see sharp() constructor, (optional, default false) */
|
1598
|
+
autoOrient?: boolean | undefined;
|
1517
1599
|
}
|
1518
1600
|
|
1519
1601
|
interface TileOptions {
|
@@ -1654,6 +1736,10 @@ declare namespace sharp {
|
|
1654
1736
|
|
1655
1737
|
type TextWrap = 'word' | 'char' | 'word-char' | 'none';
|
1656
1738
|
|
1739
|
+
type HorizontalAlignment = 'left' | 'centre' | 'center' | 'right';
|
1740
|
+
|
1741
|
+
type VerticalAlignment = 'top' | 'centre' | 'center' | 'bottom';
|
1742
|
+
|
1657
1743
|
type TileContainer = 'fs' | 'zip';
|
1658
1744
|
|
1659
1745
|
type TileLayout = 'dz' | 'iiif' | 'iiif3' | 'zoomify' | 'google';
|
package/lib/input.js
CHANGED
@@ -14,9 +14,13 @@ const sharp = require('./sharp');
|
|
14
14
|
*/
|
15
15
|
const align = {
|
16
16
|
left: 'low',
|
17
|
+
top: 'low',
|
18
|
+
low: 'low',
|
17
19
|
center: 'centre',
|
18
20
|
centre: 'centre',
|
19
|
-
right: 'high'
|
21
|
+
right: 'high',
|
22
|
+
bottom: 'high',
|
23
|
+
high: 'high'
|
20
24
|
};
|
21
25
|
|
22
26
|
/**
|
@@ -24,9 +28,9 @@ const align = {
|
|
24
28
|
* @private
|
25
29
|
*/
|
26
30
|
function _inputOptionsFromObject (obj) {
|
27
|
-
const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground } = obj;
|
28
|
-
return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground].some(is.defined)
|
29
|
-
? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground }
|
31
|
+
const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient } = obj;
|
32
|
+
return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient].some(is.defined)
|
33
|
+
? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd, pdfBackground, autoOrient }
|
30
34
|
: undefined;
|
31
35
|
}
|
32
36
|
|
@@ -36,6 +40,7 @@ function _inputOptionsFromObject (obj) {
|
|
36
40
|
*/
|
37
41
|
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
38
42
|
const inputDescriptor = {
|
43
|
+
autoOrient: false,
|
39
44
|
failOn: 'warning',
|
40
45
|
limitInputPixels: Math.pow(0x3FFF, 2),
|
41
46
|
ignoreIcc: false,
|
@@ -71,6 +76,18 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|
71
76
|
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
|
72
77
|
// Stream without options
|
73
78
|
inputDescriptor.buffer = [];
|
79
|
+
} else if (Array.isArray(input)) {
|
80
|
+
if (input.length > 1) {
|
81
|
+
// Join images together
|
82
|
+
if (!this.options.joining) {
|
83
|
+
this.options.joining = true;
|
84
|
+
this.options.join = input.map(i => this._createInputDescriptor(i));
|
85
|
+
} else {
|
86
|
+
throw new Error('Recursive join is unsupported');
|
87
|
+
}
|
88
|
+
} else {
|
89
|
+
throw new Error('Expected at least two images to join');
|
90
|
+
}
|
74
91
|
} else {
|
75
92
|
throw new Error(`Unsupported input '${input}' of type ${typeof input}${
|
76
93
|
is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
|
@@ -93,6 +110,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|
93
110
|
throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
|
94
111
|
}
|
95
112
|
}
|
113
|
+
// autoOrient
|
114
|
+
if (is.defined(inputOptions.autoOrient)) {
|
115
|
+
if (is.bool(inputOptions.autoOrient)) {
|
116
|
+
inputDescriptor.autoOrient = inputOptions.autoOrient;
|
117
|
+
} else {
|
118
|
+
throw is.invalidParameterError('autoOrient', 'boolean', inputOptions.autoOrient);
|
119
|
+
}
|
120
|
+
}
|
96
121
|
// Density
|
97
122
|
if (is.defined(inputOptions.density)) {
|
98
123
|
if (is.inRange(inputOptions.density, 1, 100000)) {
|
@@ -360,6 +385,57 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|
360
385
|
throw new Error('Expected a valid string to create an image with text.');
|
361
386
|
}
|
362
387
|
}
|
388
|
+
// Join images together
|
389
|
+
if (is.defined(inputOptions.join)) {
|
390
|
+
if (is.defined(this.options.join)) {
|
391
|
+
if (is.defined(inputOptions.join.animated)) {
|
392
|
+
if (is.bool(inputOptions.join.animated)) {
|
393
|
+
inputDescriptor.joinAnimated = inputOptions.join.animated;
|
394
|
+
} else {
|
395
|
+
throw is.invalidParameterError('join.animated', 'boolean', inputOptions.join.animated);
|
396
|
+
}
|
397
|
+
}
|
398
|
+
if (is.defined(inputOptions.join.across)) {
|
399
|
+
if (is.integer(inputOptions.join.across) && is.inRange(inputOptions.join.across, 1, 1000000)) {
|
400
|
+
inputDescriptor.joinAcross = inputOptions.join.across;
|
401
|
+
} else {
|
402
|
+
throw is.invalidParameterError('join.across', 'integer between 1 and 100000', inputOptions.join.across);
|
403
|
+
}
|
404
|
+
}
|
405
|
+
if (is.defined(inputOptions.join.shim)) {
|
406
|
+
if (is.integer(inputOptions.join.shim) && is.inRange(inputOptions.join.shim, 0, 1000000)) {
|
407
|
+
inputDescriptor.joinShim = inputOptions.join.shim;
|
408
|
+
} else {
|
409
|
+
throw is.invalidParameterError('join.shim', 'integer between 0 and 100000', inputOptions.join.shim);
|
410
|
+
}
|
411
|
+
}
|
412
|
+
if (is.defined(inputOptions.join.background)) {
|
413
|
+
const background = color(inputOptions.join.background);
|
414
|
+
inputDescriptor.joinBackground = [
|
415
|
+
background.red(),
|
416
|
+
background.green(),
|
417
|
+
background.blue(),
|
418
|
+
Math.round(background.alpha() * 255)
|
419
|
+
];
|
420
|
+
}
|
421
|
+
if (is.defined(inputOptions.join.halign)) {
|
422
|
+
if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) {
|
423
|
+
inputDescriptor.joinHalign = this.constructor.align[inputOptions.join.halign];
|
424
|
+
} else {
|
425
|
+
throw is.invalidParameterError('join.halign', 'valid alignment', inputOptions.join.halign);
|
426
|
+
}
|
427
|
+
}
|
428
|
+
if (is.defined(inputOptions.join.valign)) {
|
429
|
+
if (is.string(inputOptions.join.valign) && is.string(this.constructor.align[inputOptions.join.valign])) {
|
430
|
+
inputDescriptor.joinValign = this.constructor.align[inputOptions.join.valign];
|
431
|
+
} else {
|
432
|
+
throw is.invalidParameterError('join.valign', 'valid alignment', inputOptions.join.valign);
|
433
|
+
}
|
434
|
+
}
|
435
|
+
} else {
|
436
|
+
throw new Error('Expected input to be an array of images to join');
|
437
|
+
}
|
438
|
+
}
|
363
439
|
} else if (is.defined(inputOptions)) {
|
364
440
|
throw new Error('Invalid input options ' + inputOptions);
|
365
441
|
}
|
@@ -475,15 +551,9 @@ function _isStreamInput () {
|
|
475
551
|
* });
|
476
552
|
*
|
477
553
|
* @example
|
478
|
-
* //
|
479
|
-
*
|
480
|
-
* const
|
481
|
-
*
|
482
|
-
* function getNormalSize({ width, height, orientation }) {
|
483
|
-
* return (orientation || 0) >= 5
|
484
|
-
* ? { width: height, height: width }
|
485
|
-
* : { width, height };
|
486
|
-
* }
|
554
|
+
* // Get dimensions taking EXIF Orientation into account.
|
555
|
+
* const { autoOrient } = await sharp(input).metadata();
|
556
|
+
* const { width, height } = autoOrient;
|
487
557
|
*
|
488
558
|
* @param {Function} [callback] - called with the arguments `(err, metadata)`
|
489
559
|
* @returns {Promise<Object>|Sharp}
|
package/lib/operation.js
CHANGED
@@ -18,22 +18,19 @@ const vipsPrecision = {
|
|
18
18
|
};
|
19
19
|
|
20
20
|
/**
|
21
|
-
* Rotate the output image
|
22
|
-
* or auto-orient based on the EXIF `Orientation` tag.
|
21
|
+
* Rotate the output image.
|
23
22
|
*
|
24
|
-
*
|
23
|
+
* The provided angle is converted to a valid positive degree rotation.
|
25
24
|
* For example, `-450` will produce a 270 degree rotation.
|
26
25
|
*
|
27
26
|
* When rotating by an angle other than a multiple of 90,
|
28
27
|
* the background colour can be provided with the `background` option.
|
29
28
|
*
|
30
|
-
*
|
31
|
-
* Mirroring is supported and may infer the use of a flip operation.
|
32
|
-
*
|
33
|
-
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
|
29
|
+
* For backwards compatibility, if no angle is provided, `.autoOrient()` will be called.
|
34
30
|
*
|
35
|
-
* Only one rotation can occur per pipeline
|
36
|
-
* Previous calls to `rotate` in the same
|
31
|
+
* Only one rotation can occur per pipeline (aside from an initial call without
|
32
|
+
* arguments to orient via EXIF data). Previous calls to `rotate` in the same
|
33
|
+
* pipeline will be ignored.
|
37
34
|
*
|
38
35
|
* Multi-page images can only be rotated by 180 degrees.
|
39
36
|
*
|
@@ -41,17 +38,6 @@ const vipsPrecision = {
|
|
41
38
|
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
|
42
39
|
*
|
43
40
|
* @example
|
44
|
-
* const pipeline = sharp()
|
45
|
-
* .rotate()
|
46
|
-
* .resize(null, 200)
|
47
|
-
* .toBuffer(function (err, outputBuffer, info) {
|
48
|
-
* // outputBuffer contains 200px high JPEG image data,
|
49
|
-
* // auto-rotated using EXIF Orientation tag
|
50
|
-
* // info.width and info.height contain the dimensions of the resized image
|
51
|
-
* });
|
52
|
-
* readableStream.pipe(pipeline);
|
53
|
-
*
|
54
|
-
* @example
|
55
41
|
* const rotateThenResize = await sharp(input)
|
56
42
|
* .rotate(90)
|
57
43
|
* .resize({ width: 16, height: 8, fit: 'fill' })
|
@@ -68,12 +54,15 @@ const vipsPrecision = {
|
|
68
54
|
* @throws {Error} Invalid parameters
|
69
55
|
*/
|
70
56
|
function rotate (angle, options) {
|
71
|
-
if (
|
57
|
+
if (!is.defined(angle)) {
|
58
|
+
return this.autoOrient();
|
59
|
+
}
|
60
|
+
if (this.options.angle || this.options.rotationAngle) {
|
72
61
|
this.options.debuglog('ignoring previous rotate options');
|
62
|
+
this.options.angle = 0;
|
63
|
+
this.options.rotationAngle = 0;
|
73
64
|
}
|
74
|
-
if (
|
75
|
-
this.options.useExifOrientation = true;
|
76
|
-
} else if (is.integer(angle) && !(angle % 90)) {
|
65
|
+
if (is.integer(angle) && !(angle % 90)) {
|
77
66
|
this.options.angle = angle;
|
78
67
|
} else if (is.number(angle)) {
|
79
68
|
this.options.rotationAngle = angle;
|
@@ -92,6 +81,34 @@ function rotate (angle, options) {
|
|
92
81
|
return this;
|
93
82
|
}
|
94
83
|
|
84
|
+
/**
|
85
|
+
* Auto-orient based on the EXIF `Orientation` tag, then remove the tag.
|
86
|
+
* Mirroring is supported and may infer the use of a flip operation.
|
87
|
+
*
|
88
|
+
* Previous or subsequent use of `rotate(angle)` and either `flip()` or `flop()`
|
89
|
+
* will logically occur after auto-orientation, regardless of call order.
|
90
|
+
*
|
91
|
+
* @example
|
92
|
+
* const output = await sharp(input).autoOrient().toBuffer();
|
93
|
+
*
|
94
|
+
* @example
|
95
|
+
* const pipeline = sharp()
|
96
|
+
* .autoOrient()
|
97
|
+
* .resize(null, 200)
|
98
|
+
* .toBuffer(function (err, outputBuffer, info) {
|
99
|
+
* // outputBuffer contains 200px high JPEG image data,
|
100
|
+
* // auto-oriented using EXIF Orientation tag
|
101
|
+
* // info.width and info.height contain the dimensions of the resized image
|
102
|
+
* });
|
103
|
+
* readableStream.pipe(pipeline);
|
104
|
+
*
|
105
|
+
* @returns {Sharp}
|
106
|
+
*/
|
107
|
+
function autoOrient () {
|
108
|
+
this.options.input.autoOrient = true;
|
109
|
+
return this;
|
110
|
+
}
|
111
|
+
|
95
112
|
/**
|
96
113
|
* Mirror the image vertically (up-down) about the x-axis.
|
97
114
|
* This always occurs before rotation, if any.
|
@@ -935,6 +952,7 @@ function modulate (options) {
|
|
935
952
|
*/
|
936
953
|
module.exports = function (Sharp) {
|
937
954
|
Object.assign(Sharp.prototype, {
|
955
|
+
autoOrient,
|
938
956
|
rotate,
|
939
957
|
flip,
|
940
958
|
flop,
|
package/lib/output.js
CHANGED
@@ -1232,10 +1232,6 @@ function raw (options) {
|
|
1232
1232
|
*
|
1233
1233
|
* The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
|
1234
1234
|
*
|
1235
|
-
* Requires libvips compiled with support for libgsf.
|
1236
|
-
* The prebuilt binaries do not include this - see
|
1237
|
-
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
1238
|
-
*
|
1239
1235
|
* @example
|
1240
1236
|
* sharp('input.tiff')
|
1241
1237
|
* .png()
|
package/lib/resize.js
CHANGED
@@ -107,7 +107,7 @@ const mapFitToCanvas = {
|
|
107
107
|
* @private
|
108
108
|
*/
|
109
109
|
function isRotationExpected (options) {
|
110
|
-
return (options.angle % 360) !== 0 || options.
|
110
|
+
return (options.angle % 360) !== 0 || options.input.autoOrient === true || options.rotationAngle !== 0;
|
111
111
|
}
|
112
112
|
|
113
113
|
/**
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@revizly/sharp",
|
3
3
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
4
|
-
"version": "0.34.0-
|
4
|
+
"version": "0.34.0-revizly4",
|
5
5
|
"author": "Lovell Fuller <npm@lovell.info>",
|
6
6
|
"homepage": "https://sharp.pixelplumbing.com",
|
7
7
|
"contributors": [
|
@@ -102,9 +102,9 @@
|
|
102
102
|
"test-types": "tsd",
|
103
103
|
"package-from-local-build": "node npm/from-local-build",
|
104
104
|
"package-from-github-release": "node npm/from-github-release",
|
105
|
-
"docs-build": "node docs/build
|
106
|
-
"docs-serve": "cd docs &&
|
107
|
-
"docs-publish": ""
|
105
|
+
"docs-build": "node docs/build.mjs",
|
106
|
+
"docs-serve": "cd docs && npm start",
|
107
|
+
"docs-publish": "cd docs && npm run build && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
|
108
108
|
},
|
109
109
|
"type": "commonjs",
|
110
110
|
"main": "lib/index.js",
|
@@ -142,28 +142,28 @@
|
|
142
142
|
"semver": "^7.6.3"
|
143
143
|
},
|
144
144
|
"optionalDependencies": {
|
145
|
-
"@revizly/sharp-libvips-linux-arm64": "1.0.
|
146
|
-
"@revizly/sharp-libvips-linux-x64": "1.0.
|
147
|
-
"@revizly/sharp-linux-arm64": "0.
|
148
|
-
"@revizly/sharp-linux-x64": "0.
|
145
|
+
"@revizly/sharp-libvips-linux-arm64": "1.0.10",
|
146
|
+
"@revizly/sharp-libvips-linux-x64": "1.0.10",
|
147
|
+
"@revizly/sharp-linux-arm64": "0.34.0-revizly2",
|
148
|
+
"@revizly/sharp-linux-x64": "0.34.0-revizly2"
|
149
149
|
},
|
150
150
|
"devDependencies": {
|
151
151
|
"@emnapi/runtime": "^1.3.1",
|
152
|
-
"@revizly/sharp-libvips-dev": "1.0.
|
152
|
+
"@revizly/sharp-libvips-dev": "1.0.10",
|
153
153
|
"@types/node": "*",
|
154
154
|
"cc": "^3.0.1",
|
155
155
|
"emnapi": "^1.3.1",
|
156
|
-
"exif-reader": "^2.0.
|
156
|
+
"exif-reader": "^2.0.2",
|
157
157
|
"extract-zip": "^2.0.1",
|
158
158
|
"icc": "^3.0.0",
|
159
|
-
"jsdoc-to-markdown": "^9.
|
159
|
+
"jsdoc-to-markdown": "^9.1.1",
|
160
160
|
"license-checker": "^25.0.1",
|
161
|
-
"mocha": "^
|
162
|
-
"node-addon-api": "^8.
|
161
|
+
"mocha": "^11.0.1",
|
162
|
+
"node-addon-api": "^8.3.0",
|
163
163
|
"nyc": "^17.1.0",
|
164
164
|
"prebuild": "^13.0.1",
|
165
165
|
"semistandard": "^17.0.0",
|
166
|
-
"tar-fs": "
|
166
|
+
"tar-fs": "3.0.6",
|
167
167
|
"tsd": "^0.31.2"
|
168
168
|
},
|
169
169
|
"license": "Apache-2.0",
|
package/src/common.cc
CHANGED
@@ -160,12 +160,34 @@ namespace sharp {
|
|
160
160
|
descriptor->textWrap = AttrAsEnum<VipsTextWrap>(input, "textWrap", VIPS_TYPE_TEXT_WRAP);
|
161
161
|
}
|
162
162
|
}
|
163
|
+
// Join images together
|
164
|
+
if (HasAttr(input, "joinAnimated")) {
|
165
|
+
descriptor->joinAnimated = AttrAsBool(input, "joinAnimated");
|
166
|
+
}
|
167
|
+
if (HasAttr(input, "joinAcross")) {
|
168
|
+
descriptor->joinAcross = AttrAsUint32(input, "joinAcross");
|
169
|
+
}
|
170
|
+
if (HasAttr(input, "joinShim")) {
|
171
|
+
descriptor->joinShim = AttrAsUint32(input, "joinShim");
|
172
|
+
}
|
173
|
+
if (HasAttr(input, "joinBackground")) {
|
174
|
+
descriptor->joinBackground = AttrAsVectorOfDouble(input, "joinBackground");
|
175
|
+
}
|
176
|
+
if (HasAttr(input, "joinHalign")) {
|
177
|
+
descriptor->joinHalign = AttrAsEnum<VipsAlign>(input, "joinHalign", VIPS_TYPE_ALIGN);
|
178
|
+
}
|
179
|
+
if (HasAttr(input, "joinValign")) {
|
180
|
+
descriptor->joinValign = AttrAsEnum<VipsAlign>(input, "joinValign", VIPS_TYPE_ALIGN);
|
181
|
+
}
|
163
182
|
// Limit input images to a given number of pixels, where pixels = width * height
|
164
183
|
descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
|
165
|
-
|
166
|
-
|
184
|
+
if (HasAttr(input, "access")) {
|
185
|
+
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
186
|
+
}
|
167
187
|
// Remove safety features and allow unlimited input
|
168
188
|
descriptor->unlimited = AttrAsBool(input, "unlimited");
|
189
|
+
// Use the EXIF orientation to auto orient the image
|
190
|
+
descriptor->autoOrient = AttrAsBool(input, "autoOrient");
|
169
191
|
return descriptor;
|
170
192
|
}
|
171
193
|
|
@@ -248,6 +270,7 @@ namespace sharp {
|
|
248
270
|
case ImageType::FITS: id = "fits"; break;
|
249
271
|
case ImageType::EXR: id = "exr"; break;
|
250
272
|
case ImageType::JXL: id = "jxl"; break;
|
273
|
+
case ImageType::RAD: id = "rad"; break;
|
251
274
|
case ImageType::VIPS: id = "vips"; break;
|
252
275
|
case ImageType::RAW: id = "raw"; break;
|
253
276
|
case ImageType::UNKNOWN: id = "unknown"; break;
|
@@ -294,6 +317,8 @@ namespace sharp {
|
|
294
317
|
{ "VipsForeignLoadOpenexr", ImageType::EXR },
|
295
318
|
{ "VipsForeignLoadJxlFile", ImageType::JXL },
|
296
319
|
{ "VipsForeignLoadJxlBuffer", ImageType::JXL },
|
320
|
+
{ "VipsForeignLoadRadFile", ImageType::RAD },
|
321
|
+
{ "VipsForeignLoadRadBuffer", ImageType::RAD },
|
297
322
|
{ "VipsForeignLoadVips", ImageType::VIPS },
|
298
323
|
{ "VipsForeignLoadVipsFile", ImageType::VIPS },
|
299
324
|
{ "VipsForeignLoadRaw", ImageType::RAW }
|
@@ -570,14 +595,6 @@ namespace sharp {
|
|
570
595
|
return image;
|
571
596
|
}
|
572
597
|
|
573
|
-
/*
|
574
|
-
Does this image have an alpha channel?
|
575
|
-
Uses colour space interpretation with number of channels to guess this.
|
576
|
-
*/
|
577
|
-
bool HasAlpha(VImage image) {
|
578
|
-
return image.has_alpha();
|
579
|
-
}
|
580
|
-
|
581
598
|
static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
|
582
599
|
std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data);
|
583
600
|
std::string fieldName(field);
|
@@ -986,13 +1003,13 @@ namespace sharp {
|
|
986
1003
|
};
|
987
1004
|
}
|
988
1005
|
// Add alpha channel to alphaColour colour
|
989
|
-
if (colour[3] < 255.0 ||
|
1006
|
+
if (colour[3] < 255.0 || image.has_alpha()) {
|
990
1007
|
alphaColour.push_back(colour[3] * multiplier);
|
991
1008
|
}
|
992
1009
|
// Ensure alphaColour colour uses correct colourspace
|
993
1010
|
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
|
994
1011
|
// Add non-transparent alpha channel, if required
|
995
|
-
if (colour[3] < 255.0 && !
|
1012
|
+
if (colour[3] < 255.0 && !image.has_alpha()) {
|
996
1013
|
image = image.bandjoin(
|
997
1014
|
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
|
998
1015
|
}
|
@@ -1000,10 +1017,10 @@ namespace sharp {
|
|
1000
1017
|
}
|
1001
1018
|
|
1002
1019
|
/*
|
1003
|
-
Removes alpha
|
1020
|
+
Removes alpha channels, if any.
|
1004
1021
|
*/
|
1005
1022
|
VImage RemoveAlpha(VImage image) {
|
1006
|
-
|
1023
|
+
while (image.bands() > 1 && image.has_alpha()) {
|
1007
1024
|
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
|
1008
1025
|
}
|
1009
1026
|
return image;
|
@@ -1013,7 +1030,7 @@ namespace sharp {
|
|
1013
1030
|
Ensures alpha channel, if missing.
|
1014
1031
|
*/
|
1015
1032
|
VImage EnsureAlpha(VImage image, double const value) {
|
1016
|
-
if (!
|
1033
|
+
if (!image.has_alpha()) {
|
1017
1034
|
std::vector<double> alpha;
|
1018
1035
|
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
|
1019
1036
|
image = image.bandjoin_const(alpha);
|
package/src/common.h
CHANGED
@@ -33,6 +33,7 @@ namespace sharp {
|
|
33
33
|
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
|
34
34
|
std::string name;
|
35
35
|
std::string file;
|
36
|
+
bool autoOrient;
|
36
37
|
char *buffer;
|
37
38
|
VipsFailOn failOn;
|
38
39
|
uint64_t limitInputPixels;
|
@@ -70,14 +71,21 @@ namespace sharp {
|
|
70
71
|
int textSpacing;
|
71
72
|
VipsTextWrap textWrap;
|
72
73
|
int textAutofitDpi;
|
74
|
+
bool joinAnimated;
|
75
|
+
int joinAcross;
|
76
|
+
int joinShim;
|
77
|
+
std::vector<double> joinBackground;
|
78
|
+
VipsAlign joinHalign;
|
79
|
+
VipsAlign joinValign;
|
73
80
|
std::vector<double> pdfBackground;
|
74
81
|
|
75
82
|
InputDescriptor():
|
83
|
+
autoOrient(false),
|
76
84
|
buffer(nullptr),
|
77
85
|
failOn(VIPS_FAIL_ON_WARNING),
|
78
86
|
limitInputPixels(0x3FFF * 0x3FFF),
|
79
87
|
unlimited(false),
|
80
|
-
access(
|
88
|
+
access(VIPS_ACCESS_SEQUENTIAL),
|
81
89
|
bufferLength(0),
|
82
90
|
isBuffer(false),
|
83
91
|
density(72.0),
|
@@ -106,6 +114,12 @@ namespace sharp {
|
|
106
114
|
textSpacing(0),
|
107
115
|
textWrap(VIPS_TEXT_WRAP_WORD),
|
108
116
|
textAutofitDpi(0),
|
117
|
+
joinAnimated(false),
|
118
|
+
joinAcross(1),
|
119
|
+
joinShim(0),
|
120
|
+
joinBackground{ 0.0, 0.0, 0.0, 255.0 },
|
121
|
+
joinHalign(VIPS_ALIGN_LOW),
|
122
|
+
joinValign(VIPS_ALIGN_LOW),
|
109
123
|
pdfBackground{ 255.0, 255.0, 255.0, 255.0 } {}
|
110
124
|
};
|
111
125
|
|
@@ -145,6 +159,7 @@ namespace sharp {
|
|
145
159
|
FITS,
|
146
160
|
EXR,
|
147
161
|
JXL,
|
162
|
+
RAD,
|
148
163
|
VIPS,
|
149
164
|
RAW,
|
150
165
|
UNKNOWN,
|
@@ -230,12 +245,6 @@ namespace sharp {
|
|
230
245
|
*/
|
231
246
|
VImage SetProfile(VImage image, std::pair<char*, size_t> icc);
|
232
247
|
|
233
|
-
/*
|
234
|
-
Does this image have an alpha channel?
|
235
|
-
Uses colour space interpretation with number of channels to guess this.
|
236
|
-
*/
|
237
|
-
bool HasAlpha(VImage image);
|
238
|
-
|
239
248
|
/*
|
240
249
|
Remove all EXIF-related image fields.
|
241
250
|
*/
|
@@ -366,7 +375,7 @@ namespace sharp {
|
|
366
375
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
|
367
376
|
|
368
377
|
/*
|
369
|
-
Removes alpha
|
378
|
+
Removes alpha channels, if any.
|
370
379
|
*/
|
371
380
|
VImage RemoveAlpha(VImage image);
|
372
381
|
|
package/src/metadata.cc
CHANGED
@@ -95,7 +95,7 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
95
95
|
baton->background = image.get_array_double("background");
|
96
96
|
}
|
97
97
|
// Derived attributes
|
98
|
-
baton->hasAlpha =
|
98
|
+
baton->hasAlpha = image.has_alpha();
|
99
99
|
baton->orientation = sharp::ExifOrientation(image);
|
100
100
|
// EXIF
|
101
101
|
if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
|
@@ -242,6 +242,15 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
242
242
|
if (baton->orientation > 0) {
|
243
243
|
info.Set("orientation", baton->orientation);
|
244
244
|
}
|
245
|
+
Napi::Object autoOrient = Napi::Object::New(env);
|
246
|
+
info.Set("autoOrient", autoOrient);
|
247
|
+
if (baton->orientation >= 5) {
|
248
|
+
autoOrient.Set("width", baton->height);
|
249
|
+
autoOrient.Set("height", baton->width);
|
250
|
+
} else {
|
251
|
+
autoOrient.Set("width", baton->width);
|
252
|
+
autoOrient.Set("height", baton->height);
|
253
|
+
}
|
245
254
|
if (baton->exifLength > 0) {
|
246
255
|
info.Set("exif", Napi::Buffer<char>::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback));
|
247
256
|
}
|
package/src/operations.cc
CHANGED
@@ -40,7 +40,7 @@ namespace sharp {
|
|
40
40
|
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
41
41
|
}
|
42
42
|
// Apply lookup table
|
43
|
-
if (
|
43
|
+
if (image.has_alpha()) {
|
44
44
|
VImage alpha = image[image.bands() - 1];
|
45
45
|
image = RemoveAlpha(image)
|
46
46
|
.colourspace(VIPS_INTERPRETATION_B_W)
|
@@ -83,7 +83,7 @@ namespace sharp {
|
|
83
83
|
// Scale luminance, join to chroma, convert back to original colourspace
|
84
84
|
VImage normalized = luminance.linear(f, a).bandjoin(chroma).colourspace(typeBeforeNormalize);
|
85
85
|
// Attach original alpha channel, if any
|
86
|
-
if (
|
86
|
+
if (image.has_alpha()) {
|
87
87
|
// Extract original alpha channel
|
88
88
|
VImage alpha = image[image.bands() - 1];
|
89
89
|
// Join alpha channel to normalised image
|
@@ -106,7 +106,7 @@ namespace sharp {
|
|
106
106
|
* Gamma encoding/decoding
|
107
107
|
*/
|
108
108
|
VImage Gamma(VImage image, double const exponent) {
|
109
|
-
if (
|
109
|
+
if (image.has_alpha()) {
|
110
110
|
// Separate alpha channel
|
111
111
|
VImage alpha = image[image.bands() - 1];
|
112
112
|
return RemoveAlpha(image).gamma(VImage::option()->set("exponent", exponent)).bandjoin(alpha);
|
@@ -132,7 +132,7 @@ namespace sharp {
|
|
132
132
|
* Produce the "negative" of the image.
|
133
133
|
*/
|
134
134
|
VImage Negate(VImage image, bool const negateAlpha) {
|
135
|
-
if (
|
135
|
+
if (image.has_alpha() && !negateAlpha) {
|
136
136
|
// Separate alpha channel
|
137
137
|
VImage alpha = image[image.bands() - 1];
|
138
138
|
return RemoveAlpha(image).invert().bandjoin(alpha);
|
@@ -205,7 +205,7 @@ namespace sharp {
|
|
205
205
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
206
206
|
int const hue, double const lightness) {
|
207
207
|
VipsInterpretation colourspaceBeforeModulate = image.interpretation();
|
208
|
-
if (
|
208
|
+
if (image.has_alpha()) {
|
209
209
|
// Separate alpha channel
|
210
210
|
VImage alpha = image[image.bands() - 1];
|
211
211
|
return RemoveAlpha(image)
|
@@ -297,7 +297,7 @@ namespace sharp {
|
|
297
297
|
threshold *= 256.0;
|
298
298
|
}
|
299
299
|
std::vector<double> backgroundAlpha({ background.back() });
|
300
|
-
if (
|
300
|
+
if (image.has_alpha()) {
|
301
301
|
background.pop_back();
|
302
302
|
} else {
|
303
303
|
background.resize(image.bands());
|
@@ -307,7 +307,7 @@ namespace sharp {
|
|
307
307
|
->set("background", background)
|
308
308
|
->set("line_art", lineArt)
|
309
309
|
->set("threshold", threshold));
|
310
|
-
if (
|
310
|
+
if (image.has_alpha()) {
|
311
311
|
// Search alpha channel (A)
|
312
312
|
int leftA, topA, widthA, heightA;
|
313
313
|
VImage alpha = image[image.bands() - 1];
|
@@ -344,7 +344,7 @@ namespace sharp {
|
|
344
344
|
throw VError("Band expansion using linear is unsupported");
|
345
345
|
}
|
346
346
|
bool const uchar = !Is16Bit(image.interpretation());
|
347
|
-
if (
|
347
|
+
if (image.has_alpha() && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
|
348
348
|
// Separate alpha channel
|
349
349
|
VImage alpha = image[bands - 1];
|
350
350
|
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
|
@@ -357,7 +357,7 @@ namespace sharp {
|
|
357
357
|
* Unflatten
|
358
358
|
*/
|
359
359
|
VImage Unflatten(VImage image) {
|
360
|
-
if (
|
360
|
+
if (image.has_alpha()) {
|
361
361
|
VImage alpha = image[image.bands() - 1];
|
362
362
|
VImage noAlpha = RemoveAlpha(image);
|
363
363
|
return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
|
package/src/pipeline.cc
CHANGED
@@ -42,7 +42,39 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
42
42
|
// Open input
|
43
43
|
vips::VImage image;
|
44
44
|
sharp::ImageType inputImageType;
|
45
|
-
|
45
|
+
if (baton->join.empty()) {
|
46
|
+
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
47
|
+
} else {
|
48
|
+
std::vector<VImage> images;
|
49
|
+
bool hasAlpha = false;
|
50
|
+
for (auto &join : baton->join) {
|
51
|
+
std::tie(image, inputImageType) = sharp::OpenInput(join);
|
52
|
+
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
|
53
|
+
images.push_back(image);
|
54
|
+
hasAlpha |= image.has_alpha();
|
55
|
+
}
|
56
|
+
if (hasAlpha) {
|
57
|
+
for (auto &image : images) {
|
58
|
+
if (!image.has_alpha()) {
|
59
|
+
image = sharp::EnsureAlpha(image, 1);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
} else {
|
63
|
+
baton->input->joinBackground.pop_back();
|
64
|
+
}
|
65
|
+
inputImageType = sharp::ImageType::PNG;
|
66
|
+
image = VImage::arrayjoin(images, VImage::option()
|
67
|
+
->set("across", baton->input->joinAcross)
|
68
|
+
->set("shim", baton->input->joinShim)
|
69
|
+
->set("background", baton->input->joinBackground)
|
70
|
+
->set("halign", baton->input->joinHalign)
|
71
|
+
->set("valign", baton->input->joinValign));
|
72
|
+
if (baton->input->joinAnimated) {
|
73
|
+
image = image.copy();
|
74
|
+
image.set(VIPS_META_N_PAGES, static_cast<int>(images.size()));
|
75
|
+
image.set(VIPS_META_PAGE_HEIGHT, static_cast<int>(image.height() / images.size()));
|
76
|
+
}
|
77
|
+
}
|
46
78
|
VipsAccess access = baton->input->access;
|
47
79
|
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
|
48
80
|
|
@@ -63,14 +95,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
63
95
|
bool autoFlip = false;
|
64
96
|
bool autoFlop = false;
|
65
97
|
|
66
|
-
if (baton->
|
98
|
+
if (baton->input->autoOrient) {
|
67
99
|
// Rotate and flip image according to Exif orientation
|
68
100
|
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
69
101
|
image = sharp::RemoveExifOrientation(image);
|
70
|
-
} else {
|
71
|
-
rotation = CalculateAngleRotation(baton->angle);
|
72
102
|
}
|
73
103
|
|
104
|
+
rotation = CalculateAngleRotation(baton->angle);
|
105
|
+
|
74
106
|
// Rotate pre-extract
|
75
107
|
bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
|
76
108
|
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
|
@@ -92,18 +124,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
92
124
|
image = image.rot(autoRotation);
|
93
125
|
autoRotation = VIPS_ANGLE_D0;
|
94
126
|
}
|
95
|
-
if (autoFlip) {
|
127
|
+
if (autoFlip != baton->flip) {
|
96
128
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
97
129
|
autoFlip = false;
|
98
|
-
} else if (baton->flip) {
|
99
|
-
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
100
130
|
baton->flip = false;
|
101
131
|
}
|
102
|
-
if (autoFlop) {
|
132
|
+
if (autoFlop != baton->flop) {
|
103
133
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
104
134
|
autoFlop = false;
|
105
|
-
} else if (baton->flop) {
|
106
|
-
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
107
135
|
baton->flop = false;
|
108
136
|
}
|
109
137
|
if (rotation != VIPS_ANGLE_D0) {
|
@@ -344,7 +372,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
344
372
|
}
|
345
373
|
|
346
374
|
// Flatten image to remove alpha channel
|
347
|
-
if (baton->flatten &&
|
375
|
+
if (baton->flatten && image.has_alpha()) {
|
348
376
|
image = sharp::Flatten(image, baton->flattenBackground);
|
349
377
|
}
|
350
378
|
|
@@ -364,12 +392,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
364
392
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
365
393
|
bool const shouldComposite = !baton->composite.empty();
|
366
394
|
|
367
|
-
if (shouldComposite && !
|
395
|
+
if (shouldComposite && !image.has_alpha()) {
|
368
396
|
image = sharp::EnsureAlpha(image, 1);
|
369
397
|
}
|
370
398
|
|
371
399
|
VipsBandFormat premultiplyFormat = image.format();
|
372
|
-
bool const shouldPremultiplyAlpha =
|
400
|
+
bool const shouldPremultiplyAlpha = image.has_alpha() &&
|
373
401
|
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
|
374
402
|
|
375
403
|
if (shouldPremultiplyAlpha) {
|
@@ -396,11 +424,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
396
424
|
image = image.rot(autoRotation);
|
397
425
|
}
|
398
426
|
// Mirror vertically (up-down) about the x-axis
|
399
|
-
if (baton->flip
|
427
|
+
if (baton->flip != autoFlip) {
|
400
428
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
401
429
|
}
|
402
430
|
// Mirror horizontally (left-right) about the y-axis
|
403
|
-
if (baton->flop
|
431
|
+
if (baton->flop != autoFlop) {
|
404
432
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
405
433
|
}
|
406
434
|
// Rotate post-extract 90-angle
|
@@ -631,6 +659,30 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
631
659
|
composite->input->access = access;
|
632
660
|
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
633
661
|
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline);
|
662
|
+
|
663
|
+
if (composite->input->autoOrient) {
|
664
|
+
// Respect EXIF Orientation
|
665
|
+
VipsAngle compositeAutoRotation = VIPS_ANGLE_D0;
|
666
|
+
bool compositeAutoFlip = false;
|
667
|
+
bool compositeAutoFlop = false;
|
668
|
+
std::tie(compositeAutoRotation, compositeAutoFlip, compositeAutoFlop) =
|
669
|
+
CalculateExifRotationAndFlip(sharp::ExifOrientation(compositeImage));
|
670
|
+
|
671
|
+
compositeImage = sharp::RemoveExifOrientation(compositeImage);
|
672
|
+
compositeImage = sharp::StaySequential(compositeImage,
|
673
|
+
compositeAutoRotation != VIPS_ANGLE_D0 || compositeAutoFlip);
|
674
|
+
|
675
|
+
if (compositeAutoRotation != VIPS_ANGLE_D0) {
|
676
|
+
compositeImage = compositeImage.rot(compositeAutoRotation);
|
677
|
+
}
|
678
|
+
if (compositeAutoFlip) {
|
679
|
+
compositeImage = compositeImage.flip(VIPS_DIRECTION_VERTICAL);
|
680
|
+
}
|
681
|
+
if (compositeAutoFlop) {
|
682
|
+
compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL);
|
683
|
+
}
|
684
|
+
}
|
685
|
+
|
634
686
|
// Verify within current dimensions
|
635
687
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
636
688
|
throw vips::VError("Image to composite must have same dimensions or smaller");
|
@@ -673,9 +725,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
673
725
|
}
|
674
726
|
// Ensure image to composite is sRGB with unpremultiplied alpha
|
675
727
|
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
676
|
-
|
677
|
-
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
678
|
-
}
|
728
|
+
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
679
729
|
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
680
730
|
// Calculate position
|
681
731
|
int left;
|
@@ -776,7 +826,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
776
826
|
// Extract channel
|
777
827
|
if (baton->extractChannel > -1) {
|
778
828
|
if (baton->extractChannel >= image.bands()) {
|
779
|
-
if (baton->extractChannel == 3 &&
|
829
|
+
if (baton->extractChannel == 3 && image.has_alpha()) {
|
780
830
|
baton->extractChannel = image.bands() - 1;
|
781
831
|
} else {
|
782
832
|
(baton->err)
|
@@ -1000,7 +1050,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
1000
1050
|
} else if (baton->formatOut == "dz") {
|
1001
1051
|
// Write DZ to buffer
|
1002
1052
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
1003
|
-
if (!
|
1053
|
+
if (!image.has_alpha()) {
|
1004
1054
|
baton->tileBackground.pop_back();
|
1005
1055
|
}
|
1006
1056
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
@@ -1049,6 +1099,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
1049
1099
|
// Unsupported output format
|
1050
1100
|
(baton->err).append("Unsupported output format ");
|
1051
1101
|
if (baton->formatOut == "input") {
|
1102
|
+
(baton->err).append("when trying to match input format of ");
|
1052
1103
|
(baton->err).append(ImageTypeId(inputImageType));
|
1053
1104
|
} else {
|
1054
1105
|
(baton->err).append(baton->formatOut);
|
@@ -1203,7 +1254,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
1203
1254
|
if (isDzZip) {
|
1204
1255
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
1205
1256
|
}
|
1206
|
-
if (!
|
1257
|
+
if (!image.has_alpha()) {
|
1207
1258
|
baton->tileBackground.pop_back();
|
1208
1259
|
}
|
1209
1260
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
@@ -1475,6 +1526,14 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
1475
1526
|
|
1476
1527
|
// Input
|
1477
1528
|
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
1529
|
+
// Join images together
|
1530
|
+
if (sharp::HasAttr(options, "join")) {
|
1531
|
+
Napi::Array join = options.Get("join").As<Napi::Array>();
|
1532
|
+
for (unsigned int i = 0; i < join.Length(); i++) {
|
1533
|
+
baton->join.push_back(
|
1534
|
+
sharp::CreateInputDescriptor(join.Get(i).As<Napi::Object>()));
|
1535
|
+
}
|
1536
|
+
}
|
1478
1537
|
// Extract image options
|
1479
1538
|
baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
|
1480
1539
|
baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
|
@@ -1567,7 +1626,6 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
1567
1626
|
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
1568
1627
|
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
1569
1628
|
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
1570
|
-
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
1571
1629
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
1572
1630
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
1573
1631
|
baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
|
package/src/pipeline.h
CHANGED
@@ -39,6 +39,7 @@ struct Composite {
|
|
39
39
|
|
40
40
|
struct PipelineBaton {
|
41
41
|
sharp::InputDescriptor *input;
|
42
|
+
std::vector<sharp::InputDescriptor *> join;
|
42
43
|
std::string formatOut;
|
43
44
|
std::string fileOut;
|
44
45
|
void *bufferOut;
|
@@ -109,7 +110,6 @@ struct PipelineBaton {
|
|
109
110
|
int claheWidth;
|
110
111
|
int claheHeight;
|
111
112
|
int claheMaxSlope;
|
112
|
-
bool useExifOrientation;
|
113
113
|
int angle;
|
114
114
|
double rotationAngle;
|
115
115
|
std::vector<double> rotationBackground;
|
@@ -282,7 +282,6 @@ struct PipelineBaton {
|
|
282
282
|
claheWidth(0),
|
283
283
|
claheHeight(0),
|
284
284
|
claheMaxSlope(3),
|
285
|
-
useExifOrientation(false),
|
286
285
|
angle(0),
|
287
286
|
rotationAngle(0.0),
|
288
287
|
rotationBackground{ 0.0, 0.0, 0.0, 255.0 },
|
package/src/stats.cc
CHANGED
@@ -58,7 +58,7 @@ class StatsWorker : public Napi::AsyncWorker {
|
|
58
58
|
baton->channelStats.push_back(cStats);
|
59
59
|
}
|
60
60
|
// Image is not opaque when alpha layer is present and contains a non-mamixa value
|
61
|
-
if (
|
61
|
+
if (image.has_alpha()) {
|
62
62
|
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
|
63
63
|
if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
|
64
64
|
baton->isOpaque = false;
|
package/src/utilities.cc
CHANGED
@@ -119,7 +119,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
|
|
119
119
|
Napi::Object format = Napi::Object::New(env);
|
120
120
|
for (std::string const f : {
|
121
121
|
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
122
|
-
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl"
|
122
|
+
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl", "rad"
|
123
123
|
}) {
|
124
124
|
// Input
|
125
125
|
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
|
@@ -233,10 +233,10 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
|
|
233
233
|
double maxColourDistance;
|
234
234
|
try {
|
235
235
|
// Premultiply and remove alpha
|
236
|
-
if (
|
236
|
+
if (image1.has_alpha()) {
|
237
237
|
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
|
238
238
|
}
|
239
|
-
if (
|
239
|
+
if (image2.has_alpha()) {
|
240
240
|
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
|
241
241
|
}
|
242
242
|
// Calculate colour distance
|