diff --git a/src/global-options.js b/src/global-options.js index 05f87e0..8d7f6ba 100644 --- a/src/global-options.js +++ b/src/global-options.js @@ -8,6 +8,8 @@ const DEFAULTS = { widths: ["auto"], formats: ["webp", "jpeg"], // "png", "svg", "avif" + formatFiltering: ["transparent", "animated"], + // Via https://github.com/11ty/eleventy-img/issues/258 concurrency: Math.min(Math.max(8, os.availableParallelism()), 16), diff --git a/src/image.js b/src/image.js index d911bfe..87421ef 100644 --- a/src/image.js +++ b/src/image.js @@ -38,6 +38,19 @@ const ANIMATED_TYPES = [ "gif", ]; +const TRANSPARENCY_TYPES = [ + "avif", + "png", + "webp", + "gif", + "svg", +]; + +const MINIMUM_TRANSPARENCY_TYPES = [ + "png", + "gif", + "svg", +]; class Image { #input; @@ -191,7 +204,7 @@ class Image { return valid.sort((a, b) => a - b); } - static getFormatsArray(formats, autoFormat, svgShortCircuit, isAnimated) { + static getFormatsArray(formats, autoFormat, svgShortCircuit, isAnimated, hasTransparency) { if(formats && formats.length) { if(typeof formats === "string") { formats = formats.split(","); @@ -233,6 +246,18 @@ class Image { } } + if(hasTransparency) { + let minimumValidTransparencyFormats = formats.filter(f => MINIMUM_TRANSPARENCY_TYPES.includes(f)); + // override formats if a valid animated format is found, otherwise leave as-is + if(minimumValidTransparencyFormats.length > 0) { + let validTransparencyFormats = formats.filter(f => TRANSPARENCY_TYPES.includes(f)); + debug("Filtering non-transparency-friendly formats from output: from %o to %o", formats, validTransparencyFormats); + formats = validTransparencyFormats; + } else { + debug("At least one transparency-friendly output format of %o must be included if the source image has an alpha channel, skipping formatFiltering and using original formats.", MINIMUM_TRANSPARENCY_TYPES); + } + } + // Remove duplicates (e.g., if null happens to coincide with an explicit format // or a user passes in multiple duplicate values) formats = [...new Set(formats)]; @@ -477,6 +502,11 @@ class Image { return metadata?.pages > 1; } + hasAlpha(metadata) { + console.log( {metadata} ); + return true; + } + getEntryFormat(metadata) { return metadata.format || this.options.overrideInputFormat; } @@ -485,9 +515,10 @@ class Image { // src is used to calculate the output file names getFullStats(metadata) { let results = []; - let isImageAnimated = this.isAnimated(metadata); + let isImageAnimated = this.isAnimated(metadata) && Array.isArray(this.options.formatFiltering) && this.options.formatFiltering.includes("animated"); + let hasAlpha = metadata.hasAlpha && Array.isArray(this.options.formatFiltering) && this.options.formatFiltering.includes("transparent"); let entryFormat = this.getEntryFormat(metadata); - let outputFormats = Image.getFormatsArray(this.options.formats, entryFormat, this.options.svgShortCircuit, isImageAnimated); + let outputFormats = Image.getFormatsArray(this.options.formats, entryFormat, this.options.svgShortCircuit, isImageAnimated, hasAlpha); if (this.needsRotation(metadata.orientation)) { [metadata.height, metadata.width] = [metadata.width, metadata.height]; diff --git a/test/david-mascot.png b/test/david-mascot.png new file mode 100644 index 0000000..c7d8db7 Binary files /dev/null and b/test/david-mascot.png differ diff --git a/test/test.js b/test/test.js index 3a59ee4..05ba93b 100644 --- a/test/test.js +++ b/test/test.js @@ -1256,3 +1256,24 @@ test("Keep ICC Profiles by default #244 test image from Sharp repo", async t => t.true(Buffer.isBuffer(outputMetadata.icc)); t.true(outputMetadata.hasProfile); }); + +test("#105 Transparent format output filtering", async t => { + let stats = await eleventyImage("./test/david-mascot.png", { + dryRun: true, + formats: ["png", "avif", "jpeg"], + useCache: false, + }); + + t.deepEqual(Object.keys(stats), ["png", "avif"]); +}); + +test("#105 Transparent format output filtering (no minimum transparency formats found)", async t => { + let stats = await eleventyImage("./test/david-mascot.png", { + dryRun: true, + useCache: false, + }); + + // even though webp is transparency-friendly, we still use the full originally formats because webp is not a sufficiently minimum format for transparency + // must include one of: svg, png, or gif + t.deepEqual(Object.keys(stats), ["webp", "jpeg"]); +});