Home > database >  Fetch image and crop before save django
Fetch image and crop before save django

Time:04-02

I'm using django. I have a code that will parse an HTML code and try to get and save the image to the database. Here is the code:

link = content[content.find('src=') 5:content.find('alt')-2]
img_data = requests.get(link).content
with open('temp_image.jpg', 'wb') as handler:
    handler.write(img_data)
with open('temp_image.jpg', 'rb') as handler:
    file_name = link.split("/")[-1]
    post.cover.save(file_name, files.File(handler))
os.remove("temp_image.jpg")

But I also need to crop the image. How can I do that? Thank you.

CodePudding user response:

Add a save method to your database in models.py. I have added an example below which crops the image and stores the cropped file. This example crops the image but maintains the aspect ratio where the short side is 200 pixels. Change desired_height if you need it bigger or smaller.

'''
from PIL import Image
from django.core.files.uploadedfile import InMemoryUploadedFile

class Profile(models.Model):
    id = models.UUIDField(primary_key=True,default=uuid.uuid4,editable=False)
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    publish_status = models.BooleanField(max_length=1, null=True, default=False)
    hourly_rate = models.DecimalField(max_digits=6, decimal_places=0, null=True)
    first_name = models.CharField(max_length=30, null=True)
    last_name = models.CharField(max_length=30, null=True)
    address = models.CharField(max_length=100, null=True)
    birth_date = models.DateField(null=True, blank=True)
    bio = models.TextField(validators=[MaxLengthValidator(1200)])
    location = models.CharField(max_length=100, null=True)
    country = models.CharField(max_length=30, null=True)
    affiliation = models.CharField(max_length=100, null=True, blank=True)
    profile_image = models.ImageField(blank=False, upload_to='profile_images/', default='profile_images/avatar.png')
    cover_image = ResizedImageField(upload_to='cover_images/', size=[768, 432], crop=['middle', 'center'], quality=60,
                                    blank=False, default='cover_images/default_cover_image_1080.jpeg')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return '{} {}'.format(self.first_name, self.last_name)

    def save(self, *args, **kwargs):
        # Opening the uploaded image
        im = Image.open(self.profile_image)

        if im.mode == "JPEG":
            pass
        elif im.mode in ["RGBA", "P"]:
            im = im.convert("RGB")

        output = BytesIO()
        # Resize/modify the image - desired short edge of 200 pixels
        original_width, original_height = im.size

        if original_width > original_height:  # landscape image
            aspect_ratio = round(original_width / original_height, 2)
            desired_height = 200
            desired_width = round(desired_height * aspect_ratio)
            im = im.resize((desired_width, desired_height), Image.ANTIALIAS)

        elif original_height > original_width:  # portrait image
            aspect_ratio = round(original_height / original_width, 2)
            desired_width = 200
            desired_height = round(desired_width * aspect_ratio)
            im = im.resize((desired_width, desired_height), Image.ANTIALIAS)

        elif original_height == original_width:  # square image
            desired_width = 200
            desired_height = 200
            # Resize the image
            im = im.resize((desired_width, desired_height), Image.ANTIALIAS)

        # after modifications, save it to the output
        im.save(output, format='JPEG', subsampling=0, quality=95)
        output.seek(0)

        # change the imagefield value to be the newly modified image value
        self.profile_image = InMemoryUploadedFile(output, 'ImageField',
                                                  "%s.jpg" % self.profile_image.name.split('.')[0], 'image/jpeg',
                                                  sys.getsizeof(output), None)
        super(Profile, self).save()
'''

CodePudding user response:

Using the Pillow library, you can perform all kinds of image processing on your model's save method, including complex crops. Pillow is the gold standard for image processing in Python.

pip install Pillow

You can read more about the Pillow API here in the documentation.

Working Example:

The following is a working example of an overridden Django model save method that crops the largest central square out of an image, regardless of dimensions, then resizes that square to 200 x 200 pixels.

I wrote this as a simple way of handling logo pics or profile pics, where the art or face is in the center of the image. When cropping, it's important to consider the different scenarios that arise when your input file is portrait vs landscape vs square, which is why I've included this example. Handling each indifferently can lead to undesirable resolutions for some images.

from io import BytesIO
import sys
from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image


class MyImage(models.Model):
    picture_title = models.CharField(max_length=120)
    some_picture  = models.ImageField(upload_to='/media')
   
    def __str__(self):
        return self.picture_title

    def save(self, *args, **kwargs):
        if self.pk is not None:
            orig = MyImage.objects.get(pk=self.pk)
            if self.some_picture:
                if orig.some_picture != self.some_picture: # Perform the crop if the image is updated with a different image
                    im = Image.open(self.some_picture)
                    im = im.convert("RGB") # To standardize your color space if not already RGB
                    exif = None
                    if 'exif' in im.info:  # Exif data records orientation, among other things
                        exif = im.info['exif']

                    output = BytesIO()
                    square = 200 # Set the dimension
                    x_final, y_final = square, square

                    # Get image dimensions
                    width = im.size[0]
                    height = im.size[1]

                    if width > height: # crop the largest square out of the center of a landscape image and resize
                        x1 = (float(width/2)-float(height/2))
                        y1 = (height - height)
                        x2 = ((float(float(width/2)-float(height/2)))   height)
                        y2 = height
                        im = im.crop((x1, y1, x2, y2))
                        im = im.resize((x_final, y_final), Image.ANTIALIAS)
                    
                    elif width < height: # crop the largest square out of the center of a portrait image and resize
                        x1 = (width - width)
                        y1 = (float(height/2)-float(width/2))
                        x2 = width 
                        y2 = ((float(float(height/2)-float(width/2)))   width)
                        im = im.crop((x1, y1, x2, y2))
                        im = im.resize((x_final, y_final), Image.ANTIALIAS) # this is Image.LANCZOS in later versions of Pillow

                    elif width == height: # resize the image if it's already perfectly square
                        im = im.resize((x_final, y_final), Image.ANTIALIAS)

                    else:
                        raise ValueError('Something went wrong.')

                    if exif:
                        im.save(output, format='JPEG', exif=exif, quality=60)
                    else:
                        im.save(output, format='JPEG', quality=60)
                    output.seek(0)
                    self.profile_pic = InMemoryUploadedFile(output,'ImageField', "%s.jpg" % self.some_picture.name.split('.')[0], 'image/jpeg', sys.getsizeof(output), None)

                else:
                    pass
            else:
                pass

        else:
            pass

        super(MyImage, self).save()   

Extra fun fact:

If you're interested in how Pillow's Image.ANTIALIAS or Image.LANCZOS method works, check out the fascinating Lanczos algorithm, which is used to downsample images on resize but retain their visual information.

  • Related