可能重复:
resize image on save
我试图在django中创建缩略图,我正在尝试构建一个专门用于生成缩略图的自定义类。如下
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()
这个类的目的是让我可以在不同的位置使用它来动态生成缩略图。这个类工作得很好,我直接在视图下测试过..我在表单的save方法中实现了缩略图类,以便在保存时调整原始图像的大小。
在我的设计中,我有两个缩略图字段。我能够生成一个缩略图,如果我尝试生成两个崩溃,我已经被困了几个小时不知道是什么问题。
这是我的模特
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)
这是我的表格
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
奇怪的是,当我在表单中删除包含instance.r200的行时保存。它工作正常,它完成缩略图并成功存储它。一旦我添加第二个缩略图,它就会失败..
任何想法在这里做错了什么?
由于
更新
根据评论请求,附加错误跟踪
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
更新
尝试创建print语句,下面是输出
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
如图所示,第二张图像失败后,第一张图像被成功打印并处理。
更新
在缩略图类中应用copy()后,跟踪错误更新
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
更新
最后,我设法让它工作,但我必须将文件流式传输到self.source,因为belo
def __init__(self, source):
self.source = StringIO(file(source.path, "rb").read())
self.output = None
self._pilImage = Image.open(self.source)
是上述理想方法吗?在每次点击时读取文件是个好主意吗?如果不是,我的替代方案是什么?
答案 0 :(得分:4)
我看到的问题在于您设计Thumbnail
课程的方式。它使用类属性来存储实例变量,这意味着当您尝试多次使用该类时会出现冲突。
不需要静态load
方法,因为一旦将属性移动到实例,它就会与类的构造函数完全相同。通过在构造函数中要求source
,可以确保在generate
中查找空字符串值时不会发生崩溃。
此外,我认为您面临的一个主要问题是当您使用类似文件的对象包装器时,您的django模型将返回ImageField
。虽然如果传入字符串路径,则不会看到此内容,但是当您传入文件对象时,generate
方法会将其读取到最后。然后,您使用相同的源对象再次调用generate
,但它最后会得到IOError
。现在,一种方法是确保在再次使用0
再次调用Thumbnail
之前寻找源代码,但是您可以省去麻烦并让您的Thumbnail
课程打开,在构造函数中缓存PIL图像一次。然后generate
不需要每次都重新读取它。
# Example from your code #
def generate(self, size=None, fit=True):
...
# The first time you do this, it will read
# self.source to the end, because in Django, you
# are passing a file-like object.
image = Image.open(self.source)
# this will work the first time
generate()
# uh oh. self.source was a file object that is at the end
generate() # crash
重写缩略图类
from cStringIO import StringIO
from PIL import Image
class Thumbnail(object):
SIZE = (50, 50)
def __init__(self, source):
self.source = source
self.output = None
self._pilImage = Image.open(self.source)
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 = self._pilImage.copy()
# 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()
用法:Thumbnail(src).generate((200, 200)).get_output()
source
和output
对每个实例都必须是唯一的。但是在您的版本中,您可以将output
设置为类级别,这意味着Thumbnail
的两个实例使用最新版本的共享output
。
# your code #
# this is assigning the most recently processed
# object to the class level. shared among all.
self.output = temp_handle
return self
def get_output(self):
# always read the shared class level
return self.output.read()
此外,我觉得有一种更简单的方法来执行调整大小/适合/裁剪。如果你解释想要为图像做的确切转换,我也可以简化它。
<强>更新强>
我忘了特别提到我保存源图像一次的建议,你的用法应该是这样的:
def save(self, commit=True):
instance = super(ImageForm, self).save(commit=True)
thumb = Thumbnail(instance.src)
instance.r128 = SimpleUploadedFile(
instance.src.name,
thumb.generate((128, 128)).get_output(),
content_type='image/png'
)
instance.r200 = SimpleUploadedFile(
instance.src.name,
thumb.generate((200, 200)).get_output(),
content_type='image/png'
)
请注意,我们只使用源创建一个Thumbnail
实例,它只在PIL中打开一次。然后,您可以根据需要生成任意数量的图像。
答案 1 :(得分:2)
PIL.Image.open(...)
的参数可以是文件名或文件对象。如果使用类似对象的文件,则读取位置应位于文件的开头。您使用文件对象。 (这是肯定的,因为您使用instance.src.name
然后传递Thumbnail(instance.src)
。)
解决方案:在创建第二个缩略图之前将文件回到instance.src.seek(0)
开头,或者只传递文件名,而不是文件对象:Thumbnail(instance.src.name)
。