1

重複の可能性:
保存時に画像のサイズを変更する

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メソッド内にサムネイルクラスを実装して、保存時に元の画像のサイズを変更しました。

私のデザインでは、サムネイル用に2つのフィールドがあります。1つのサムネイルを生成できましたが、2つ生成しようとするとクラッシュし、何が問題なのかわからないまま何時間も立ち往生しています。

これが私のモデルです

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

奇妙な部分は、saveの形式でinstance.r200を含む行を削除したときです。正常に動作し、サムネイルを作成して正常に保存します。2番目のサムネイルを追加すると、失敗します。

ここで何が間違っているのか考えてみてください。

ありがとう

アップデート:

コメントリクエストに従って、エラートレースを追加しています

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

アップデート

印刷ステートメントを作成しようとしましたが、以下が出力です

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

ご覧のとおり、最初の画像は正常に印刷および処理され、2番目の画像は失敗しています。

アップデート

サムネイルクラスに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にストリーミングする必要がありました

def __init__(self, source):
    self.source = StringIO(file(source.path, "rb").read())
    self.output = None

    self._pilImage = Image.open(self.source)

上記の理想的なアプローチは何ですか?ヒットするたびにファイルを読み取るのは良い考えですか?いいえの場合、私の選択肢は何ですか?

4

2 に答える 2

4

私が見る問題は、あなたがThumbnailクラスを設計した方法にあります。クラス属性を使用してインスタンス変数を格納しているため、クラスを複数回使用しようとすると競合が発生します。

load属性をインスタンスに移動すると、クラスのコンストラクターとまったく同じことを行うため、静的メソッドは必要ありません。また、コンストラクターでを要求することにより、後で空の文字列値を検索するときにsourceクラッシュが発生しないようにします。generate

また、私が直面している主な問題の1つは、djangoモデルがImageField'sに対して返すファイルのようなオブジェクトラッパーを使用している場合です。文字列パスを渡す場合はこれは表示されませんが、ファイルオブジェクトを渡すと、generateメソッドはそれを最後まで読み取ります。次にgenerate、同じソースオブジェクトを使用してもう一度呼び出しますが、最後に呼び出して、を取得しIOErrorます。0ここでのアプローチの1つは、ソースを再度呼び出す前に必ずソースを検索すること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、クラスレベルに設定します。これは、2つのインスタンスが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'
    )

ソースを使用するインスタンスを1つだけ作成Thumbnailし、PILで1回だけ開くことに注意してください。次に、そこから必要な数の画像を生成できます。

于 2012-12-01T17:00:58.430 に答える
2

のパラメータはPIL.Image.open(...)、ファイル名またはファイルオブジェクトにすることができます。オブジェクトのようなファイルを使用する場合、読み取り位置はファイルの先頭にある必要があります。ファイルオブジェクトを使用します。instance.src.name(使用してから合格するので確実ですThumbnail(instance.src)。)

解決策instance.src.seek(0): 2番目のサムネイルを作成する前にファイルを最初に巻き戻すか、ファイルオブジェクトではなくファイル名のみを渡しますThumbnail(instance.src.name)

于 2012-12-02T01:16:19.880 に答える