diff --git a/package.json b/package.json index 81ce77f..a246793 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@eslint/js": "^9.17.0", "ava": "^6.2.0", "eslint": "^9.17.0", + "exifr": "^7.1.3", "globals": "^15.14.0", "pixelmatch": "^5.3.0" }, diff --git a/src/global-options.js b/src/global-options.js index 78c24e7..ad2596f 100644 --- a/src/global-options.js +++ b/src/global-options.js @@ -61,9 +61,6 @@ const DEFAULTS = { fixOrientation: false, // always rotate images to ensure correct orientation - // Removed, no longer necessary in v6.0 - // useCacheValidityInHash: true, - // When the original width is smaller than the desired output width, this is the minimum size difference // between the next smallest image width that will generate one extra width in the output. // e.g. when using `widths: [400, 800]`, the source image would need to be at least (400 * 1.25 =) 500px wide @@ -76,7 +73,11 @@ const DEFAULTS = { // writes to the file system and speeding up builds! transformOnRequest: false, - // v5 `extensions` was removed (option to override output format with new file extension), it wasn’t being used anywhere or documented + // operate on Sharp instance manually. + transform: undefined, + + // v5.0.0 Removed `extensions`, option to override output format with new file extension. It wasn’t being used anywhere or documented. + // v6.0.0, removed `useCacheValidityInHash: true` see https://github.com/11ty/eleventy-img/issues/146#issuecomment-2555741376 }; function getGlobalOptions(eleventyConfig, options, via) { diff --git a/src/image.js b/src/image.js index 1ce75b9..cf2a242 100644 --- a/src/image.js +++ b/src/image.js @@ -120,7 +120,13 @@ class Image { opts.__originalSize = fs.statSync(this.src).size; } - return JSON.stringify(opts); + return JSON.stringify(opts, function(key, value) { + // allows `transform` functions to be truthy for in-memory key + if (typeof value === "function") { + return ""; + } + return value; + }); } getFileContents(overrideLocalFilePath) { @@ -550,6 +556,15 @@ class Image { } let sharpInstance = sharpImage.clone(); + let transform = this.options.transform; + if(transform) { + if(typeof transform !== "function") { + throw new Error("Expected `function` type in `transform` option. Received: " + transform); + } + + await transform(sharpInstance); + } + // 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. diff --git a/test/exif-sample-large.jpg b/test/exif-sample-large.jpg new file mode 100644 index 0000000..839ecd5 Binary files /dev/null and b/test/exif-sample-large.jpg differ diff --git a/test/transform-hooks-test.js b/test/transform-hooks-test.js new file mode 100644 index 0000000..28409fa --- /dev/null +++ b/test/transform-hooks-test.js @@ -0,0 +1,33 @@ +const test = require("ava"); +const exifr = require("exifr"); + +const eleventyImage = require("../img.js"); + +test("Transforms Empty", async t => { + let stats = await eleventyImage("./test/exif-sample-large.jpg", { + formats: ["auto"], + // transform: undefined, + dryRun: true, + }); + + let exif = await exifr.parse(stats.jpeg[0].buffer); + t.deepEqual(exif, undefined); +}); + +test("Transforms keep exif", async t => { + let stats = await eleventyImage("./test/exif-sample-large.jpg", { + formats: ["auto"], + // Keep exif metadata + transform: (sharp) => { + sharp.keepExif(); + }, + dryRun: true, + }); + + let exif = await exifr.parse(stats.jpeg[0].buffer); + + t.is(Math.round(exif.latitude), 50); + t.is(Math.round(exif.longitude), 15); + t.is(exif.ApertureValue, 2); + t.is(exif.BrightnessValue, 9.38); +});