Add support for flip/flop/rotate after auto-orient

fixes #4144
This commit is contained in:
Don Denton 2024-07-05 23:14:15 -04:00
parent b4e9dd3e19
commit 0263e6eaac
5 changed files with 103 additions and 24 deletions

View File

@ -15,8 +15,9 @@ Mirroring is supported and may infer the use of a flip operation.
The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any. The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
Only one rotation can occur per pipeline. Only one rotation can occur per pipeline (aside from an initial call without
Previous calls to `rotate` in the same pipeline will be ignored. arguments to orient via EXIF data). Previous calls to `rotate` in the same
pipeline will be ignored.
Multi-page images can only be rotated by 180 degrees. Multi-page images can only be rotated by 180 degrees.
@ -60,6 +61,22 @@ const resizeThenRotate = await sharp(input)
``` ```
## autoOrient
> autoOrient() ⇒ <code>Sharp</code>
Alias for calling `rotate()` with no arguments, which orients the image based
on EXIF orientsion.
This operation is aliased to emphasize its purpose, helping to remove any
confusion between rotation and orientation.
**Example**
```js
const output = await sharp(input).autoOrient().toBuffer();
```
## flip ## flip
> flip([flip]) ⇒ <code>Sharp</code> > flip([flip]) ⇒ <code>Sharp</code>

File diff suppressed because one or more lines are too long

66
lib/index.d.ts vendored
View File

@ -364,24 +364,72 @@ declare namespace sharp {
//#region Operation functions //#region Operation functions
/** /**
* Rotate the output image by either an explicit angle or auto-orient based on the EXIF Orientation tag. * Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag.
* *
* If an angle is provided, it is converted to a valid positive degree rotation. For example, -450 will produce a 270deg rotation. * If an angle is provided, it is converted to a valid positive degree rotation.
* For example, `-450` will produce a 270 degree rotation.
* *
* When rotating by an angle other than a multiple of 90, the background colour can be provided with the background option. * When rotating by an angle other than a multiple of 90,
* the background colour can be provided with the `background` option.
* *
* If no angle is provided, it is determined from the EXIF data. Mirroring is supported and may infer the use of a flip operation. * If no angle is provided, it is determined from the EXIF data.
* Mirroring is supported and may infer the use of a flip operation.
* *
* The use of rotate implies the removal of the EXIF Orientation tag, if any. * The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
* *
* 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). * Only one rotation can occur per pipeline (aside from an initial call without
* @param angle angle of rotation. (optional, default auto) * arguments to orient via EXIF data). Previous calls to `rotate` in the same
* @param options if present, is an Object with optional attributes. * pipeline will be ignored.
*
* Multi-page images can only be rotated by 180 degrees.
*
* Method order is important when rotating, resizing and/or extracting regions,
* for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
*
* @example
* const pipeline = sharp()
* .rotate()
* .resize(null, 200)
* .toBuffer(function (err, outputBuffer, info) {
* // outputBuffer contains 200px high JPEG image data,
* // auto-rotated using EXIF Orientation tag
* // info.width and info.height contain the dimensions of the resized image
* });
* readableStream.pipe(pipeline);
*
* @example
* const rotateThenResize = await sharp(input)
* .rotate(90)
* .resize({ width: 16, height: 8, fit: 'fill' })
* .toBuffer();
* const resizeThenRotate = await sharp(input)
* .resize({ width: 16, height: 8, fit: 'fill' })
* .rotate(90)
* .toBuffer();
*
* @param {number} [angle=auto] angle of rotation.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @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.
* @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
* @returns A sharp instance that can be used to chain operations
*/ */
rotate(angle?: number, options?: RotateOptions): Sharp; rotate(angle?: number, options?: RotateOptions): Sharp;
/**
* Alias for calling `rotate()` with no arguments, which orients the image based
* on EXIF orientsion.
*
* This operation is aliased to emphasize its purpose, helping to remove any
* confusion between rotation and orientation.
*
* @example
* const output = await sharp(input).autoOrient().toBuffer();
*
* @returns {Sharp}
*/
autoOrient(): Sharp
/** /**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any. * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
* The use of flip implies the removal of the EXIF Orientation tag, if any. * The use of flip implies the removal of the EXIF Orientation tag, if any.

View File

@ -32,8 +32,9 @@ const vipsPrecision = {
* *
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any. * The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
* *
* Only one rotation can occur per pipeline. * Only one rotation can occur per pipeline (aside from an initial call without
* Previous calls to `rotate` in the same pipeline will be ignored. * arguments to orient via EXIF data). Previous calls to `rotate` in the same
* pipeline will be ignored.
* *
* Multi-page images can only be rotated by 180 degrees. * Multi-page images can only be rotated by 180 degrees.
* *
@ -92,6 +93,22 @@ function rotate (angle, options) {
return this; return this;
} }
/**
* Alias for calling `rotate()` with no arguments, which orients the image based
* on EXIF orientsion.
*
* This operation is aliased to emphasize its purpose, helping to remove any
* confusion between rotation and orientation.
*
* @example
* const output = await sharp(input).autoOrient().toBuffer();
*
* @returns {Sharp}
*/
function autoOrient () {
return this.rotate();
}
/** /**
* Mirror the image vertically (up-down) about the x-axis. * Mirror the image vertically (up-down) about the x-axis.
* This always occurs before rotation, if any. * This always occurs before rotation, if any.
@ -935,6 +952,7 @@ function modulate (options) {
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
autoOrient,
rotate, rotate,
flip, flip,
flop, flop,

View File

@ -67,10 +67,10 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate and flip image according to Exif orientation // Rotate and flip image according to Exif orientation
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image)); std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
image = sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} else {
rotation = CalculateAngleRotation(baton->angle);
} }
rotation = CalculateAngleRotation(baton->angle);
// Rotate pre-extract // Rotate pre-extract
bool const shouldRotateBefore = baton->rotateBeforePreExtract && bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 || (rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
@ -92,18 +92,14 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.rot(autoRotation); image = image.rot(autoRotation);
autoRotation = VIPS_ANGLE_D0; autoRotation = VIPS_ANGLE_D0;
} }
if (autoFlip) { if (autoFlip != baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL); image = image.flip(VIPS_DIRECTION_VERTICAL);
autoFlip = false; autoFlip = false;
} else if (baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
baton->flip = false; baton->flip = false;
} }
if (autoFlop) { if (autoFlop != baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL); image = image.flip(VIPS_DIRECTION_HORIZONTAL);
autoFlop = false; autoFlop = false;
} else if (baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
baton->flop = false; baton->flop = false;
} }
if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D0) {
@ -396,11 +392,11 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.rot(autoRotation); image = image.rot(autoRotation);
} }
// Mirror vertically (up-down) about the x-axis // Mirror vertically (up-down) about the x-axis
if (baton->flip || autoFlip) { if (baton->flip != autoFlip) {
image = image.flip(VIPS_DIRECTION_VERTICAL); image = image.flip(VIPS_DIRECTION_VERTICAL);
} }
// Mirror horizontally (left-right) about the y-axis // Mirror horizontally (left-right) about the y-axis
if (baton->flop || autoFlop) { if (baton->flop != autoFlop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL); image = image.flip(VIPS_DIRECTION_HORIZONTAL);
} }
// Rotate post-extract 90-angle // Rotate post-extract 90-angle