Skip to content

Commit

Permalink
Add support for flip/flop/rotate after auto-orient
Browse files Browse the repository at this point in the history
  • Loading branch information
happycollision committed Dec 8, 2024
1 parent b4e9dd3 commit 0263e6e
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 24 deletions.
21 changes: 19 additions & 2 deletions docs/api-operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Only one rotation can occur per pipeline.
Previous calls to `rotate` in the same pipeline will be ignored.
Only one rotation can occur per pipeline (aside from an initial call without
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.

Expand Down Expand Up @@ -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]) ⇒ <code>Sharp</code>
Expand Down
2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

66 changes: 57 additions & 9 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,24 +364,72 @@ declare namespace sharp {
//#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).
* @param angle angle of rotation. (optional, default auto)
* @param options if present, is an Object with optional attributes.
* Only one rotation can occur per pipeline (aside from an initial call without
* 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.
*
* 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
* @returns A sharp instance that can be used to chain operations
*/
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.
* The use of flip implies the removal of the EXIF Orientation tag, if any.
Expand Down
22 changes: 20 additions & 2 deletions lib/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ const vipsPrecision = {
*
* The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
*
* Only one rotation can occur per pipeline.
* Previous calls to `rotate` in the same pipeline will be ignored.
* Only one rotation can occur per pipeline (aside from an initial call without
* 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.
*
Expand Down Expand Up @@ -92,6 +93,22 @@ function rotate (angle, options) {
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.
* This always occurs before rotation, if any.
Expand Down Expand Up @@ -935,6 +952,7 @@ function modulate (options) {
*/
module.exports = function (Sharp) {
Object.assign(Sharp.prototype, {
autoOrient,
rotate,
flip,
flop,
Expand Down
16 changes: 6 additions & 10 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate and flip image according to Exif orientation
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
image = sharp::RemoveExifOrientation(image);
} else {
rotation = CalculateAngleRotation(baton->angle);
}

rotation = CalculateAngleRotation(baton->angle);

// Rotate pre-extract
bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
Expand All @@ -92,18 +92,14 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.rot(autoRotation);
autoRotation = VIPS_ANGLE_D0;
}
if (autoFlip) {
if (autoFlip != baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
autoFlip = false;
} else if (baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
baton->flip = false;
}
if (autoFlop) {
if (autoFlop != baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
autoFlop = false;
} else if (baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
baton->flop = false;
}
if (rotation != VIPS_ANGLE_D0) {
Expand Down Expand Up @@ -396,11 +392,11 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.rot(autoRotation);
}
// Mirror vertically (up-down) about the x-axis
if (baton->flip || autoFlip) {
if (baton->flip != autoFlip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
}
// Mirror horizontally (left-right) about the y-axis
if (baton->flop || autoFlop) {
if (baton->flop != autoFlop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
}
// Rotate post-extract 90-angle
Expand Down

0 comments on commit 0263e6e

Please sign in to comment.