Storage is one of the essential pieces of web app development and needs to be paid special attention as it attracts server costs. If you have to pay for the storage costs of your web apps that involve images or any other non-text content it is better to focus on compression to reduce storage costs and scale at ease. This post discusses the image compression (lossy) in Django in detail and demonstrates an example of its implementation. You can find all the implementation details here.
What is compression?
According to Wikipedia, data-compression involves encoding information using fewer bits than the original representation. To put it in simple words, compression is a method of making the file size smaller using a specific technique or algorithm.
Types and methods Data-compression is classified into two types.
Lossy: In this type of compression, the quality of data(in this case images and video) is reduced on compression. This is widely used to compress multimedia.
Lossless: In this type of compression, the quality of data(in this case image) is not lost. This is in wide use to compress sensitive data where data loss cannot be afforded.
Installation and Configuration
Install the python image handling library Pillow
pip install Pillow
We will set up a simple project to demonstrate the image upload and the file sizes of before and after upload. You can also refer to Django-docs or download the completed source code here.
Our models.py looks like,
import sys
from django.db import models
from PIL import Image
from io import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
class Upload(models.Model):
nameImage = models.CharField(max_length = 140,blank=False,null=True)
uploadedImage = models.ImageField(upload_to = 'Upload/',blank=False,null=True)
def save(self, *args, **kwargs):
if not self.id:
self.uploadedImage = self.compressImage(self.uploadedImage)
super(Upload, self).save(*args, **kwargs)
def compressImage(self,uploadedImage):
imageTemproary = Image.open(uploadedImage)
outputIoStream = BytesIO()
imageTemproaryResized = imageTemproary.resize( (1020,573) )
imageTemproary.save(outputIoStream , format='JPEG', quality=60)
outputIoStream.seek(0)
uploadedImage = InMemoryUploadedFile(outputIoStream,'ImageField', "%s.jpg" % uploadedImage.name.split('.')[0], 'image/jpeg', sys.getsizeof(outputIoStream), None)
return uploadedImage
In the above models.py
file we declare a utility method of the name compressImage
and pass it the uploadedImage
as a parameter. This method is called in save
module while creating the Upload
object.
We use a python image handling library Pillow(PIL) to handle image processing. We open the uploaded image and store it in a temporary object imageTemproary
and we initialize a BytesIO
stream to handle writing the changesto the image. We use the resize()
method to resize the uploaded image to a particular size (lesser than the original size in this case) and reset the output stream pointer stream to initial position using seek()
method. We can further use django's InMemoryUploadedFile
to overwrite the existing uncompressed image.
You can go ahead and customize the method to change the image type, size, and quality level according to your needs.
Our views.py looks like,
from django.shortcuts import render
from CompressUpload.models import Upload
from CompressUpload.forms import imageUploadForm
def displayUploadedFiles(request):
uploadImageList = Upload.objects.all()
return render(request, 'CompressUpload/list.html', {'uploadImageList': uploadImageList})
def uploadImage(request):
imageUploadFormResult = imageUploadForm(request.POST, request.FILES)
if request.method == 'POST':
if imageUploadFormResult.is_valid():
imageUploadFormResult.save()
else:
return render(request, 'CompressUpload/list.html', {'imageUploadFormResult': imageUploadFormResult})
return render(request, 'CompressUpload/upload.html', {'imageUploadFormResult': imageUploadFormResult})
our forms.py looks like,
from django import forms
from CompressUpload.models import Upload
class imageUploadForm(forms.ModelForm):
class Meta:
model = Upload
fields = ('Name','uploadedImage',)
Our urls.py (of the app in which models.py is present)
from django.conf.urls import url, include
from CompressUpload.views import displayUploadedFiles , uploadImage
urlpatterns = [
url(r'^list/$',displayUploadedFiles,name='List Uploaded Images'),
url(r'^upload/$',uploadImage,name='Upload Images')
]
An example of upload.html
in the templates
directory
<!DOCTYPE html>
<html>
<head>
<title>Upload Image</title>
</head>
<body>
<form id="create_podcast_form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ imageUploadFormResult.as_p }}
<input type="submit" value="Submit"/>
</form>
</body>
</html>
The settings.py must contain the usual static
and media
settings to store the uploaded file. The setting given below is recommended.
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.normpath(os.path.join(BASE_DIR, 'templates')),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.request',
],
},
},
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/
Here’s an example image being uploaded, note that the size before the upload is 179.4 KB and after compressing and resizing is 83.9 KB. This can make a significant difference when it comes to files of larger sizes at scale.
Note on resizing and Quality tradeoff:
In this compression, we have performed a lossy compression to reduce the image size, and that comes with a quality trade-off. You can try different values (from 1–100) to suit your needs and also resize the image as per its use. To state an example, we can reduce the size of images that are only used in thumbnails to the size of the thumbnail allowed on the site. Further reading on this topic can be found in the References.
You can read more of my articles and follow me on Medium or Twitter.
Top comments (8)
Great Article, but when you encounter this error - cannot write mode RGBA as JPEG add this :-
Refrence to this issue : github.com/python-pillow/Pillow/is...
hi thanks for writing this tutorial. but it only works when creating a new record, how does it work when editing the object?
thanks a lot for this <3 You've saved my project :)
Your welcome. Happy to know it was helpful.
Thank you so much. Saved my site in production time
hi thanks for writing this tutorial. but it only works when creating a new record, how does it work when editing the object?
It was very helpful for me!
Thanks!
God bless you sir