How to resize source with sorl-thumbnail?
Asked Answered
L

4

10

I was searching the web for my question and couldn't find a clear answeror any example.

Basically, I want to use sorl and want to resize the source image during the Model save time to resize it down to 640x480 size, so that I don't end-up storing user's original 2.5 MB files on the disk. I will then use templatetags to create regular thumbnails out of my source as documented in sorl.

I came across couple of sources refer to use ThumbnailField model field that is supposed to be available in sorl.thumbnail.fields. See the link here. However, in my up-to-date sorl copy from the trunk I don't see any ThumbnailField or ImageWithThumbnailsField. My attempt to import it in the model fails accordingly. I see these references are old though and wondering whether I can achieve the same with up-to-date sorl.

On the other hand sorl documentation indicates only ImageField from sorl.thumbnail (see here) that does not have any size argument to control the source resizing.

BTW, I see this functionality is available with easy_thumbnail that takes an input parameter source_resize.

Any help will be appreciated!

SUMMARY

I accepted the below answer, however I feel natural sorl support for this use case can be very useful - i.e. adding resize_source param to sorl's ImageField to allow resizing the source image. Below are two factors why this can be useful in the field:

  1. Not to store user's huge original images if your app has no need for it. Saving disk space.

  2. Not to spend extra CPU for resizing thumbnails from that huge source images if you don't have specific extreme high quality reasons. To avoid this one may write nested tags in templates to thumbnail from smaller size images but it can become annoying very soon.

Lyndsaylyndsey answered 19/2, 2012 at 23:8 Comment(3)
so you want the users uploaded image to be resized to a manageable width and height before applying any thumbnail crops?Bloat
Yes, basically I want to keep the source file on disk always to fit 640x480 box.Lyndsaylyndsey
I am sensing get_thumbnail would help me doing it - example here. However, I would like someone to confirm this to be the only case as well as recommend how best to achieve this during ModelForm save. Should I for instance use pre-create signal or similar to achieve this??Lyndsaylyndsey
N
8

Sorl's ImageField that you mention, is just a normal Django ImageField with the added benefit of managing the deletion of cached thumbnails. There's no resizing done on the initial upload - that's something you have to implement yourself manually via the view you are using to upload. The docs show how to go about this. You can use sorl in that view to do the actual resize operation itself, using the low level API examlpes

EDIT

A quicker alternative is to just resize the image when the model is being saved using sorl. You can do something like the following (completely untested though!)

from sorl.thumbnail import get_thumbnail

class Foo(models.Model):
    image = models.ImageField(upload_to...)

    def save(self, *args, **kwargs):
        if not self.id:
            # Have to save the image (and imagefield) first
            super(Foo, self).save(*args, **kwargs)
            # obj is being created for the first time - resize
            resized = get_thumbnail(self.image, "100x100" ...)
            # Manually reassign the resized image to the image field
            self.image.save(resized.name, resized.read(), True)
        super(Foo, self).save(*args, **kwargs)

this will mean that you will have 2 versions of the same image on disk - one where the django image field decides to save it (upload_to path) and one where sorl thumbnail has saved it's resized thumbnail. This, along with the fact the image is uploaded and saved twice, are the disadvantages of this approach. It's quicker to implement though

Nullity answered 19/2, 2012 at 23:29 Comment(6)
Thanks for your answer! Could you elaborate on the links? I am curious to see what should be the recommended way of doing it. If in the view should I save my Model twice then - once initially via ModelForm save, and later saving the instance again in the view after applying low-level method on the instance image field?Lyndsaylyndsey
Ideally you only need to save once - you actually resize the file before it is saved instead of saving it, resizing it and saving it again. This is where the handle_uploaded_file method comes in handy - this is where the actual file is saved onto the disk. I'll try add an example in a minuteBloat
Yes, it's clear, the only thing though that I am wondering is whether handling the upload myself will later affect moving to another storage (say S3)? That's the only piece that keeps me wonder whether or not to handle it myself or let ImageField to solve it and save the instance and then update it (though this will also be a bit ugly from performence perspective if everything is on S3).Lyndsaylyndsey
pastylegs thanks for the detailed example! I wonder wouldn't it be better in your example to simply either move the file from get_thumbnail destination to my upload_to destination and then simply assign the path to self.image prior save? It will eliminate the second save I think....What you think?Lyndsaylyndsey
Yes, but then sorl thumbnail would have an entry in the cache that points to a non existant file (that you have moved). You will probably never request that file (it would be strange to ask for a thumbnail the same size as the original) but it's still not good practiceBloat
Thanks for all your feedback. I am marking your answer accepted though I think it's obvious how handly it can come to have a natural support in sorl's ImageField support the extra resize param.Lyndsaylyndsey
S
12

