2012-11-29 48 views
1

可能重复时:
resize image on save越来越无法识别图像文件试图在Django创建缩略图

想创建在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() 

类的目的是这样我就可以使用它不同的地点里面动态生成缩略图。该类完美工作,我已经直接在视图下进行测试..我已经在窗体的保存方法中实现了缩略图类,以在保存时调整原始图像的大小。

在我的设计中,我有两个缩略图字段。我能够生成一个缩略图,如果我尝试生成两个它的崩溃,我已经被困了几个小时,不知道是什么问题。

这里是我的模型

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 

如图所示,第一张图像被打印并成功处理,第二张图像失败。在缩略图类应用副本()后

更新

追踪误差更新

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

P.S得到的错误是“无法识别图像文件” –

+0

您有没有原因使用['django-stdimage'](http://code.google.com/p/django-stdimage/)? –

+0

不使用stdimage的原因是因为我的裁剪图像不仅仅是在重新生成..我不知道重复,我认为它与缩略图类有关。 –

回答

4

我看到的问题是你设计你的Thumbnail类的方式。它使用类属性来存储实例变量,这意味着当您尝试多次使用该类时,将会发生冲突。

静态load方法没有必要,因为一旦将属性移动到实例中,它就会完成与该类的构造函数完全相同的操作。并且通过在构造函数中要求source,确保在查找空字符串值时不会在generate后发生崩溃。

另外,我认为你面临的一个主要问题是当你使用类似于文件的对象包装时,你的django模型返回ImageField's。虽然如果传递字符串路径时不会看到此内容,但在传入文件对象时,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()

sourceoutput必须为每个实例是唯一的。但是在您的版本中,您可以将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中只打开一次。然后,您可以根据需要生成尽可能多的图像。

+0

嗨jdi,非常感谢细节的探索,我很欣赏花费的时间。然而,我仍然面临同样的错误,我复制了你提出的同样的确切代码,但它的同一个问题。我开始怀疑问题来自forms.save()方法。看着它,你认为这可能是问题吗? –

+0

我怀疑forms.save()方法的原因,是因为我只是试图使用缩略图类到一个视图呈现HttpResponse与图像,并将其作为一个memtype为PNG。我尝试生成两个和三个缩略图,并只回调最后一个,它工作。所以,这不意味着forms.save()或模型不接受 –

+0

我刚更新了最新的代码正在使用和回溯错误的问题 –

2

PIL.Image.open(...)的参数可以是文件名或文件对象。如果使用像对象这样的文件,读取位置应该在文件的开头。你使用一个文件对象。 (因为你用instance.src.name,然后你通过Thumbnail(instance.src)这是肯定的。)

解决方案:创建第二个缩略图之前instance.src.seek(0)倒回文件的开头或只传递文件名,而不是文件对象:Thumbnail(instance.src.name)

相关问题