Skip to content

Commit e39c747

Browse files
Add support for flip/flop/rotate after auto-orient
fixes #4144
1 parent b9f3c23 commit e39c747

File tree

5 files changed

+117
-36
lines changed

5 files changed

+117
-36
lines changed

docs/api-operation.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ Mirroring is supported and may infer the use of a flip operation.
1515

1616
The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
1717

18-
Only one rotation can occur per pipeline.
19-
Previous calls to `rotate` in the same pipeline will be ignored.
18+
Only one rotation can occur per pipeline (aside from an initial call without
19+
arguments to orient via EXIF data). Previous calls to `rotate` in the same
20+
pipeline will be ignored.
2021

2122
Multi-page images can only be rotated by 180 degrees.
2223

@@ -60,6 +61,22 @@ const resizeThenRotate = await sharp(input)
6061
```
6162

6263

64+
## autoOrient
65+
> autoOrient() ⇒ <code>Sharp</code>
66+
67+
Alias for calling `rotate()` with no arguments, which orients the image based
68+
on EXIF orientsion.
69+
70+
This operation is aliased to emphasize its purpose, helping to remove any
71+
confusion between rotation and orientation.
72+
73+
74+
**Example**
75+
```js
76+
const output = await sharp(input).autoOrient().toBuffer();
77+
```
78+
79+
6380
## flip
6481
> flip([flip]) ⇒ <code>Sharp</code>
6582
@@ -580,7 +597,7 @@ Recombine the image with the specified matrix.
580597

581598
| Param | Type | Description |
582599
| --- | --- | --- |
583-
| inputMatrix | <code>Array.&lt;Array.&lt;number&gt;&gt;</code> | 3x3 or 4x4 Recombination matrix |
600+
| inputMatrix | <code>Array.&lt;Array.&lt;number&gt;&gt;</code> | 3x3 Recombination matrix |
584601

585602
**Example**
586603
```js

docs/search-index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

