by vault . 24 June 2018
Images are always a pain in the butt when it comes to the speed of the site. This would present a lot of problems for visiting users and eventually would decrease the users' loyalty. Hence, will leave a negative impact on visitors. What we could do is to compress these images to their lowest possible size either by decreasing their pixel quality or resizing them depending on the container's width.
We will use Pillow (PIL) library to create thumbnails and modify images. Now, this would require you the be familiar with Pillow library. I would suggest reading the pillow documentation. But now considering the shortage of time, process the below kick-start introduction:
A simple Pillow program
from PIL imort Image img = Image.open('image.jpg') img.convert( 'RGB' ) img.save( '/path/to/file.jpeg', format="JPEG", quality=80 ) img.close()
This was the pretty simple image program. We opened an image file, converted into RGB mode, save it on the disk with JPEG format and reduced it's quality to 80. After which we closed the file.
Some other Pillow behaviors:
img1 = img.copy() img.paste(img1)
This first one copy the image to variable image 1 while the second one paste back the data of img in img1.
img.resize( (width, height) )
This will resize the image to the provided width and height values.
img.thumbnail( (width, height) )
This will create thumbnail of an image. It's working is similar to resize(). The difference is that resize method perform operations on the actual image and commit the required changes. While thumbnail returns an instance of the modfied image on which further actions are done.
Take a head-start...
Whenever the Model instance change or any sort of alteration is encountered by the django workers, the save method from models.Model base class is executed. This method provides us the neccessary scope to alter the data before it gets saved on the database.
Come to your application models.py file and create a new model with two Image fields. Subsequently, subclass the save method from model.Model super class.
from django.models import models class Images(models.Model): image1 = models.ImageField( upload_to="images/" ) image2 = models.ImageField( upload_to="images/", null=True, blank=True, editable=False ) def save(self, *args, **kwargs): if self.image1: <...snipped...>
We created a non-editable extra image field (image2) in the model. This field will not be presented to user and initially be blank when save method is encountered. I created this extra field merely to store a thumbnail of different size. However, we are not going to mess with this right now neither we are gonna see it's usage in the rest of this tutorial. It's for you to test if you are able to do it perfectly as required.
Now, run migrations command and register the model to Admin Interface...
python manage.py makemigrations python manage.py migrate
from django.contrib import admin from . import models admin.site.register( models.Images )
So, now we have our environment setup. Come back to the models.py and let's see how we can compress images.
Remember that save method from the superclass models.Model will be executed everytime, if any of database change is committed with the exception of update method. If you are dealing with a large database or something that pretty big enough to mess with speed of the site, make sure you do know the use of update method. It will help in many of such cases where you want to update a field without calling the save method.
Coming towards save method, it gets called when the model fields are going to save. At this part, we can check and make changes as required to the populated fields. Lets see it a little closer:
from PIL import Image as PILImage from StringIO import StringIO class Images(models.Mode): ... def save(self, *args, **kwargs): if self.image1: img = PILImage.open( StringIO( self.image1.read() ) ) if img.mode != "RGB": img.convert('RGB') width, height = img.size #1 if width > 640 or height > 480: width, height = 640, 480 img.resize( (width, height), PILImage.ANTIALIAS ) #2 save_buff = StringIO() #3 img.save( save_buffer, format="JPEG", optimize=True, quality=70) #4 super( Images, self ).save( *args, **kwargs )
Above in the code, We first opened the file in a buffer and read image data from this memory block. We used buffer here because we are going to make changes to, let's say pretty large binary data and buffers are very useful while dealing with such data. At the second part, we converted the image into RGB mode. Further note these steps:
Saving the Image
Now, it's all just the matter of saving image to disk. Until now, our image is placed in a memory buffer. To save the image, we will use django InMemoryFileUploadedFile. Let's see it a more lively...
from PIL import Image as PILImage from StringIO import StringIO from django.core.files.uploadedfile import InMemoryUploadedFile class Images(models.Model): ... def save(self, *args, **kwargs): if self.image1: img = PILImage.open( StringIO( self.image1.read() ) ) if img.mode != "RGB": img.convert('RGB') width, height = img.size if width > 640 or height > 480: width, height = 640, 480 img.resize( (width, height), PILImage.ANTIALIAS ) save_buff = StringIO() img.save( save_buffer, format="JPEG", optimize=True, quality=70) self.image1 = InMemoryUploadedFile( save_buff, 'ImageField', self.Image.name.split( '.' ), 'image/jpeg', raw_space.len, None ) super( Images, self ).save( *args, **kwargs )
Now, everything's done. You can now test it. This is the before and after of an image is uploaded:
So, that's how we compressed the image using a few lines of code in Python. Well, this is for Django. But now you'll be able to implement the image compressions logic in your other programs as well.
We created two image fields. If you'd read all of this which I bet you didn't, I said I created that another field merely to store another copy of the uploaded image, i.e. of a different size or resolution. I'll leave that to you. Resize the image to 320x200 pixels and save that another copy to the image2 field and test yourself, if you really be able to do it or not.