Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/global-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const getDefaults = () => ({
sharpPngOptions: {}, // options passed to the Sharp png output method
sharpJpegOptions: {}, // options passed to the Sharp jpeg output method
sharpAvifOptions: {}, // options passed to the Sharp avif output method
sharpResizeOptions: {}, // options passed to the Sharp resize method

formatHooks: {
svg: svgHook,
Expand Down
42 changes: 29 additions & 13 deletions src/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,24 @@ export default class Image {
return {};
}

getSharpResizeOptions(stat, metadata) {
let resizeOptions = {
...this.options.sharpResizeOptions,
};

// Eleventy Image owns output dimensions. Allowing these here would make
// returned metadata disagree with the output image.
delete resizeOptions.width;
delete resizeOptions.height;
resizeOptions.width = stat.width;

if(!("withoutEnlargement" in resizeOptions) && (metadata.format !== "svg" || !this.options.svgAllowUpscale)) {
resizeOptions.withoutEnlargement = true;
}

return resizeOptions;
}

async getInput() {
// internal cache
if(!this.#input) {
Expand Down Expand Up @@ -415,14 +433,21 @@ export default class Image {
"sharpWebpOptions",
"sharpPngOptions",
"sharpJpegOptions",
"sharpAvifOptions"
"sharpAvifOptions",
"sharpResizeOptions",
].sort();

let hashObject = {};
// The code currently assumes are keysToKeep are Object literals (see Util.getSortedObject)
for(let key of keysToKeep) {
if(this.options[key]) {
hashObject[key] = Util.getSortedObject(this.options[key]);
let options = this.options[key];

if(key === "sharpResizeOptions" && Object.keys(options).length === 0) {
continue;
}

if(options) {
hashObject[key] = Util.getSortedObject(options);
}
}

Expand Down Expand Up @@ -710,15 +735,7 @@ export default class Image {

if(!isTransformResize) {
if(stat.width < sharpMetadata.width || (this.options.svgAllowUpscale && sharpMetadata.format === "svg")) {
let resizeOptions = {
width: stat.width
};

if(sharpMetadata.format !== "svg" || !this.options.svgAllowUpscale) {
resizeOptions.withoutEnlargement = true;
}

sharpInstance.resize(resizeOptions);
sharpInstance.resize(this.getSharpResizeOptions(stat, sharpMetadata));
}
}

Expand Down Expand Up @@ -927,4 +944,3 @@ export default class Image {
return img.statsByDimensionsSync(width, height);
}
}

75 changes: 75 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,81 @@ test("widths array should be ignored in hashing", t => {
t.is(stats2.jpeg[1].url, "/img/KkPMmHd3hP-600.jpeg");
});

test("empty sharpResizeOptions should be ignored in hashing", t => {
let stats = eleventyImage.statsSync("./test/bio-2017.jpg", {
widths: [1280],
sharpResizeOptions: {}
});

t.is(stats.jpeg[0].url, "/img/KkPMmHd3hP-1280.jpeg");
});

test("dimension sharpResizeOptions should be included in hashing", t => {
let stats = eleventyImage.statsSync("./test/bio-2017.jpg", {
widths: [1280],
sharpResizeOptions: {
width: 10,
height: 10,
withoutEnlargement: true,
withoutReduction: true,
}
});

t.not(stats.jpeg[0].url, "/img/KkPMmHd3hP-1280.jpeg");
});

test("sharpResizeOptions should apply to resize output and hash", async t => {
let defaultStats = await eleventyImage("./test/bio-2017.jpg", {
widths: [64],
formats: ["jpeg"],
outputDir: "./test/img/",
dryRun: true,
useCache: false,
});

let stats = await eleventyImage("./test/bio-2017.jpg", {
widths: [64],
formats: ["jpeg"],
outputDir: "./test/img/",
dryRun: true,
useCache: false,
sharpResizeOptions: {
kernel: "nearest",
},
});

t.is(stats.jpeg[0].width, 64);
t.is(stats.jpeg[0].height, 42);
t.not(stats.jpeg[0].outputPath, path.join("test/img/KkPMmHd3hP-64.jpeg"));

let defaultRaw = await sharp(defaultStats.jpeg[0].buffer).ensureAlpha().toFormat(sharp.format.raw).toBuffer();
let nearestRaw = await sharp(stats.jpeg[0].buffer).ensureAlpha().toFormat(sharp.format.raw).toBuffer();

t.true(pixelmatch(defaultRaw, nearestRaw, null, stats.jpeg[0].width, stats.jpeg[0].height, { threshold: 0.15 }) > 0);
});

test("sharpResizeOptions should not override Eleventy Image dimensions", async t => {
let stats = await eleventyImage("./test/bio-2017.jpg", {
widths: [300],
formats: ["jpeg"],
outputDir: "./test/img/",
dryRun: true,
useCache: false,
sharpResizeOptions: {
width: 10,
height: 10,
kernel: "nearest",
},
});

let outputMetadata = await sharp(stats.jpeg[0].buffer).metadata();

t.is(stats.jpeg[0].width, 300);
t.is(stats.jpeg[0].height, 199);
t.is(outputMetadata.width, 300);
t.true(outputMetadata.height > 10);
});

test("statsSync and eleventyImage output comparison", async t => {
let statsSync = eleventyImage.statsSync("./test/bio-2017.jpg", {
widths: [399],
Expand Down