💾 Archived View for wilw.capsule.town › log › 2021-07-06-resizing-images.gmi captured on 2023-09-08 at 16:08:31. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-04-19)

-=-=-=-=-=-=-

🏡 Home

Back to gemlog

How to resize images client-side in your webapps

Posted on 06 July 2021

The problem

Image processing and resizing is a common task in many types of applications. This is made even more important by modern phones that take photos several megabytes in size.

For example, if you offer an application that allows people to choose an avatar image, you won't want to render the full multi-MB size image each time it's shown. This is extra data for your users to download each time (which costs them both time and money, and can give a poor sluggish experience) and also means you need to fork out more cash for the additional bandwidth usage. If you have lots of users, then this time/money saving can be amplified significantly.

Lots of tools exist for server-side processing. However this may mean you need to invest in infrastructure to handle asynchronous or scheduled server-side processing, or additional compute capacity to process image uploads on-the-fly.

I'm a big fan of letting the users' clients do more of the work, since this helps to distribute the compute power required. Allowing your user clients to upload files directly to a service such as S3 is often quicker and means you don't need to provision and pay for the bigger server capacity yourself to deal with centralised processing of image files.

So, to return to the avatar example above, what if your web front-end can instead resize the image before it is uploaded? This means you don't need to do the additional server-side processing, you store less data, and the process is a little more _predictable_.

Client-side JavaScript resizing

Modern browsers - both on desktops and mobile devices - are more than capable of doing a bit of additional work. For image-resizing, as you might have guessed, this involves making use of the HTML `canvas` element.

Essentially, the process involves the following steps:

1. Read in the user-chosen file (e.g. from a file chooser) as a data URL.

1. Load the image from the file.

1. Determine the correct dimensions for the image.

1. Draw the image to the canvas.

1. Build a new file based on the canvas.

The code below should help illustrate this further.

// Allow the caller to specify the file and a max width/height for the file
const resizeImage = (file, maxWidth, maxHeight) => {
  return new Promise((resolve, reject) => {
    // Create a new FileReader
    const reader = new FileReader();

    // Once the FileReader is ready...
    reader.onload = e => {
      // Create a new image
      const img = new Image();

      // Once the image is ready...
      img.onload = () => {
        // Create a new canvas
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        // Determine the new image dimensions based on maxWidth and maxHeight
        let width = img.width;
        let height = img.height;
        if (width > height) {
          if (width > maxWidth) {
            height *= maxWidth / width;
            width = maxWidth;
          }
        } else {
          if (height > maxHeight) {
            width *= maxHeight / height;
            height = maxHeight;
          }
        }
        canvas.width = width;
        canvas.height = height;

        // Draw the image to the canvas with the new sizes
        ctx.drawImage(img, 0, 0, width, height);

        // Build and return the resized image as an image file
        canvas.toBlob(blob => {
          resolve(new File([blob], file.name));
        }, 'image/jpeg', 1);
      }

      // Begin to load the image
      img.src = e.target.result;
    }

    // Begin to load the file to the FileReader
    reader.readAsDataURL(file);
  });
}

// Elsewhere in your code (e.g. after the user has selected an image file)
const resizedFile = await resizeImage(file, 400, 400);

// Now you can upload the resized image, etc....

At this point you can upload the `resizedFile` directly to a storage provider, such as Amazon S3. In case it helps, there is more information on the direct-to-S3 file upload process in this article [1].

1

If you care less about storage cost and would prefer to store multiple image sizes anyway (in case you want to offer users the chance to view the full-sized avatar too), then you can upload both `file` and `resizedFile`. You can then choose to serve one or the other in different scenarios.

Either way, the key thing is that you don't need any extra image-processing on the server. I'm sure there are more efficient methods than this in practice, but if you're looking for a simple, no-dependency approach then I hope this might help!

Reply via email

Back to gemlog