I found a flaw in the code above, got “str has no method chunck()”, if somebody want to use it. Here is my fix:

    from sorl.thumbnail import get_thumbnail
    from django.core.files.base import ContentFile

 class Foo(models.Model):
    image = models.ImageField(upload_to...)


    def save(self, *args, **kwargs):
        if not self.id:  
            super(Foo, self).save(*args, **kwargs)  
            resized = get_thumbnail(self.image, "100x100" ...)
            self.image.save(resized.name, ContentFile(resized.read()), True)
        super(Foo, self).save(*args, **kwargs)
Sergias answered 2/10, 2012 at 23:38 Comment(0)
N
8

Sorl's ImageField that you mention, is just a normal Django ImageField with the added benefit of managing the deletion of cached thumbnails. There's no resizing done on the initial upload - that's something you have to implement yourself manually via the view you are using to upload. The docs show how to go about this. You can use sorl in that view to do the actual resize operation itself, using the low level API examlpes

EDIT

A quicker alternative is to just resize the image when the model is being saved using sorl. You can do something like the following (completely untested though!)

from sorl.thumbnail import get_thumbnail

class Foo(models.Model):
    image = models.ImageField(upload_to...)

    def save(self, *args, **kwargs):
        if not self.id:
            # Have to save the image (and imagefield) first
            super(Foo, self).save(*args, **kwargs)
            # obj is being created for the first time - resize
            resized = get_thumbnail(self.image, "100x100" ...)
            # Manually reassign the resized image to the image field
            self.image.save(resized.name, resized.read(), True)
        super(Foo, self).save(*args, **kwargs)

this will mean that you will have 2 versions of the same image on disk - one where the django image field decides to save it (upload_to path) and one where sorl thumbnail has saved it's resized thumbnail. This, along with the fact the image is uploaded and saved twice, are the disadvantages of this approach. It's quicker to implement though

Nullity answered 19/2, 2012 at 23:29 Comment(6)
Thanks for your answer! Could you elaborate on the links? I am curious to see what should be the recommended way of doing it. If in the view should I save my Model twice then - once initially via ModelForm save, and later saving the instance again in the view after applying low-level method on the instance image field?Lyndsaylyndsey
Ideally you only need to save once - you actually resize the file before it is saved instead of saving it, resizing it and saving it again. This is where the handle_uploaded_file method comes in handy - this is where the actual file is saved onto the disk. I'll try add an example in a minuteBloat
Yes, it's clear, the only thing though that I am wondering is whether handling the upload myself will later affect moving to another storage (say S3)? That's the only piece that keeps me wonder whether or not to handle it myself or let ImageField to solve it and save the instance and then update it (though this will also be a bit ugly from performence perspective if everything is on S3).Lyndsaylyndsey
pastylegs thanks for the detailed example! I wonder wouldn't it be better in your example to simply either move the file from get_thumbnail destination to my upload_to destination and then simply assign the path to self.image prior save? It will eliminate the second save I think....What you think?Lyndsaylyndsey
Yes, but then sorl thumbnail would have an entry in the cache that points to a non existant file (that you have moved). You will probably never request that file (it would be strange to ask for a thumbnail the same size as the original) but it's still not good practiceBloat
Thanks for all your feedback. I am marking your answer accepted though I think it's obvious how handly it can come to have a natural support in sorl's ImageField support the extra resize param.Lyndsaylyndsey
S
2

I was looking for solution for some time and eventually wrote app django-resized.

Sides answered 12/9, 2012 at 5:29 Comment(0)
F
0

The following code uses the PIL Engine (part of sorl-thumbnail) to crop an image called picture.jpg (tested using Python 3.8 and sorl-thumbnail==12.6.3):

#
# Change this import to get the Engine of your underlying libraries.
# Options are: convert_engine, pgmagick_engine, pil_engine, vipsthumbnail_engine or wand_engine.
#
from sorl.thumbnail.engines.pil_engine import Engine

# This object has all we need
engine = Engine()

#
# When receiving data from a request
# you probably have a BytesIO instance ready to use like:
#
#   im = engine.get_image(my_bytes_io)
#
with open("picture.jpg", "rb") as f:
    im = engine.get_image(f)

im_crop = engine.crop(im, (535, 535), options={'crop': 'smart'})

im_crop.save("picture-thumb.jpg")

Instead of modifying the save method, I would have a helper function for reducing the image size (with the lines above) and call it from the Django view or form before updating the image field. Though it would work doing it on the save itself.

On the other hand, the Engine API has more useful features that could be useful! This API has been there since the first commit so, in my opinion, it's unlikely to change in the future: create, cropbox, orientation, flip_dimensions, colorspace, remove_border, calculate_scaling_factor, scale, crop, rounded, blur, padding, write, cleanup, get_image_ratio, get_image_info, get_image, get_image_size, is_valid_image.

Ferreous answered 14/7, 2020 at 21:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.