Skip to content

Commit

Permalink
Merge dd7c02c into 4cd3b66
Browse files Browse the repository at this point in the history
  • Loading branch information
ncoden committed Apr 30, 2017
2 parents 4cd3b66 + dd7c02c commit 15b63c8
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 40 deletions.
1 change: 1 addition & 0 deletions lib/constructor.js
Expand Up @@ -110,6 +110,7 @@ const Sharp = function (input, options) {
height: -1,
canvas: 'crop',
crop: 0,
useExifOrientation: false,
angle: 0,
rotateBeforePreExtract: false,
flip: false,
Expand Down
13 changes: 8 additions & 5 deletions lib/operation.js
Expand Up @@ -6,7 +6,10 @@ const is = require('./is');
* Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag.
*
* Use this method without angle to determine the angle from EXIF data.
* If an angle is provided, it is converted to a valid 90/180/270deg rotation.
* For example, `-450` will produce a 270deg rotation.
*
* If no angle is provided, it is determined the from 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.
Expand All @@ -25,17 +28,17 @@ const is = require('./is');
* });
* readableStream.pipe(pipeline);
*
* @param {Number} [angle=auto] 0, 90, 180 or 270.
* @param {Number} [angle=auto] angle of rotation, must be a multiple of 90.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function rotate (angle) {
if (!is.defined(angle)) {
this.options.angle = -1;
} else if (is.integer(angle) && is.inArray(angle, [0, 90, 180, 270])) {
this.options.useExifOrientation = true;
} else if (is.integer(angle) && !(angle % 90)) {
this.options.angle = angle;
} else {
throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle);
throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + angle);
}
return this;
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -34,7 +34,8 @@
"Jérémy Lal <kapouer@melix.org>",
"Rahul Nanwani <r.nanwani@gmail.com>",
"Alice Monday <alice0meta@gmail.com>",
"Kristo Jorgenson <kristo.jorgenson@gmail.com>"
"Kristo Jorgenson <kristo.jorgenson@gmail.com>",
"Nicolas Coden <nicolas@ncoden.fr>"
],
"scripts": {
"clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*",
Expand Down
69 changes: 35 additions & 34 deletions src/pipeline.cc
Expand Up @@ -80,16 +80,12 @@ class PipelineWorker : public Nan::AsyncWorker {

// Calculate angle of rotation
VipsAngle rotation;
bool flip;
bool flop;
std::tie(rotation, flip, flop) = CalculateRotationAndFlip(baton->angle, image);
if (flip && !baton->flip) {
// Add flip operation due to EXIF mirroring
baton->flip = TRUE;
}
if (flop && !baton->flop) {
// Add flip operation due to EXIF mirroring
baton->flop = TRUE;
if (baton->useExifOrientation) {
// Rotate and flip image according to Exif orientation
// (ignore the requested rotation and flip)
std::tie(rotation, baton->flip, baton->flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
} else {
rotation = CalculateAngleRotation(baton->angle);
}

// Rotate pre-extract
Expand Down Expand Up @@ -995,39 +991,43 @@ class PipelineWorker : public Nan::AsyncWorker {
std::vector<v8::Local<v8::Object>> buffersToPersist;

/*
Calculate the angle of rotation and need-to-flip for the output image.
In order of priority:
1. Use explicitly requested angle (supports 90, 180, 270)
2. Use input image EXIF Orientation header - supports mirroring
3. Otherwise default to zero, i.e. no rotation
Calculate the angle of rotation and need-to-flip for the given Exif orientation
By default, returns zero, i.e. no rotation.
*/
std::tuple<VipsAngle, bool, bool>
CalculateRotationAndFlip(int const angle, vips::VImage image) {
CalculateExifRotationAndFlip(int const exifOrientation) {
VipsAngle rotate = VIPS_ANGLE_D0;
bool flip = FALSE;
bool flop = FALSE;
if (angle == -1) {
switch (sharp::ExifOrientation(image)) {
case 6: rotate = VIPS_ANGLE_D90; break;
case 3: rotate = VIPS_ANGLE_D180; break;
case 8: rotate = VIPS_ANGLE_D270; break;
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
}
} else {
if (angle == 90) {
rotate = VIPS_ANGLE_D90;
} else if (angle == 180) {
rotate = VIPS_ANGLE_D180;
} else if (angle == 270) {
rotate = VIPS_ANGLE_D270;
}
switch (exifOrientation) {
case 6: rotate = VIPS_ANGLE_D90; break;
case 3: rotate = VIPS_ANGLE_D180; break;
case 8: rotate = VIPS_ANGLE_D270; break;
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
}
return std::make_tuple(rotate, flip, flop);
}

/*
Calculate the rotation for the given angle.
Supports any positive or negative angle that is a multiple of 90.
*/
VipsAngle
CalculateAngleRotation(int angle) {
angle = angle % 360;
if (angle < 0)
angle = 360 - angle;
switch (angle) {
case 90: return VIPS_ANGLE_D90;
case 180: return VIPS_ANGLE_D180;
case 270: return VIPS_ANGLE_D270;
}
return VIPS_ANGLE_D0;
}

/*
Assemble the suffix argument to dzsave, which is the format (by extname)
alongisde comma-separated arguments to the corresponding `formatsave` vips
Expand Down Expand Up @@ -1152,6 +1152,7 @@ NAN_METHOD(pipeline) {
baton->gamma = AttrTo<double>(options, "gamma");
baton->greyscale = AttrTo<bool>(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip");
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline.h
Expand Up @@ -80,6 +80,7 @@ struct PipelineBaton {
double gamma;
bool greyscale;
bool normalise;
bool useExifOrientation;
int angle;
bool rotateBeforePreExtract;
bool flip;
Expand Down Expand Up @@ -156,6 +157,7 @@ struct PipelineBaton {
gamma(0.0),
greyscale(false),
normalise(false),
useExifOrientation(false),
angle(0),
flip(false),
flop(false),
Expand Down
19 changes: 19 additions & 0 deletions test/unit/rotate.js
Expand Up @@ -34,6 +34,25 @@ describe('Rotation', function () {
});
});

it('Rotate by any 90-multiple angle', function (done) {
[-3690, -450, -90, 90, 450, 3690].forEach(function (angle) {
sharp(fixtures.inputJpg).rotate(angle).resize(320, 240).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
[-3780, -540, 0, 180, 540, 3780].forEach(function (angle) {
sharp(fixtures.inputJpg).rotate(angle).resize(320, 240).toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(240, info.width);
assert.strictEqual(320, info.height);
done();
});
});
});

it('Rotate by 270 degrees, square output ignoring aspect ratio', function (done) {
sharp(fixtures.inputJpg)
.resize(240, 240)
Expand Down

0 comments on commit 15b63c8

Please sign in to comment.