payload icon indicating copy to clipboard operation
payload copied to clipboard

feat: dynamic image resize

Open alexbechmann opened this issue 2 years ago • 3 comments

Description

This PR exposes an express route for all collections with upload configuration, which will return the image resized from the parameters from the query string.

The motivation for this change is to allow for a frontend to query for sizes that are not known beforehand and defined as named crops in the collection config.

Implemented in this PR

  • [x] Express route /resize/:id for all collections with upload configuration. Query parameters width, height, position, fit, background are passed to the image resizer and the resulting image is returned. e.g:

    /api/{collectionSlug}/resize/{id}?width=160&height=100&position=centre&fit=cover&background=white
    
  • [x] It accounts for the disableLocalStorage option in the upload config.

    • If false: The image is loaded from disk, resized and returned.

    • If true: The image is pulled from the url property, resized and returned. This only works if consumer has configured a url property, e.g. from an afterRead hook. This is the same property that is used by the payload dashboard to show the image correctly.

      import { AfterReadHook } from 'payload/dist/collections/config/types';
      
      const afterRead: AfterReadHook[] = [
        ({ doc }) => {
          doc.url = `https://.../${doc.filename}`;
        },
      ];
      
  • [x] I have read and understand the CONTRIBUTING.md document in this repository

Type of change

  • [x] New feature (non-breaking change which adds functionality)
  • [x] This change requires a documentation update

Checklist:

  • [ ] I have added tests that prove my fix is effective or that my feature works
  • [ ] Existing test suite passes locally with my changes
  • [ ] I have made corresponding changes to the documentation

alexbechmann avatar Jul 30 '22 17:07 alexbechmann

While I was working on my payload-webp plugin I was thinking of implementing this function as well, but then I stopped because of concerns of misuse of such function - anyone could spam a server with resizing requests resulting in cpu/mem/queue issues.

Did you consider the risks? If it's going to be merged maybe there could be placed some ways to prevent some at least like request limits, generate new size only if resolution is significantly different from any existing ones, verify origin (for example allow resize only when origin is one of CSFR domains), etc...

I'm thinking of similiar optimalization of the pre-set image sizes flow by not generating particular size until it is requested for the first time. Potentially saving huge amounts of disk space.

chladog avatar Aug 08 '22 16:08 chladog

Hi @chladog Those are some good points about the security which I had not considered. I like your suggestions & perhaps this should be enabled via a flag in the collection config?

I agree that it makes sense to compute the pre-set image sizes when requested instead of at upload time so that you are able to add or change them in the future without requiring the images to be re-uploaded & processed.

Then you would be able to request a named crop like:

/api/{collectionSlug}/resize/{id}?crop=my_pre_defined_crop_name

If it saves the resized image to disk, then it will also prevent computation for subsequent requests.

I also thought it would be useful if the editor could configure a focal point in the GUI that the resizing would use as a centre point.

alexbechmann avatar Aug 09 '22 20:08 alexbechmann

Just dropping my two cents here that this feature and especially the focal point idea would be phenomenal!

The fixed sizes are pretty limiting for something like a Hero Image, where the needed size is influenced directly by the client viewport width.

christian-reichart avatar Aug 18 '22 21:08 christian-reichart

Adding focal point, as well as crop/flip/rotation, would be a game changer over other headless cms

Rar9 avatar Oct 15 '22 03:10 Rar9

Agreed, crops and focal points would be awesome for sure.

niallobrien avatar Dec 01 '22 12:12 niallobrien

A different way to solve the security point is using HMAC, similar to for example Thumbor (HMAC-SHA256 should be used instead of HMAC-SHA1 though.) or Imaginary. This way, fully dynamic options are still an option. However this still isn't a way to have safe client influenced resizing.

GoldElysium avatar Dec 16 '22 13:12 GoldElysium

Hey all, the team and I have revisited this specific feature and have landed on the following thoughts:

  1. We are going to be revisiting our usage of Sharp entirely, and potentially abstracting it out into a separate package where its usage would be opt-in. Sharp is a big dependency, and lots of frontends now use third-party image manipulation libraries like Cloudinary or next/image to transform image sizes on-the-fly in a better way.
  2. If / when we remove Sharp from our dependencies, we will need to refactor this PR to support the "opt-in" Sharp method.
  3. This functionality could be added via plugin, as an additional endpoint for each upload-enabled collection. The plugin could expose its own suite of rate-limiting / protection mechanisms wherein the author could allow only requests from certain origins / requests with specific API keys / etc. to be considered valid. This would solve the bad-actor scenarios that we discussed above.

@alexbechmann what do you think about converting this to a plugin? And / or exposing options to allow users to leverage certain authentication / access mechanisms like API keys or whitelisted origin domains?

Looking forward to hearing what you think. In the interim, we are doing a bit of PR cleanup and I will close this one for now.

Once we make Sharp an opt-in dependency, I will revisit this and we can determine if this functionality does indeed belong in core. 👍

jmikrut avatar Apr 24 '23 16:04 jmikrut