Optimize Images prior to CloudPage Upload


Introduction

SFMC Expert and fellow Marketing Champion Zuzanna Jarczynska has an amazing blog post on how to Create a CloudPages form with an image/file upload option. I’m sure that the information on that page has been used hundreds or thousands of times to make clients happy in the Salesforce Marketing Cloud ecosystem.

The Problem

The generally accepted best practice is to utilize Base64 encoding in order to upload image file via the page and then passing it through a REST API Call on a helper page. The problem is actually in that Base64 encoding–the size of the file gets much larger than it was, just by virtue of the Base64 encoding. Where typically, I’d expect a filesize jump of about 33%, I was seeing a filesize jump of just over 200%. So a 2.1MB File became 4.7MB using Base64 encoding through the CloudPage platform. That’s normally not an issue, but there is a limit around the 5MB mark, and for my use case, uploading pictures of forms, many reasonable image files were just too big to upload, and we didn’t need super high resolution files.

Our options

  1. Try to get binary file imports to work (challenging rework)
  2. Split the Base64 encoding up, try to join them back together somewhere.
  3. Just tell the clients that they would have to make sure their files were less than about 2.2MB in order to work.
  4. Build a image resize/refactor engine into the CloudPage.

So we tried out option 4, and I can’t believe how well it works!

The code

function processAndCompressImage(file, formdata) {
  const MAX_WIDTH = 1024; // Maximum width for the resized image
  const MAX_HEIGHT = 1024; // Maximum height for the resized image
  const QUALITY = 0.8; // Compression quality (0 to 1)

  const reader = new FileReader();

  reader.onload = function (event) {
    const img = new Image();
    img.onload = function () {
      // Create a canvas element
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d");

      // Calculate new dimensions while maintaining the aspect ratio
      let width = img.width;
      let height = img.height;

      if (width > MAX_WIDTH || height > MAX_HEIGHT) {
        if (width > height) {
          height *= MAX_WIDTH / width;
          width = MAX_WIDTH;
        } else {
          width *= MAX_HEIGHT / height;
          height = MAX_HEIGHT;
        }
      }

      canvas.width = width;
      canvas.height = height;

      // Draw the image on the canvas
      ctx.drawImage(img, 0, 0, width, height);

      // Convert the canvas to a Base64 string
      const compressedBase64 = canvas.toDataURL("image/jpeg", QUALITY).split(",")[1];

      // Upload the compressed image
      uploadFileToServer(compressedBase64, file.name.split(".")[0], "jpeg", formdata);
    };

    img.onerror = function () {
      showNotification("Error processing the image. Please try again.", "error");
    };

    img.src = event.target.result;
  };

  reader.onerror = function () {
    showNotification("Error reading the file. Please try again.", "error");
  };

  reader.readAsDataURL(file); // Read the file as a Data URL
}

How it works:


1. Read the File: The function begins by using the FileReader API to load the selected file as a Base64 string.

reader.readAsDataURL(file);


2. Load the Image: The Base64 data is assigned to an img element. The onload event ensures the image is fully loaded before processing.

img.src = event.target.result;

3. Resize the Image:

• The function calculates the new dimensions while maintaining the aspect ratio.

• If the image exceeds the MAX_WIDTH or MAX_HEIGHT, the dimensions are scaled down proportionally.

if (width > MAX_WIDTH || height > MAX_HEIGHT) {
  if (width > height) {
    height *= MAX_WIDTH / width;
    width = MAX_WIDTH;
  } else {
    width *= MAX_HEIGHT / height;
    height = MAX_HEIGHT;
  }
}

4. Compress the Image:

• A <canvas> element is used to draw the resized image.

• The toDataURL method converts the canvas content to a compressed Base64 string with the specified QUALITY.

const compressedBase64 = canvas.toDataURL("image/jpeg", QUALITY).split(",")[1];

5. Upload the File:

• The compressed image is sent to the server using a separate function, uploadFileToServer, which handles the upload logic.

uploadFileToServer(compressedBase64, file.name.split(".")[0], "jpeg", formdata);


Key Parameters You Can Adjust

• MAX_WIDTH and MAX_HEIGHT: Define the maximum dimensions of the resized image. Adjust these values based on your application’s needs.

• QUALITY: Controls the compression level. A value closer to 1 means better quality but larger file size.

Final Thoughts

Resizing and compressing images before upload can significantly enhance your application’s performance and user experience. The processAndCompressImage function offers a lightweight and effective solution for achieving this.

If you’re working with large files or need more advanced processing, you might consider integrating third-party libraries like Pica.js or server-side optimization tools.

Feel free to tweak the parameters and use this code in your projects. Let me know in the comments if you have any questions or enhancements!

Leave a Comment

Your email address will not be published. Required fields are marked *

Shopping Cart