DEV Community

loading...
Cover image for Compress images in django

Compress images in django

Gajesh
Reading, Coding, and writing. In that order.
・4 min read

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
Enter fullscreen mode Exit fullscreen mode

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})
Enter fullscreen mode Exit fullscreen mode

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',)
Enter fullscreen mode Exit fullscreen mode

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')
]
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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/

Enter fullscreen mode Exit fullscreen mode

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.

Image before compression.
Image after compression.

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.

References:

  1. Wikipedia page on Data Compression
  2. Lossless compression further reading
  3. Lossy compression
  4. Django Docs for creating a simple Django App
  5. Image handling tutorial on GeeksForGeeks
  6. BytesIO Documentation
  7. Django’s Uploaded file handler

Discussion (8)

Collapse
johnpaul89 profile image
JoPa

Great Article, but when you encounter this error - cannot write mode RGBA as JPEG add this :-

imageTemproary = imageTemproary.convert('RGB')

Refrence to this issue : github.com/python-pillow/Pillow/is...

Collapse
hebertdev1 profile image
Hebert

hi thanks for writing this tutorial. but it only works when creating a new record, how does it work when editing the object?

Collapse
halsing profile image
halsing

thanks a lot for this <3 You've saved my project :)

Collapse
gajesh profile image
Gajesh Author

Your welcome. Happy to know it was helpful.

Collapse
yash2998chhabria profile image
yash2998chhabria

Thank you so much. Saved my site in production time

Collapse
hebertdev1 profile image
Hebert

hi thanks for writing this tutorial. but it only works when creating a new record, how does it work when editing the object?

Collapse
gusxtavoandrade profile image
Gustavo de Andrade

It was very helpful for me!
Thanks!

Collapse
gopeeey profile image
gopeeey

God bless you sir