Add autoOrient operation and constructor option #4144

This commit is contained in:
Don Denton
2024-07-03 10:34:54 -04:00
committed by Lovell Fuller
parent b7ff2645c4
commit 14c83e1f4c
137 changed files with 428 additions and 136 deletions

View File

@@ -23,6 +23,10 @@ describe('AVIF', () => {
const { size, ...metadata } = await sharp(data)
.metadata();
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
width: 32
},
channels: 3,
chromaSubsampling: '4:2:0',
density: 72,
@@ -48,6 +52,10 @@ describe('AVIF', () => {
const { size, ...metadata } = await sharp(data)
.metadata();
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
width: 32
},
channels: 3,
compression: 'av1',
depth: 'uchar',
@@ -72,6 +80,10 @@ describe('AVIF', () => {
const { size, ...metadata } = await sharp(data)
.metadata();
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 13,
width: 32
},
channels: 3,
compression: 'av1',
depth: 'uchar',
@@ -97,6 +109,10 @@ describe('AVIF', () => {
const { size, ...metadata } = await sharp(data)
.metadata();
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 300,
width: 10
},
channels: 4,
compression: 'av1',
depth: 'uchar',
@@ -123,6 +139,10 @@ describe('AVIF', () => {
const { size, ...metadata } = await sharp(data)
.metadata();
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 26,
width: 32
},
channels: 3,
compression: 'av1',
depth: 'uchar',

View File

@@ -138,6 +138,22 @@ describe('composite', () => {
fixtures.assertMaxColourDistance(actual, expected);
});
it('autoOrient', async () => {
const data = await sharp({
create: {
width: 600, height: 600, channels: 4, background: { ...red, alpha: 1 }
}
})
.composite([{
input: fixtures.inputJpgWithExif,
autoOrient: true
}])
.jpeg()
.toBuffer();
await fixtures.assertSimilar(fixtures.expected('composite-autoOrient.jpg'), data);
});
it('zero offset', done => {
sharp(fixtures.inputJpg)
.resize(80)

View File

@@ -102,6 +102,8 @@ describe('Image metadata', function () {
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(1, metadata.orientation);
assert.strictEqual(2464, metadata.autoOrient.width);
assert.strictEqual(3248, metadata.autoOrient.height);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
assert.strictEqual('inch', metadata.resolutionUnit);
@@ -148,6 +150,8 @@ describe('Image metadata', function () {
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual(2809, metadata.autoOrient.width);
assert.strictEqual(2074, metadata.autoOrient.height);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
done();
@@ -218,7 +222,11 @@ describe('Image metadata', function () {
isPalette: false,
isProgressive: false,
space: 'b-w',
width: 32
width: 32,
autoOrient: {
width: 32,
height: 32
}
});
});
@@ -239,7 +247,11 @@ describe('Image metadata', function () {
isPalette: false,
isProgressive: false,
space: 'grey16',
width: 32
width: 32,
autoOrient: {
width: 32,
height: 32
}
});
});
@@ -601,6 +613,10 @@ describe('Image metadata', function () {
if (err) throw err;
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(8, metadata.orientation);
assert.strictEqual(320, metadata.width);
assert.strictEqual(240, metadata.height);
assert.strictEqual(240, metadata.autoOrient.width);
assert.strictEqual(320, metadata.autoOrient.height);
assert.strictEqual('object', typeof metadata.exif);
assert.strictEqual(true, metadata.exif instanceof Buffer);
// EXIF
@@ -926,7 +942,11 @@ describe('Image metadata', function () {
pagePrimary: 0,
compression: 'av1',
hasProfile: false,
hasAlpha: false
hasAlpha: false,
autoOrient: {
width: 2048,
height: 858
}
});
});

View File

@@ -138,6 +138,10 @@ describe('PNG', function () {
const { size, ...metadata } = await sharp(data).metadata();
assert.deepStrictEqual(metadata, {
autoOrient: {
height: 68,
width: 68
},
format: 'png',
width: 68,
height: 68,

View File

@@ -9,46 +9,110 @@ const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Rotation', function () {
['Landscape', 'Portrait'].forEach(function (orientation) {
[1, 2, 3, 4, 5, 6, 7, 8].forEach(function (exifTag) {
const input = fixtures[`inputJpgWith${orientation}Exif${exifTag}`];
const expectedOutput = fixtures.expected(`${orientation}_${exifTag}-out.jpg`);
it(`Auto-rotate ${orientation} image with EXIF Orientation ${exifTag}`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600];
sharp(input)
.rotate()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
['autoOrient', 'constructor'].forEach(function (rotateMethod) {
describe(`Auto orientation via ${rotateMethod}:`, () => {
const options = rotateMethod === 'constructor' ? { autoOrient: true } : {};
['Landscape', 'Portrait'].forEach(function (orientation) {
[1, 2, 3, 4, 5, 6, 7, 8].forEach(function (exifTag) {
const input = fixtures[`inputJpgWith${orientation}Exif${exifTag}`];
const expectedOutput = fixtures.expected(`${orientation}_${exifTag}-out.jpg`);
it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600];
const img = sharp(input, options);
rotateMethod === 'autoOrient' && img.autoOrient();
img.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
});
it(`Auto-rotate then resize ${orientation} image with EXIF Orientation ${exifTag}`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [320, 240] : [320, 427];
sharp(input)
.rotate()
.resize({ width: 320 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then resize`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape' ? [320, 240] : [320, 427];
const img = sharp(input, options);
rotateMethod === 'autoOrient' && img.autoOrient();
img.resize({ width: 320 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
});
it(`Resize then auto-rotate ${orientation} image with EXIF Orientation ${exifTag}`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape'
? (exifTag < 5) ? [320, 240] : [320, 240]
: [320, 427];
sharp(input)
.resize({ width: 320 })
.rotate()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
if (rotateMethod !== 'constructor') {
it(`${orientation} image with EXIF Orientation ${exifTag}: Resize then auto-rotate`, function (done) {
const [expectedWidth, expectedHeight] = orientation === 'Landscape'
? (exifTag < 5) ? [320, 240] : [320, 240]
: [320, 427];
const img = sharp(input, options)
.resize({ width: 320 });
rotateMethod === 'autoOrient' && img.autoOrient();
img.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
}
[true, false].forEach((doResize) => {
[90, 180, 270, 45].forEach(function (angle) {
const [inputWidth, inputHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600];
const expectedOutput = fixtures.expected(`${orientation}_${exifTag}_rotate${angle}-out.jpg`);
it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then rotate ${angle} ${doResize ? 'and resize' : ''}`, function (done) {
const [width, height] = (angle === 45 ? [742, 742] : [inputWidth, inputHeight]).map((x) => doResize ? Math.floor(x / 1.875) : x);
const [expectedWidth, expectedHeight] = angle % 180 === 0 ? [width, height] : [height, width];
const img = sharp(input, options);
rotateMethod === 'autoOrient' && img.autoOrient();
img.rotate(angle);
doResize && img.resize(expectedWidth);
img.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, expectedWidth);
assert.strictEqual(info.height, expectedHeight);
fixtures.assertSimilar(expectedOutput, data, done);
});
});
});
[[true, true], [true, false], [false, true]].forEach(function ([flip, flop]) {
const [inputWidth, inputHeight] = orientation === 'Landscape' ? [600, 450] : [450, 600];
const flipFlopFileName = [flip && 'flip', flop && 'flop'].filter(Boolean).join('_');
const flipFlopTestName = [flip && 'flip', flop && 'flop'].filter(Boolean).join(' & ');
it(`${orientation} image with EXIF Orientation ${exifTag}: Auto-rotate then ${flipFlopTestName} ${doResize ? 'and resize' : ''}`, function (done) {
const expectedOutput = fixtures.expected(`${orientation}_${exifTag}_${flipFlopFileName}-out.jpg`);
const img = sharp(input, options);
rotateMethod === 'autoOrient' && img.autoOrient();
flip && img.flip();
flop && img.flop();
doResize && img.resize(orientation === 'Landscape' ? 320 : 240);
img.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(info.width, inputWidth / (doResize ? 1.875 : 1));
assert.strictEqual(info.height, inputHeight / (doResize ? 1.875 : 1));
fixtures.assertSimilar(expectedOutput, data, done);
});
});
});
});
});
});
});
});
@@ -372,12 +436,36 @@ describe('Rotation', function () {
let warningMessage = '';
const s = sharp();
s.on('warning', function (msg) { warningMessage = msg; });
s.rotate();
s.rotate(90);
assert.strictEqual(warningMessage, '');
s.rotate();
s.rotate(180);
assert.strictEqual(warningMessage, 'ignoring previous rotate options');
});
it('Multiple rotate: last one wins (cardinal)', function (done) {
sharp(fixtures.inputJpg)
.rotate(45)
.rotate(90)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(2225, info.width);
assert.strictEqual(2725, info.height);
done();
});
});
it('Multiple rotate: last one wins (non cardinal)', function (done) {
sharp(fixtures.inputJpg)
.rotate(90)
.rotate(45)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(3500, info.width);
assert.strictEqual(3500, info.height);
done();
});
});
it('Flip - vertical', function (done) {
sharp(fixtures.inputJpg)
.resize(320)
@@ -546,4 +634,11 @@ describe('Rotation', function () {
assert.strictEqual(width, 3);
assert.strictEqual(height, 6);
});
it('Invalid autoOrient throws', () =>
assert.throws(
() => sharp({ autoOrient: 'fail' }),
/Expected boolean for autoOrient but received fail of type string/
)
);
});