Possible Duplicate:
resize image on save
Am trying to create a thumbnail in django, am trying to build a custom class specifically to be used for generating thumbnails. As following
from cStringIO import StringIO
from PIL import Image
class Thumbnail(object):
SIZE = (50, 50)
def __init__(self, source):
self.source = source
self.output = None
def generate(self, size=None, fit=True):
if not size:
size = self.SIZE
if not isinstance(size, tuple):
raise TypeError('Thumbnail class: The size parameter must be an instance of a tuple.')
# resize properties
box = size
factor = 1
image = Image.open(self.source)
# Convert to RGB if necessary
if image.mode not in ('L', 'RGB'):
image = image.convert('RGB')
while image.size[0]/factor > 2*box[0] and image.size[1]*2/factor > 2*box[1]:
factor *=2
if factor > 1:
image.thumbnail((image.size[0]/factor, image.size[1]/factor), Image.NEAREST)
#calculate the cropping box and get the cropped part
if fit:
x1 = y1 = 0
x2, y2 = image.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)
image = image.crop((x1,y1,x2,y2))
#Resize the image with best quality algorithm ANTI-ALIAS
image.thumbnail(box, Image.ANTIALIAS)
# save image to memory
temp_handle = StringIO()
image.save(temp_handle, 'png')
temp_handle.seek(0)
self.output = temp_handle
return self
def get_output(self):
self.output.seek(0)
return self.output.read()
the purpose of the class is so i can use it inside different locations to generate thumbnails on the fly. The class works perfectly, I’ve tested it directly under a view.. I’ve implemented the thumbnail class inside the save method of the forms to resize the original images on saving.
in my design, I have two fields for thumbnails. I was able to generate one thumbnail, if I try to generate two it crashes and I’ve been stuck for hours not sure whats the problem.
Here is my model
class Image(models.Model):
article = models.ForeignKey(Article)
title = models.CharField(max_length=100, null=True, blank=True)
src = models.ImageField(upload_to='publication/image/')
r128 = models.ImageField(upload_to='publication/image/128/', blank=True, null=True)
r200 = models.ImageField(upload_to='publication/image/200/', blank=True, null=True)
uploaded_at = models.DateTimeField(auto_now=True)
Here is my forms
class ImageForm(models.ModelForm):
"""
"""
class Meta:
model = Image
fields = ('src',)
def save(self, commit=True):
instance = super(ImageForm, self).save(commit=True)
instance.r128 = SimpleUploadedFile(
instance.src.name,
Thumbnail(instance.src).generate((128, 128)).get_output(),
content_type='image/png'
)
instance.r200 = SimpleUploadedFile(
instance.src.name,
Thumbnail(instance.src).generate((200, 200)).get_output(),
content_type='image/png'
)
if commit:
instance.save()
return instance
the strange part is, when i remove the line which contains instance.r200 in the form save. It works fine, and it does the thumbnail and stores it successfully. Once I add the second thumbnail it fails..
Any ideas what am doing wrong here?
Thanks
Update:
as per the comment request, am appending the error trace
IOError at /en/publication/new/
cannot identify image file
Request Method: POST
Request URL: http://127.0.0.1:8000/en/publication/new/?image-extra=
Django Version: 1.4.2
Exception Type: IOError
Exception Value:
cannot identify image file
Exception Location: /Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/PIL/Image.py in open, line 1980
Python Executable: /Users/mo/Projects/pythonic/snowflake-env/bin/python
Python Version: 2.7.2
Update
Tried to create print statement and below is the output
Source: publication/image/tumblr_m9o7244nZM1rykg1io1_1280_11.jpg
Source: publication/image/tumblr_m9o7244nZM1rykg1io1_1280_11.jpg
ERROR:root:cannot identify image file
ERROR:django.request:Internal Server Error: /en/publication/new/
Traceback (most recent call last):
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/db/transaction.py", line 209, in inner
return func(*args, **kwargs)
File "/Users/mo/Projects/pythonic/snowflake-env/snowflake/snowflake/apps/publication/views.py", line 69, in new
formset.save()
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/forms/models.py", line 497, in save
return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/forms/models.py", line 628, in save_new_objects
self.new_objects.append(self.save_new(form, commit=commit))
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/forms/models.py", line 727, in save_new
obj = form.save(commit=False)
File "/Users/mo/Projects/pythonic/snowflake-env/snowflake/snowflake/apps/publication/forms.py", line 113, in save
Thumbnail(instance.src).generate((200, 200)).get_output(),
File "/Users/mo/Projects/pythonic/snowflake-env/snowflake/snowflake/apps/core/utils.py", line 23, in generate
image = Image.open(self.source)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/PIL/Image.py", line 1980, in open
raise IOError("cannot identify image file")
IOError: cannot identify image file
As seen, the first image is printed and processed successfully the second image is failing.
update
traceback error update after applying the copy() in the thumbnail class
ERROR:root:cannot identify image file
ERROR:django.request:Internal Server Error: /en/publication/new/
Traceback (most recent call last):
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/core/handlers/base.py", line 111, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/contrib/auth/decorators.py", line 20, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/db/transaction.py", line 209, in inner
return func(*args, **kwargs)
File "/Users/mo/Projects/pythonic/snowflake-env/snowflake/snowflake/apps/publication/views.py", line 69, in new
formset.save()
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/forms/models.py", line 497, in save
return self.save_existing_objects(commit) + self.save_new_objects(commit)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/forms/models.py", line 628, in save_new_objects
self.new_objects.append(self.save_new(form, commit=commit))
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/django/forms/models.py", line 727, in save_new
obj = form.save(commit=False)
File "/Users/mo/Projects/pythonic/snowflake-env/snowflake/snowflake/apps/publication/forms.py", line 113, in save
f128.write(Thumbnail(instance.src).generate((128, 128)).get_output())
File "/Users/mo/Projects/pythonic/snowflake-env/snowflake/snowflake/apps/core/utils.py", line 15, in __init__
self._pilImage = Image.open(self.source)
File "/Users/mo/Projects/pythonic/snowflake-env/lib/python2.7/site-packages/PIL/Image.py", line 1980, in open
raise IOError("cannot identify image file")
IOError: cannot identify image file
Update
Finally, I managed to get it to work, but I had to stream the file into self.source as belo
def __init__(self, source):
self.source = StringIO(file(source.path, "rb").read())
self.output = None
self._pilImage = Image.open(self.source)
is the above ideal approach? is it a good idea to read the file at each hit? if no, what are my alternatives?
The problem I see is in the way you have designed your
Thumbnailclass. It is using class attributes to store instance variables, meaning that you will have conflicts when you try to use the class more than once.There is no need for the static
loadmethod, as once you move the attributes to the instance, it does the exact same thing as the constructor of the class. And by requiring asourcein the constructor, you ensure a crash will not occur later ingeneratewhen it looks for empty string values.Also, one of the major problems I think you are facing is when you are using the file-like object wrappers that your django model is returning for the
ImageField‘s. While you would not see this if you were passing in string paths, when you pass in the file object, thegeneratemethod reads it to the end. Then you callgeneratea second time with the same source object, but it is at the end and you get anIOError. Now one approach would be to make sure to seek the source back to0before callingThumbnailagain with it, but instead you can save yourself the trouble and just have yourThumbnailclass open and cache the PIL image once in the constructor. Thengeneratedoes not need to constantly re-read it again each time.Re-written Thumbnail Class
Usage:
Thumbnail(src).generate((200, 200)).get_output()The
sourceandoutputneed to be unique for each instance. But in your version you would setoutputto the class level, which means that two instances of theThumbnailuse the shared most recent version ofoutput.Also, I feel there is an easier way to perform your resize/fit/crop. If you explain the exact transformation you want to do for the image, I can probably simplify that as well.
Update
I forgot to specifically mention that with my suggestions for saving the source image once, your usage should look like this:
Notice that we only create one instance of
Thumbnailusing the source, which will open it only once in PIL. Then you can generate as many images as you want from it.