EXIF Orientation tags 2 and 4 were flipping instead of flopping
@ -250,11 +250,16 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Calculate angle of rotation
|
// Calculate angle of rotation
|
||||||
Angle rotation;
|
Angle rotation;
|
||||||
bool flip;
|
bool flip;
|
||||||
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
|
bool flop;
|
||||||
|
std::tie(rotation, flip, flop) = CalculateRotationAndFlip(baton->angle, image);
|
||||||
if (flip && !baton->flip) {
|
if (flip && !baton->flip) {
|
||||||
// Add flip operation due to EXIF mirroring
|
// Add flip operation due to EXIF mirroring
|
||||||
baton->flip = TRUE;
|
baton->flip = TRUE;
|
||||||
}
|
}
|
||||||
|
if (flop && !baton->flop) {
|
||||||
|
// Add flip operation due to EXIF mirroring
|
||||||
|
baton->flop = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
// Rotate pre-extract
|
// Rotate pre-extract
|
||||||
if (baton->rotateBeforePreExtract && rotation != Angle::D0) {
|
if (baton->rotateBeforePreExtract && rotation != Angle::D0) {
|
||||||
@ -1041,18 +1046,19 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
2. Use input image EXIF Orientation header - supports mirroring
|
2. Use input image EXIF Orientation header - supports mirroring
|
||||||
3. Otherwise default to zero, i.e. no rotation
|
3. Otherwise default to zero, i.e. no rotation
|
||||||
*/
|
*/
|
||||||
std::tuple<Angle, bool>
|
std::tuple<Angle, bool, bool>
|
||||||
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
|
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
|
||||||
Angle rotate = Angle::D0;
|
Angle rotate = Angle::D0;
|
||||||
bool flip = FALSE;
|
bool flip = FALSE;
|
||||||
|
bool flop = FALSE;
|
||||||
if (angle == -1) {
|
if (angle == -1) {
|
||||||
switch(ExifOrientation(input)) {
|
switch(ExifOrientation(input)) {
|
||||||
case 6: rotate = Angle::D90; break;
|
case 6: rotate = Angle::D90; break;
|
||||||
case 3: rotate = Angle::D180; break;
|
case 3: rotate = Angle::D180; break;
|
||||||
case 8: rotate = Angle::D270; break;
|
case 8: rotate = Angle::D270; break;
|
||||||
case 2: flip = TRUE; break; // flip 1
|
case 2: flop = TRUE; break; // flop 1
|
||||||
case 7: flip = TRUE; rotate = Angle::D90; break; // flip 6
|
case 7: flip = TRUE; rotate = Angle::D90; break; // flip 6
|
||||||
case 4: flip = TRUE; rotate = Angle::D180; break; // flip 3
|
case 4: flop = TRUE; rotate = Angle::D180; break; // flop 3
|
||||||
case 5: flip = TRUE; rotate = Angle::D270; break; // flip 8
|
case 5: flip = TRUE; rotate = Angle::D270; break; // flip 8
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1064,7 +1070,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
rotate = Angle::D270;
|
rotate = Angle::D270;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::make_tuple(rotate, flip);
|
return std::make_tuple(rotate, flip, flop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1240,3 +1246,4 @@ NAN_METHOD(pipeline) {
|
|||||||
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
|
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
|
||||||
queueListener->Call(1, queueLength);
|
queueListener->Call(1, queueLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
test/fixtures/Landscape_1.jpg
vendored
Executable file
After Width: | Height: | Size: 136 KiB |
BIN
test/fixtures/Landscape_2.jpg
vendored
Executable file
After Width: | Height: | Size: 134 KiB |
BIN
test/fixtures/Landscape_3.jpg
vendored
Executable file
After Width: | Height: | Size: 138 KiB |
BIN
test/fixtures/Landscape_4.jpg
vendored
Executable file
After Width: | Height: | Size: 137 KiB |
0
test/fixtures/Landscape_5.jpg
vendored
Normal file → Executable file
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 134 KiB |
BIN
test/fixtures/Landscape_6.jpg
vendored
Executable file
After Width: | Height: | Size: 134 KiB |
BIN
test/fixtures/Landscape_7.jpg
vendored
Executable file
After Width: | Height: | Size: 137 KiB |
0
test/fixtures/Landscape_8.jpg
vendored
Normal file → Executable file
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
BIN
test/fixtures/Portrait_1.jpg
vendored
Executable file
After Width: | Height: | Size: 126 KiB |
BIN
test/fixtures/Portrait_2.jpg
vendored
Executable file
After Width: | Height: | Size: 133 KiB |
BIN
test/fixtures/Portrait_3.jpg
vendored
Executable file
After Width: | Height: | Size: 133 KiB |
BIN
test/fixtures/Portrait_4.jpg
vendored
Executable file
After Width: | Height: | Size: 128 KiB |
BIN
test/fixtures/Portrait_5.jpg
vendored
Executable file
After Width: | Height: | Size: 131 KiB |
BIN
test/fixtures/Portrait_6.jpg
vendored
Executable file
After Width: | Height: | Size: 133 KiB |
BIN
test/fixtures/Portrait_7.jpg
vendored
Executable file
After Width: | Height: | Size: 132 KiB |
BIN
test/fixtures/Portrait_8.jpg
vendored
Executable file
After Width: | Height: | Size: 129 KiB |
BIN
test/fixtures/expected/Landscape_1-out.jpg
vendored
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
test/fixtures/expected/Landscape_2-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Landscape_3-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Landscape_4-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Landscape_5-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Landscape_6-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Landscape_7-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Landscape_8-out.jpg
vendored
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
test/fixtures/expected/Portrait_1-out.jpg
vendored
Normal file
After Width: | Height: | Size: 169 KiB |
BIN
test/fixtures/expected/Portrait_2-out.jpg
vendored
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
test/fixtures/expected/Portrait_3-out.jpg
vendored
Normal file
After Width: | Height: | Size: 165 KiB |
BIN
test/fixtures/expected/Portrait_4-out.jpg
vendored
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
test/fixtures/expected/Portrait_5-out.jpg
vendored
Normal file
After Width: | Height: | Size: 172 KiB |
BIN
test/fixtures/expected/Portrait_6-out.jpg
vendored
Normal file
After Width: | Height: | Size: 167 KiB |
BIN
test/fixtures/expected/Portrait_7-out.jpg
vendored
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
test/fixtures/expected/Portrait_8-out.jpg
vendored
Normal file
After Width: | Height: | Size: 165 KiB |
18
test/fixtures/index.js
vendored
@ -40,6 +40,24 @@ var fingerprint = function(image, callback) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
|
inputJpgWithLandscapeExif1: getPath('Landscape_1.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif2: getPath('Landscape_2.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif3: getPath('Landscape_3.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif4: getPath('Landscape_4.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif5: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif6: getPath('Landscape_6.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif7: getPath('Landscape_7.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithLandscapeExif8: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
|
||||||
|
inputJpgWithPortraitExif1: getPath('Portrait_1.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif2: getPath('Portrait_2.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif3: getPath('Portrait_3.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif4: getPath('Portrait_4.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif5: getPath('Portrait_5.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif6: getPath('Portrait_6.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif7: getPath('Portrait_7.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
inputJpgWithPortraitExif8: getPath('Portrait_8.jpg'), // https://github.com/recurser/exif-orientation-examples
|
||||||
|
|
||||||
inputJpg: getPath('2569067123_aca715a2ee_o.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
|
inputJpg: getPath('2569067123_aca715a2ee_o.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
inputJpgWithExif: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
inputJpgWithExif: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
||||||
inputJpgWithExifMirroring: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_5.jpg
|
inputJpgWithExifMirroring: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_5.jpg
|
||||||
|
@ -9,6 +9,23 @@ sharp.cache(0);
|
|||||||
|
|
||||||
describe('Rotation', function() {
|
describe('Rotation', function() {
|
||||||
|
|
||||||
|
['Landscape', 'Portrait'].forEach(function(orientation) {
|
||||||
|
[1,2,3,4,5,6,7,8].forEach(function(exifTag) {
|
||||||
|
it('Input image has Orientation EXIF tag value of (' + exifTag + '), auto-rotate', function(done) {
|
||||||
|
sharp(fixtures['inputJpgWith'+orientation+'Exif'+exifTag])
|
||||||
|
.rotate()
|
||||||
|
.resize(320)
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(orientation === 'Landscape' ? 240 : 427, info.height);
|
||||||
|
fixtures.assertSimilar(fixtures.expected(orientation + '_' + exifTag + '-out.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Rotate by 90 degrees, respecting output input size', function(done) {
|
it('Rotate by 90 degrees, respecting output input size', function(done) {
|
||||||
sharp(fixtures.inputJpg).rotate(90).resize(320, 240).toBuffer(function(err, data, info) {
|
sharp(fixtures.inputJpg).rotate(90).resize(320, 240).toBuffer(function(err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|