lib/index.d.ts

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -364,24 +364,72 @@ declare namespace sharp {
364364
//#region Operation functions
365365

366366
/**
367-
* Rotate the output image by either an explicit angle or auto-orient based on the EXIF Orientation tag.
367+
* Rotate the output image by either an explicit angle
368+
* or auto-orient based on the EXIF `Orientation` tag.
368369
*
369-
* If an angle is provided, it is converted to a valid positive degree rotation. For example, -450 will produce a 270deg rotation.
370+
* If an angle is provided, it is converted to a valid positive degree rotation.
371+
* For example, `-450` will produce a 270 degree rotation.
370372
*
371-
* When rotating by an angle other than a multiple of 90, the background colour can be provided with the background option.
373+
* When rotating by an angle other than a multiple of 90,
374+
* the background colour can be provided with the `background` option.
372375
*
373-
* If no angle is provided, it is determined from the EXIF data. Mirroring is supported and may infer the use of a flip operation.
376+
* If no angle is provided, it is determined from the EXIF data.
377+
* Mirroring is supported and may infer the use of a flip operation.
374378
*
375-
* The use of rotate implies the removal of the EXIF Orientation tag, if any.
379+
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
376380
*
377-
* Method order is important when both rotating and extracting regions, for example rotate(x).extract(y) will produce a different result to extract(y).rotate(x).
378-
* @param angle angle of rotation. (optional, default auto)
379-
* @param options if present, is an Object with optional attributes.
381+
* Only one rotation can occur per pipeline (aside from an initial call without
382+
* arguments to orient via EXIF data). Previous calls to `rotate` in the same
383+
* pipeline will be ignored.
384+
*
385+
* Multi-page images can only be rotated by 180 degrees.
386+
*
387+
* Method order is important when rotating, resizing and/or extracting regions,
388+
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
389+
*
390+
* @example
391+
* const pipeline = sharp()
392+
* .rotate()
393+
* .resize(null, 200)
394+
* .toBuffer(function (err, outputBuffer, info) {
395+
* // outputBuffer contains 200px high JPEG image data,
396+
* // auto-rotated using EXIF Orientation tag
397+
* // info.width and info.height contain the dimensions of the resized image
398+
* });
399+
* readableStream.pipe(pipeline);
400+
*
401+
* @example
402+
* const rotateThenResize = await sharp(input)
403+
* .rotate(90)
404+
* .resize({ width: 16, height: 8, fit: 'fill' })
405+
* .toBuffer();
406+
* const resizeThenRotate = await sharp(input)
407+
* .resize({ width: 16, height: 8, fit: 'fill' })
408+
* .rotate(90)
409+
* .toBuffer();
410+
*
411+
* @param {number} [angle=auto] angle of rotation.
412+
* @param {Object} [options] - if present, is an Object with optional attributes.
413+
* @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.
414+
* @returns {Sharp}
380415
* @throws {Error} Invalid parameters
381-
* @returns A sharp instance that can be used to chain operations
382416
*/
383417
rotate(angle?: number, options?: RotateOptions): Sharp;
384418

419+
/**
420+
* Alias for calling `rotate()` with no arguments, which orients the image based
421+
* on EXIF orientsion.
422+
*
423+
* This operation is aliased to emphasize its purpose, helping to remove any
424+
* confusion between rotation and orientation.
425+
*
426+
* @example
427+
* const output = await sharp(input).autoOrient().toBuffer();
428+
*
429+
* @returns {Sharp}
430+
*/
431+
autoOrient(): Sharp
432+
385433
/**
386434
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
387435
* The use of flip implies the removal of the EXIF Orientation tag, if any.

lib/operation.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ const is = require('./is');
2121
*
2222
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
2323
*
24-
* Only one rotation can occur per pipeline.
25-
* Previous calls to `rotate` in the same pipeline will be ignored.
24+
* Only one rotation can occur per pipeline (aside from an initial call without
25+
* arguments to orient via EXIF data). Previous calls to `rotate` in the same
26+
* pipeline will be ignored.
2627
*
2728
* Multi-page images can only be rotated by 180 degrees.
2829
*
@@ -81,6 +82,22 @@ function rotate (angle, options) {
8182
return this;
8283
}
8384

85+
/**
86+
* Alias for calling `rotate()` with no arguments, which orients the image based
87+
* on EXIF orientsion.
88+
*
89+
* This operation is aliased to emphasize its purpose, helping to remove any
90+
* confusion between rotation and orientation.
91+
*
92+
* @example
93+
* const output = await sharp(input).autoOrient().toBuffer();
94+
*
95+
* @returns {Sharp}
96+
*/
97+
function autoOrient () {
98+
return this.rotate();
99+
}
100+
84101
/**
85102
* Mirror the image vertically (up-down) about the x-axis.
86103
* This always occurs before rotation, if any.
@@ -787,22 +804,24 @@ function linear (a, b) {
787804
* // With this example input, a sepia filter has been applied
788805
* });
789806
*
790-
* @param {Array<Array<number>>} inputMatrix - 3x3 or 4x4 Recombination matrix
807+
* @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
791808
* @returns {Sharp}
792809
* @throws {Error} Invalid parameters
793810
*/
794811
function recomb (inputMatrix) {
795-
if (!Array.isArray(inputMatrix)) {
796-
throw is.invalidParameterError('inputMatrix', 'array', inputMatrix);
797-
}
798-
if (inputMatrix.length !== 3 && inputMatrix.length !== 4) {
799-
throw is.invalidParameterError('inputMatrix', '3x3 or 4x4 array', inputMatrix.length);
800-
}
801-
const recombMatrix = inputMatrix.flat().map(Number);
802-
if (recombMatrix.length !== 9 && recombMatrix.length !== 16) {
803-
throw is.invalidParameterError('inputMatrix', 'cardinality of 9 or 16', recombMatrix.length);
812+
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
813+
inputMatrix[0].length !== 3 ||
814+
inputMatrix[1].length !== 3 ||
815+
inputMatrix[2].length !== 3
816+
) {
817+
// must pass in a kernel
818+
throw new Error('Invalid recombination matrix');
804819
}
805-
this.options.recombMatrix = recombMatrix;
820+
this.options.recombMatrix = [
821+
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
822+
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
823+
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
824+
].map(Number);
806825
return this;
807826
}
808827

@@ -895,6 +914,7 @@ function modulate (options) {
895914
*/
896915
module.exports = function (Sharp) {
897916
Object.assign(Sharp.prototype, {
917+
autoOrient,
898918
rotate,
899919
flip,
900920
flop,

src/pipeline.cc

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ class PipelineWorker : public Napi::AsyncWorker {
7777
// Rotate and flip image according to Exif orientation
7878
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
7979
image = sharp::RemoveExifOrientation(image);
80-
} else {
81-
rotation = CalculateAngleRotation(baton->angle);
8280
}
8381

82+
rotation = CalculateAngleRotation(baton->angle);
83+
8484
// Rotate pre-extract
8585
bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
8686
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
@@ -102,18 +102,14 @@ class PipelineWorker : public Napi::AsyncWorker {
102102
image = image.rot(autoRotation);
103103
autoRotation = VIPS_ANGLE_D0;
104104
}
105-
if (autoFlip) {
105+
if (autoFlip != baton->flip) {
106106
image = image.flip(VIPS_DIRECTION_VERTICAL);
107107
autoFlip = false;
108-
} else if (baton->flip) {
109-
image = image.flip(VIPS_DIRECTION_VERTICAL);
110108
baton->flip = false;
111109
}
112-
if (autoFlop) {
110+
if (autoFlop != baton->flop) {
113111
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
114112
autoFlop = false;
115-
} else if (baton->flop) {
116-
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
117113
baton->flop = false;
118114
}
119115
if (rotation != VIPS_ANGLE_D0) {
@@ -405,11 +401,11 @@ class PipelineWorker : public Napi::AsyncWorker {
405401
image = image.rot(autoRotation);
406402
}
407403
// Mirror vertically (up-down) about the x-axis
408-
if (baton->flip || autoFlip) {
404+
if (baton->flip != autoFlip) {
409405
image = image.flip(VIPS_DIRECTION_VERTICAL);
410406
}
411407
// Mirror horizontally (left-right) about the y-axis
412-
if (baton->flop || autoFlop) {
408+
if (baton->flop != autoFlop) {
413409
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
414410
}
415411
// Rotate post-extract 90-angle

0 commit comments

Comments
 (0)