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.
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.