Django – Optimise images before upload to AWS
Django – Optimise images before upload to AWS

Django – Optimise images before upload to AWS

Image uploads are problematic to store if the files are too big. Our requirements in this case were:

  • convert uploaded images to JPG before upload to S3 server
  • resize images homothetically before upload to S3 server
  • reduce image quality and image file size before uploading them to the S3 server
  • Additionally, create some thumbnails of the uploaded image with a specific naming. I am not going into details about the thumbnail implementation, but I have kept some remnants of it in the article as an illustration that you can do more processing on these images before uploading.
Lena – the most famous face of image optimisation

Prerequisites:

django-storages, PIL

Settings:

in settings.py add DEFAULT_FILE_STORAGE = 'web.utils.storage.MediaStorage'

create the file storage.py under web/utils (this can be different, but it has to match the DEFAULT_FILE_STORAGE setting)

In storage.py

import io
import os
from PIL import Image
from storages.backends.s3boto3 import S3Boto3Storage

from web.utils.images import open_or_convert, homothetical_transformation

class MediaStorage(S3Boto3Storage):
    location = 'media'
    default_acl = 'public-read'
    file_overwrite = False

    def _save(self, name, content):
        if hasattr(content, 'content_type') and content.content_type.startswith('image/'):  # noqa
            return self.generate_thumbnails(name, content)
        else:
            return super()._save(name, content)

    def _save_image(self, picture, filename, quality=100):
        fh = self.open(filename, 'wb')
        sfile = io.BytesIO()

        picture.save(sfile, format='jpeg', quality=quality)

        fh.write(sfile.getvalue())
        fh.close()

    def generate_thumbnails(self, name, content):
        _, ext = os.path.splitext(filename).lower()
        pic = Image.open(content)
        
        # thumbnails or any other extra images to be saved
        # thumbnail = ... e.g. PIL processing to create a square thumbnail
        # filename = ... e.g. thumbnail naming rules
        # self._save_image(thumbnail, filename)

        # main image (homothetical thumbnail)
        name = name.replace(ext, '.jpg')
        main_image = homothetical_transformation(pic)
        self._save_image(main_image, name, 85)

        return name

In web/utils/images.py

from django.conf import settings
from PIL import Image, ImageOps

def open_or_convert(content, ext):
    return Image.open(content)

def homothetical_transformation(pic, new_width=750):
    new_height = int(new_width * pic.height / pic.width)
    result = pic.copy()
    result = ImageOps.exif_transpose(result)
    result = convert_to_rgb(result)
    result.thumbnail((new_width, new_height))

    return result

def convert_to_rgb(image):
    if image.mode == 'P':
        image = image.convert('RGB')
        return image

    if image.mode not in ['RGBA', 'LA']:
        return image

    background = Image.new("RGB", image.size, (255, 255, 255))
    background.paste(image, mask=image.split()[3])  # better handling for transparent images

    return background

Transparency handling is fully explained here.