If you benefit from web2py hope you feel encouraged to pay it forward by contributing back to society in whatever form you choose!

If in your image gallery or website home page, or even for a user profile image you have a fixed dimensions box to show thumbnails for the uploaded image.

consider this:

<div style="width:200px; height:200px; max-width:200px; overflow:hidden;">
   <img src="{{=URL('default', 'download', args=article.image)}}" />
</div>

You will have a fixed 200x200 box to show the image, but what if the uploaded image is 1024x768 ?

1024x768 image

An image with 1024x768

 

Obviously if you resize that to fit the 200x200 box, you will end with a streched image:

The same image as above, but redimensioned to 200x200, aspect is streched.

 

How to solve this using PIL

For this example I am going to use a fixed, hardcoded centered position. But with a cropper Javascript plugin you can pass coordinates to crop in another position.

 

1. Install PIL 

pip install pil

or

sudo apt-get install python-imaging

 

2. In a module create a function to crop the image.

yourapp/modules/smarthumb.py

 

from gluon import current
import os
try:
    from PIL import Image
except:
    import Image

def SMARTHUMB(image, box, fit=True, name="thumb"):
    '''Downsample the image.
     @param img: Image -  an Image-object
     @param box: tuple(x, y) - the bounding box of the result image
     @param fit: boolean - crop the image to fill the box
    '''
    if image:
        request = current.request
        img = Image.open(request.folder + 'uploads/' + image)
        #preresize image with factor 2, 4, 8 and fast algorithm
        factor = 1
        while img.size[0] / factor > 2 * box[0] and img.size[1] * 2 / factor > 2 * box[1]:
            factor *= 2
        if factor > 1:
            img.thumbnail((img.size[0] / factor, img.size[1] / factor), Image.NEAREST)

        #calculate the cropping box and get the cropped part
        if fit:
            x1 = y1 = 0
            x2, y2 = img.size
            wRatio = 1.0 * x2 / box[0]
            hRatio = 1.0 * y2 / box[1]
            if hRatio > wRatio:
                y1 = int(y2 / 2 - box[1] * wRatio / 2)
                y2 = int(y2 / 2 + box[1] * wRatio / 2)
            else:
                x1 = int(x2 / 2 - box[0] * hRatio / 2)
                x2 = int(x2 / 2 + box[0] * hRatio / 2)
            img = img.crop((x1, y1, x2, y2))

        #Resize the image with best quality algorithm ANTI-ALIAS
        img.thumbnail(box, Image.ANTIALIAS)

        root, ext = os.path.splitext(image)
        thumb = '%s_%s%s' % (root, name, ext)
        img.save(request.folder + 'uploads/' + thumb)
        return thumb

 

3. Now take this sample model.

yourapp/models/db.py

db = DAL('youconnectionstring')

Article = db.define_table('article',
    Field("title"),
    Field("article_text", "text"),
    Field("picture", "upload"),
    Field("thumbnail", "upload")
)

Note that in the model we defined an Article object, which points us to a dal Table db.article, this table has two fields of type "upload", the idea is that the user will upload the file in "picture" fields and we are going to create the thumbnail authomatically.

 

4. Define the computation for "thumbnail" field in the same file, right after the table definition

from smarthumb import SMARTHUMB

box = (200, 200)
Article.thumbnail.compute = lambda row: SMARTHUMB(row.picture, box)

In the above code we are defining that when the FORM gets processed, DAL will compute the value for "thumbnail" field. The computed value will be the result of the "lambda' function we passed to field's compute attribute, in that case we are taking the submitted row and passing the uploaded picture (note that web2py will pass in the path to uploaded image) and also we are passing a box tuple of 200x200 to fit our thumbs div.

 

5. Create a form and test the result

yourapp/controllers/default.py

def addarticle():
    form = SQLFORM(Article).process()
    return dict(form=form)

def showarticle():
   id = request.args(0) or redirect(URL('default', 'index'))
    article = Article[id] 
    return dict(article=article)

 

6. Now you can create a view or just use the generic view for that.

yourapp/views/default/addarticle.html

<h1> Add an article </1>

{{=form}}

 

7.  A view to show the article

yourapp/views/default/showarticle.html

<article>
<h1> {{=article.title}}</h1>

<div style="width:200px; height:200px; max-width:200px; overflow:hidden;">
   <img src="{{=URL('default', 'download', args=article.image)}}" />
</div>

<p>{{=MARKMIN(article.article_text)}}</p>

