Description
@zachleat This issue is a followup of: https://indieweb.social/@djenz/113782569330355460
Lets say we want to crop the following js api usecase to an aspect ratio of 1:1.
const stats = await Image('path/to/16x9.jpg', {
widths: [80, 160, 320, 512, 768, 1024, 1200, 1440, 1600, 1720, 1920, 2560, 3200],
formats: ['avif'],
});
We could resize path/to/16x9.jpg
beforehand and provide the buffer as src for the image api:
const aspectRatio = '1:1';
const [ratioWidth, ratioHeight] = aspectRatio.split(':').map(item => parseInt(item));
const sharpInstance = Sharp('path/to/16x9.jpg');
const {
width: originalWidth,
height: originalHeight,
} = await sharpInstance.metadata();
let cropWidth = originalWidth;
let cropHeight = originalHeight;
cropHeight = Math.floor(originalWidth / ratioWidth * ratioHeight);
if (cropHeight > originalHeight) {
cropWidth = Math.floor(originalHeight / ratioHeight * ratioWidth);
cropHeight = originalHeight;
}
sharpInstance.resize({
width: cropWidth,
height: cropHeight,
fit: Sharp.fit.cover,
position: Sharp.strategy.entropy,
});
const src = await sharpInstance.toBuffer();
const stats = await Image(src, {
widths: [80, 160, 320, 512, 768, 1024, 1200, 1440, 1600, 1720, 1920, 2560, 3200],
formats: ['avif'],
});
This will work. But it has the disadvantage that it can not fully take advantage of the js api transformation cache. It would resize the original image each time you call this code (i.e. through a shortcode). With a lot of images this alone can take a long time, even though the follow up transformations are cached.
Now there is the transform
option, lets say we use it for illustration purpose in the same way:
const aspectRatio = '1:1';
const [ratioWidth, ratioHeight] = aspectRatio.split(':').map(item => parseInt(item));
const sharpInstance = Sharp('path/to/16x9.jpg');
const {
width: originalWidth,
height: originalHeight,
} = await sharpInstance.metadata();
let cropWidth = originalWidth;
let cropHeight = originalHeight;
cropHeight = Math.floor(originalWidth / ratioWidth * ratioHeight);
if (cropHeight > originalHeight) {
cropWidth = Math.floor(originalHeight / ratioHeight * ratioWidth);
cropHeight = originalHeight;
}
const stats = await Image(src, {
widths: [80, 160, 320, 512, 768, 1024, 1200, 1440, 1600, 1720, 1920, 2560, 3200],
formats: ['avif'],
transform: sharpInstance => sharpInstance.resize({
width: cropWidth,
height: cropHeight,
fit: Sharp.fit.cover,
position: Sharp.strategy.entropy,
},
});
It wouldn't work, because it will ignore the widths
property (size scale) now, as it will create 13 times an image with the same width/height -> cropWidth/cropHeight. As the transform
option is run once per resize (per width of the widths
size scale).
We could split it out into a loop, i.e.:
const stats = [];
for (const scaleWidth of [80, 160, 320, 512, 768, 1024, 1200, 1440, 1600, 1720, 1920, 2560, 3200]) {
stats.push(await Image(src, {
widths: [scaleWidth],
formats: ['avif'],
transform: sharpInstance => sharpInstance.resize({
width: scaleWidth,
height: Math.floor(scaleWidth / ratioWidth * ratioHeight),
fit: Sharp.fit.cover,
position: Sharp.strategy.entropy,
};
}));
}
This would only work if the src
is used with one aspect ratio (cropping) throughout the whole codebase, as the output filename would be the same no matter the size differences introduced through the transform
option (it is not taken into account by the 11ty img algorhythm that creates the filename hash). So different aspect ratio versions of the same src & width would overwrite each others out files.
crop
option
To omit all the extra custom code, i wish we would had something like this:
const stats = await Image('path/to/16x9.jpg', {
widths: [80, 160, 320, 512, 768, 1024, 1200, 1440, 1600, 1720, 1920, 2560, 3200],
formats: ['avif'],
crop: {
aspectRatio: 1 / 1,
position: Sharp.strategy.entropy,
});
Which does all the above automatically:
- crop the provided src image to the biggest possible "cover" portion (once, for the original image)
- calculate the cropping (height) for each width of the widths scale
transformOriginal
option
Or maybe a transformOriginal
option that is only run once for the original image provided through src
, and not on every resize of the widths scale like the transform
option now.
This might be better than the crop
option, as it is more general and would enable more usecases than only the cropping. But it would also still require some custom code, which could maybe provided through an example in the 11ty docs.
Other solutions?
But maybe there are other (better) ways, i haven't thought of? Ideas?
Thanks!