11ty / eleventy-img

Utility to perform build-time image transformations.
https://www.11ty.dev/docs/plugins/image/
436 stars 55 forks source link

Add ability to crop images #31

Closed johnridesabike closed 2 years ago

johnridesabike commented 3 years ago

I just started using this plugin, and this is the main feature I’m missing the most.

Its use case is when you need your images to conform to specific aspect ratios, such as square thumbnails.

(Sure, you can accomplish this with CSS too, but I’d prefer cropping the images during the build process.)

Since the widths option is already an array, perhaps there could be a single aspectRatio option too? e.g. [1, 1] or [16, 9].

Bonus points if you could also pass all of the sharp resize options too, e.g. position: "center" https://sharp.pixelplumbing.com/api-resize

reubenlillie commented 3 years ago

@johnridesabike, I don’t have a solution per se, just a thought that I’m currently working through as I familiarize myself with this plugin too. I’ve got an dev site that I’m experimenting with passing URLs from Cloudinary as the src argument to Image(src) using this plugin. Again, I don’t know if it meets your use case, but I think there’s some value in keeping this plugin as a slim build-time tool. Then, if you want more sophisticated image transformations, you could use a remote service (and, in that sense, the remote caching features come in really handy) or another package that’s designed for those tasks.

anghelos commented 3 years ago

I'd also love the ability to crop just some of the given widths, to change the aspect ratio for mobile, for example.

szegheo commented 2 years ago

@zachleat can we ask priority for this? E.g. think just a full screen hero background image optimization for mobiles, usually 800x1200, when we don't know the dimensions of the user selected picture (by some headless cms) but we need to resize it to be exactly 1200px height.

I'm working on a tiny Webpack image loader based on eleventy-img (to reduce dependencies in my 11ty sites) which can be used in CSS like some.jpg?format=webp&height=1200 but currently only width can be used. FYI currently I'm using webpack-image-resize-loader (link) which is also using Sharp and can handle heights, but now it's an extra unnecessary dependency with a different version of Sharp. eleventy-img could be used for this task so nicely...

Thank you!

zeroby0 commented 2 years ago

I wonder if we can use sharpOptions: {} to pass arguments to Sharp and define a shortcode that does cropping and aspect ratio

szegheo commented 2 years ago

It seems to me that the sharpOptions of eleventy-img is for the Sharp constructor, and the resize parameters cannot be set there (fix me if I'm wrong).

However looking at the code of eleventy-img where it does the resizing (L437) sharpInstance.resize(resizeOptions) the resize options - what we would need to alter - are fixed.

Maybe a new option and a little modification something like this could work?:

let userSharpResizeOptions = this.options.sharpResizeOptions || {};

sharpInstance.resize({
  resizeOptions,
  ...userSharpResizeOptions
});

...then we would be able to use all the magic of https://sharp.pixelplumbing.com/api-resize#parameters (but it's just a sudden idea)

zeroby0 commented 2 years ago

Good eye! They are constructor options, I didn't notice that 😆

The name sharpResizeOptions feels right to me too. But the codebase's way to do || {} seems to be via Object.assign So maybe Object.assign({}, resizeOptions, this.options.sharpResizeOptions)? (I don't know how object.assign works, just writing this in hope some one will make a PR)

If someone does make a PR, please include the name in keysToKeep too

szegheo commented 2 years ago

This was once started in May 2020, the PR is still open but abandoned. Even sharpResizeOptions was recommended before in the last post. I don't know why this thing is still hanging 😞

@sprabowo are you still interested in 11ty? (seems to me he has switched to Astro)

architchandra commented 2 years ago

Please consider this. Cropping images at build time seems so much better than just using CSS methods from a performance perspective.

zeroby0 commented 2 years ago

The code has changed a lot since then, so I wouldn't really expect it to get merged. If there is no PR by Jan 1st, I'll draft one. Although I wouldn't expect it to be merged and shipped any time soon from that.

If you need the feature soon, I strongly recommend forking the repo and implementing sharpResizeOptions yourself. Then after you've tested it, consider contributing the improvements back with a PR :)

szegheo commented 2 years ago

Sure, I'm thinking of that, but also I'm absolutely certain that @zachleat could do this thing ways better than I could. I'm just a plain 11ty user and to be honest I would give my half hand for the JS skills that Zach has 😄 . Also as I read somewhere before, he said that issues with more likes will get more priority.

So if you are reading this, please press the like icon on the first post 👍 if you need this feature.

zachleat commented 2 years ago

I believe this is the top feature request right now but it’s still going into the queue. Thanks folks!

This repository is now using lodash style issue management for enhancements. This means enhancement issues will now be closed instead of leaving them open.

View the enhancement backlog here. Don’t forget to upvote the top comment with 👍!

mortendk commented 1 year ago

hmmm wonder if this kinda dies or somebody have a good idea to crop images ? custom sharp instead or

jeromecoupe commented 1 year ago

@mortendk I tend to use an external NPM script running in parallel to generate resized images. On bigger projects, I generally use external images services.

HerodoteVDR commented 11 months ago

@mortendk I figured sharp could be a good workaround, here is the Shortcode I added to get responsive picture generation in templating. (I'm an absolute 11ty noob so it might be really dirty coding but it worked out for me)


const Image = require("@11ty/eleventy-img");
const Sharp = require('sharp');

module.exports = function(eleventyConfig) {
    eleventyConfig.addShortcode("myImage", async function(src, alt, smallSize, midSize, bigSize) {
        try {
            console.log(`myImage shortcode called with src:`, src);

            const resizedImageBuffer = await Promise.all([
                resizeImage(src, smallSize[0], smallSize[1], "cover"),
                resizeImage(src, midSize[0], midSize[1], "cover"),
                resizeImage(src, bigSize[0], bigSize[1], "cover"),
            ]);

            const metadata = await Promise.all(
                resizedImageBuffer.map(async buffer => {
                    return await Image(buffer, {
                        formats: ["webp"],
                        outputDir: "./dist/assets/img/output",
                        urlPath: "/assets/img/output/",
                    });
                })
            );

            console.log("Generated image metadata:", metadata);

            let imageAttributes = {
                alt,
                loading: "lazy",
                decoding: "async",
                class: "o-fluidimage"
            };

            const imageHTML = 
                `<picture> 
                    <source media="(min-width: 1050px)" srcset="${metadata[2].webp[0].url}">
                    <source media="(min-width: 750px)" srcset="${metadata[1].webp[0].url}">
                    <img
                        class="${ imageAttributes.class }" 
                        alt="${ imageAttributes.alt }" 
                        loading="${ imageAttributes.loading }
                        decoding="${ imageAttributes.decoding }" 
                        src="${ metadata[0].webp[0].url }"
                    />
                </picture>`;

            console.log(`Generated image HTML:`, imageHTML);

            return imageHTML;
        } 
        catch (error) {
            console.error(`Error in myImage shortcode:`, error);
            return "";
        }
    });
}

async function resizeImage(src, width, height, mode) {
    return await Sharp(src)
        .resize({ width: width, height: height, fit: mode })
        .toBuffer();
}