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 ?
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
Comments (12)
0
adriano 12 years ago
Hi,
nice slice. I have been trying to do something like this for a couple of time.
Thanks
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
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
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)
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
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)
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
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.
0
mlrichardvezina 11 years ago
In book it says this is bad : request = request.current
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
edwin-ven-11518 10 years ago
Thank you, this was very useful!
0
fernando-vieira-10469 10 years ago
Muito bom Bruno, vai ajudar bastante