5

I am working with Pillow, Django, and django-imagekit.

I am looking to be able to have a profile picture model field (probably using the ProcessedImageField class from imagekit) that will take any image, convert to JPEG, crop it to 150x150, and make its file size 5KB.

The first two are easy:

profile_picture = imagekit.models.ProcessedImageField(upload_to=get_profile_picture_file_path,
                                                      format='JPEG',
                                                      processors=[ResizeToFill(height=150, width=150)]
                                                      )

But how can I ensure the file size is 5KB? I could use something like the options={'quality': 60} parameter in ProcessedImageField, but that seems to be only relative to the original file size (to my knowledge).

Solutions don't have to use django-imagekit, but that would be preferred.

dcgoss
  • 2,147
  • 3
  • 20
  • 31

2 Answers2

0

Maybe in such way. Check the size of image after uploaded and remove it or decrease more in overridden save method:

class Images(models.Model):
    profile_picture = imagekit.models.ProcessedImageField(upload_to=get_profile_picture_file_path,
                                                  format='JPEG',
                                                  processors=[ResizeToFill(height=150, width=150)]
                                                  )

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):

        if os.stat(get_profile_picture_file_path + "/" + self.profile_picture.new_name).st_size > max_size:
            do_something_further_image_processing_to_decrease_size

        super(Images, self).save()
PaulWebbster
  • 1,480
  • 2
  • 14
  • 27
  • This is a good start in verifying that the file is too large, but the logic in `do_something_further_image_processing_to_decrease_size` is really the relevant part of the question. How can you ensure that you are decreasing the file size to a specific size, every time (even with images of different resolutions/JPEG qualities)? – dcgoss Feb 16 '16 at 15:16
  • I probably will add here iterative function which decreases the resolution by 10% or other factor corresponding to current size of produced picture every iteration. `PIL` gives you such possibility. – PaulWebbster Feb 17 '16 at 08:45
0

I used to have a similar issue, so I decided to optimize the images using OS tools (jpegoptim, optipng, etc) called from django after save the model using signals (you can do overriden save method too). This tools optimize and eliminate metadata from your images. In the other hand you could study the average compression ratio and size for jpg files of 150x150 and try to guess the best quality to setup check this: (jpeg compression ratio)

This is my code for optimize files after save them, I'm using easy thumbnails library which provide me signals after save:

@receiver(saved_file)
def optimize_file(sender, fieldfile, **kwargs):
    optimize(fieldfile.path)


# thumbnail optimization
@receiver(thumbnail_created)
def optimize_thumbnail(sender, **kwargs):
    optimize(sender.path)

def optimize(path):
    """
    install image utilities
    apt-get install jpegoptim optipng pngcrush advancecomp
    :param path:
    :return:
    """
    # taken from trimage (http://trimage.org/)
    runString = {
        ".jpeg": u"jpegoptim -f --strip-all '%(file)s' ; chmod 644 '%(file)s'",
        ".jpg": u"jpegoptim -f --strip-all '%(file)s' ; chmod 644 '%(file)s'",
        ".png": u"optipng -force -o7 '%(file)s' && advpng -z4 '%(file)s' && pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time '%(file)s' '%(file)s.bak' && mv '%(file)s.bak' '%(file)s' ; chmod 644 '%(file)s'"
    }

    ext = splitext(path)[1].lower()
    if ext in runString:
        subprocess.Popen(runString[ext] % {'file': path}, shell=True)
Community
  • 1
  • 1
César
  • 91
  • 1
  • 4