</article>

 

Now the end result will be:

 

Original uploaded 1024x768 image

 

The generated cropped and centered 200x200 thumbnail keeping the aspect of image.

 

So much better than the streched version no?

 

** also you can use http://src.sencha.io to improve the image exhibition

** web2py now includes gluon/contrib/imagetools.py maybe that function will go there also

 

Does anybody wants to contribute creating a plugin using JqueryCropper or anither JavaScript Crop plugin?

 

REFERENCE: http://united-coders.com/christian-harms/image-resizing-tips-general-and-for-python

 

Related slices

Comments (12)

  • Login to post



  • 1
    ilvalle 12 years ago

    I've defined the upload field with the options uploadseparate=True, uploadfolder=request.folder+'uploads/pictures'

    It would be nice to integrate automatically this two options, so far I changed the code as follows (is not the best way but it works):

    try:
       img = Image.open(request.folder + 'uploads/pictures/' + image)

    except:
       try:

          splits = image.split('.')
          path = splits[0] + '.' + splits[1] + '/' + splits[2][:2] + '/'
          global_path = request.folder + 'uploads/pictures/' + path + image
          img = Image.open(request.folder + 'uploads/pictures/' + path + image)
       except:

          return image

    [...]

     

    Paolo

    replies (2)
    • mlrichardvezina 11 years ago

      I think need to define uploadfolder with uploadseparate=True can be avoid with retrieve_file_properties Field method : image_dir = current.db.auth_user.image.retrieve_file_properties(image)['path'] img = Image.open(os.path.join(image_dir, image))

    • titogarrido 12 years ago

      Hey Paolo! I have the same problem... are you able to post here the entire code?


  • 0
    fernando-vieira-10469 10 years ago

    Muito bom Bruno, vai ajudar bastante smiley


  • 0
    edwin-ven-11518 10 years ago

    Thank you, this was very useful!


  • 0
    mlrichardvezina 11 years ago

    What do pre-resize?? We found that it cause wrong resize with certain .jpg or .png file and if we remove the pre-resize step it solves our issue... Thanks for provide explanation.

     

    Richard


  • 0
    mlrichardvezina 11 years ago

    In book it says this is bad : request = request.current


  • 0
    bo 11 years ago

    i have been trying this with the following and i just cant get it to work. i am not sure what i am doing wrong.

    
    
    1. auth.settings.extra_fields['auth_user']= [
    2. Field('company_name'),
    3. Field('street'),
    4. Field('city'),
    5. Field('country'),
    6. Field('zip'),
    7. Field('phone'),
    8. Field('bio', 'text'),
    9. Field('avatar', 'upload', autodelete=True),
    10. Field('thumb', 'upload',writable=False,readable=False, autodelete=True)]
    11.  
    12. from smarthumb import SMARTHUMB
    13.  
    14.  
    15. box = (200, 200)
    16. db.auth_user.thumb.compute = lambda row: SMARTHUMB(row.avatar, box)

     

     

     


  • 0
    titogarrido 12 years ago

    I am wondering how we could use uploadfolder and uploadseparate with smarthumb...

    I mean, uploadfolder and uploadseparate on both fields, is it possible?


  • 0
    rochacbruno 12 years ago

    I did not copied this code form that site. I got parts of the code from a student which has sent for me via e-mail.

     

    I am going to include your links on the post. 

     

    Thanks


  • 0
    christian-harms-10713 12 years ago

    If you copy shameless python code for other sites - the internet never forget. Please add a link to the original article:

     

    http://united-coders.com/christian-harms/image-resizing-tips-general-and-for-python

    And here a copy + link on stackoverflow:

    http://stackoverflow.com/questions/273946/how-do-i-resize-an-image-using-pil-and-maintain-its-aspect-ratio

     

    Thanx!

    replies (1)
    • rochacbruno 12 years ago

      Link added. Thanks for the code!


  • 0
    rochacbruno 12 years ago

    I guess it will not work on GAE, since PIL does not runs on GAE. GAE has its own image lib.


  • 0
    adriano 12 years ago

    Questions:  

    Will it work on GAE?  

    on the 7th part of yout article, shouldn´t it be <img src="{{=URL('default', 'download', args=article.thumbnail)}}


  • 0
    adriano 12 years ago

    Hi,

    nice slice. I have been trying to do something like this for a couple of time.

     

    Thanks

     


Hosting graciously provided by:
Python Anywhere