Skip to content

Commit

Permalink
Merge pull request #194 from GHF/always-rotate
Browse files Browse the repository at this point in the history
Bake input orientation into output image
  • Loading branch information
zachleat authored Jan 15, 2024
2 parents 0d6d4b3 + e53f71b commit 3662d92
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 17 deletions.
29 changes: 14 additions & 15 deletions img.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,10 @@ class Image {
}

// https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
// Orientation 5 to 8 means width/height are flipped
needsRotation(orientation) {
return orientation >= 5;
// Orientations 5 to 8 mean image is rotated ±90º (width/height are flipped)
isQuarterTurn(orientation) {
// Sharp's metadata API exposes undefined EXIF orientations >8 as 1 (normal) but check anyways
return orientation >= 5 && orientation <= 8;
}

// metadata so far: width, height, format
Expand All @@ -450,11 +451,8 @@ class Image {
let results = [];
let outputFormats = Image.getFormatsArray(this.options.formats, metadata.format || this.options.overrideInputFormat, this.options.svgShortCircuit);

if (this.needsRotation(metadata.orientation)) {
let height = metadata.height;
let width = metadata.width;
metadata.width = height;
metadata.height = width;
if (this.isQuarterTurn(metadata.orientation)) {
[metadata.height, metadata.width] = [metadata.width, metadata.height];
}

if(metadata.pageHeight) {
Expand Down Expand Up @@ -541,21 +539,22 @@ class Image {
}

let sharpInstance = sharpImage.clone();
// Output images do not include orientation metadata (https://github.com/11ty/eleventy-img/issues/52)
// Use sharp.rotate to bake orientation into the image (https://github.com/lovell/sharp/blob/v0.32.6/docs/api-operation.md#rotate):
// > 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 without an angle will remove the EXIF Orientation tag, if any.
if(this.options.fixOrientation || this.needsRotation(metadata.orientation)) {
sharpInstance.rotate();
}
if(stat.width < metadata.width || (this.options.svgAllowUpscale && metadata.format === "svg")) {
let resizeOptions = {
width: stat.width
};
if(metadata.format !== "svg" || !this.options.svgAllowUpscale) {
resizeOptions.withoutEnlargement = true;
}
if(this.options.fixOrientation || this.needsRotation(metadata.orientation)) {
sharpInstance.rotate();
}

sharpInstance.resize(resizeOptions);
} else if (metadata.format !== "svg") {
if(this.options.fixOrientation || stat.width === metadata.width && this.needsRotation(metadata.orientation)) {
sharpInstance.rotate();
}
}

if(!this.options.dryRun) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"@11ty/eleventy": "^2.0.1",
"@11ty/eleventy-plugin-webc": "^0.11.1",
"ava": "^5.3.1",
"eslint": "^8.52.0"
"eslint": "^8.52.0",
"pixelmatch": "^5.3.0"
},
"ava": {
"failFast": false,
Expand Down
Binary file added test/exif-Landscape_15.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/exif-Landscape_3-bakedOrientation-200.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/exif-Landscape_3-bakedOrientation.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/exif-Landscape_3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 42 additions & 1 deletion test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const test = require("ava");
const fs = require("fs");
const { URL } = require("url");
const eleventyImage = require("../");
const sharp = require("sharp");
const pixelmatch = require('pixelmatch');

// Remember that any outputPath tests must use path.join to work on Windows

Expand Down Expand Up @@ -846,6 +848,32 @@ test("Maintains orientation #132", async t => {
});

// Broken test cases from https://github.com/recurser/exif-orientation-examples
test("#158: Test EXIF orientation data landscape (3)", async t => {
let stats = await eleventyImage("./test/exif-Landscape_3.jpg", {
widths: [200, "auto"],
formats: ['auto'],
useCache: false,
dryRun: true,
});

t.is(stats.jpeg.length, 2);
t.is(stats.jpeg[0].width, 200);
t.is(stats.jpeg[1].width, 1800);
t.is(Math.floor(stats.jpeg[0].height), 133);
t.is(stats.jpeg[1].height, 1200);

// This orientation (180º rotation) preserves image dimensions and requires an image diff
const readToRaw = async input => {
// pixelmatch requires 4 bytes/pixel, hence alpha
return sharp(input).ensureAlpha().toFormat(sharp.format.raw).toBuffer();
};
for (const [inSrc, outStat] of [["./test/exif-Landscape_3-bakedOrientation-200.jpg", stats.jpeg[0]], ["./test/exif-Landscape_3-bakedOrientation.jpg", stats.jpeg[1]]]) {
const inRaw = await readToRaw(inSrc);
const outRaw = await readToRaw(outStat.buffer);
t.is(pixelmatch(inRaw, outRaw, null, outStat.width, outStat.height, { threshold: 0.15 }), 0);
}
});

test("#132: Test EXIF orientation data landscape (5)", async t => {
let stats = await eleventyImage("./test/exif-Landscape_5.jpg", {
widths: [400, "auto"],
Expand Down Expand Up @@ -892,7 +920,20 @@ test("#132: Test EXIF orientation data landscape (8)", async t => {
widths: [400],
formats: ['auto'],
outputDir: "./test/img/",
// dryRun: true,
dryRun: true,
});

t.is(stats.jpeg.length, 1);
t.is(stats.jpeg[0].width, 400);
t.is(Math.floor(stats.jpeg[0].height), 266);
});

test("#158: Test EXIF orientation data landscape (15)", async t => {
let stats = await eleventyImage("./test/exif-Landscape_15.jpg", {
widths: [400],
formats: ['auto'],
outputDir: "./test/img/",
dryRun: true,
});

t.is(stats.jpeg.length, 1);
Expand Down

0 comments on commit 3662d92

Please sign in to comment.