Изменение размера изображения с помощью django?

Я новичок в Django (и Python), и я сам пытался решить некоторые вещи, прежде чем переходить на использование приложений других людей. У меня возникли проблемы с пониманием того, где вещи "подходят" в способе Django (или Python) делать вещи. То, что я пытаюсь решить, - это изменить размер изображения после его загрузки. У меня хорошая настройка модели и подключен к администратору, и изображение загружается в каталог:

from django.db import models

# This is to list all the countries
# For starters though, this will be just United Kingdom (GB)
class Country(models.Model):
    name = models.CharField(max_length=120, help_text="Full name of country")
    code = models.CharField(max_length=2, help_text="This is the ISO 3166 2-letter country code (see: http://www.theodora.com/country_digraphs.html)")
    flag = models.ImageField(upload_to="images/uploaded/country/", max_length=150, help_text="The flag image of the country.", blank=True)

    class Meta:
        verbose_name_plural = "Countries"

    def __unicode__(self):
        return self.name

Теперь у меня возникают проблемы с этим файлом и созданием нового файла в миниатюре. Как я уже сказал, я хотел бы знать, как это сделать, не используя приложения других (пока). У меня есть этот код из DjangoSnippets:

from PIL import Image
import os.path
import StringIO

def thumbnail(filename, size=(50, 50), output_filename=None):
    image = Image.open(filename)
    if image.mode not in ('L', 'RGB'):
        image = image.convert('RGB')
    image = image.resize(size, Image.ANTIALIAS)

    # get the thumbnail data in memory.
    if not output_filename:
        output_filename = get_default_thumbnail_filename(filename)
    image.save(output_filename, image.format) 
    return output_filename

def thumbnail_string(buf, size=(50, 50)):
    f = StringIO.StringIO(buf)
    image = Image.open(f)
    if image.mode not in ('L', 'RGB'):
        image = image.convert('RGB')
    image = image.resize(size, Image.ANTIALIAS)
    o = StringIO.StringIO()
    image.save(o, "JPEG")
    return o.getvalue()

def get_default_thumbnail_filename(filename):
    path, ext = os.path.splitext(filename)
    return path + '.thumb.jpg'

... но это в конечном итоге смутило меня... Как я не знаю, как это "подходит" к моему приложению Django? И действительно, это лучшее решение для простого создания эскиза изображения, которое было успешно загружено? Может ли кто-нибудь показать мне хороший, солидный, достойный способ, чтобы начинающий, как я, мог научиться делать это правильно? Как, знаете, где поставить такой код (models.py? Forms.py?...) и как он будет работать в контексте?... Мне просто нужно немного помочь понять и решить эту проблему.

Спасибо!

Ответ 1

Если все в порядке, есть приложение Django, готовое сделать именно то, что вы хотите: https://github.com/sorl/sorl-thumbnail

Ответ 2

Это то, что я использую в своих моделях для сохранения нового эскиза, если загруженное изображение изменилось. Он основан на другом DjangoSnippet, но я не могу вспомнить, кто написал оригинал - если вы знаете, добавьте комментарий, чтобы я мог кредитовать их.

from PIL import Image
from django.db import models
from django.contrib.auth.models import User

import os
import settings

class Photo_Ex(models.Model):
    user = models.ForeignKey(User, blank=True, null=True)    
    photo = models.ImageField(upload_to='photos')
    thumbnail = models.ImageField(upload_to='profile_thumb', blank=True,
                              null=True, editable=False)

    def save(self, *args, **kwargs):
        size = (256,256)
        if not self.id and not self.photo:
            return

        try:
            old_obj = Photo_Ex.objects.get(pk=self.pk)
            old_path = old_obj.photo.path
        except:
            pass

        thumb_update = False
        if self.thumbnail:
            try:
                statinfo1 = os.stat(self.photo.path)
                statinfo2 = os.stat(self.thumbnail.path)
                if statinfo1 > statinfo2:
                    thumb_update = True
            except:
                thumb_update = True

        pw = self.photo.width
        ph = self.photo.height
        nw = size[0]
        nh = size[1]

        if self.photo and not self.thumbnail or thumb_update:
            # only do this if the image needs resizing
            if (pw, ph) != (nw, nh):
                filename = str(self.photo.path)
                image = Image.open(filename)
                pr = float(pw) / float(ph)
                nr = float(nw) / float(nh)

                if image.mode not in ('L', 'RGB'):
                    image = image.convert('RGB')

                if pr > nr:
                    # photo aspect is wider than destination ratio
                    tw = int(round(nh * pr))
                    image = image.resize((tw, nh), Image.ANTIALIAS)
                    l = int(round(( tw - nw ) / 2.0))
                    image = image.crop((l, 0, l + nw, nh))
                elif pr < nr:
                    # photo aspect is taller than destination ratio
                    th = int(round(nw / pr))
                    image = image.resize((nw, th), Image.ANTIALIAS)
                    t = int(round(( th - nh ) / 2.0))
                    image = image.crop((0, t, nw, t + nh))
                else:
                    # photo aspect matches the destination ratio
                    image = image.resize(size, Image.ANTIALIAS)

            image.save(self.get_thumbnail_path())
            (a, b) = os.path.split(self.photo.name)
            self.thumbnail = a + '/thumbs/' + b
            super(Photo_Ex, self).save()
            try:
                os.remove(old_path)
                os.remove(self.get_old_thumbnail_path(old_path))
            except:
                pass

    def get_thumbnail_path(self):
        (head, tail) = os.path.split(self.photo.path)
        if not os.path.isdir(head + '/thumbs'):
            os.mkdir(head + '/thumbs')
        return head + '/thumbs/' + tail

    def get_old_thumbnail_path(self, old_photo_path):
        (head, tail) = os.path.split(old_photo_path)
        return head + '/thumbs/' + tail   

Ответ 3

Не уверен в том, что вы отправили код, потому что я никогда не использую модели как таковые, но есть и другой метод.

Вы можете реализовать свой собственный FileUploadHandler для обработки загрузок файлов изображений. Пример. здесь. Сразу после строки 37 (dest.close()) используйте функцию thumbnail(upload_dir + upload.name) (тот, который вы отправили).

Надеюсь, это поможет вам.

Ответ 4

Ключевой вопрос: , когда должен быть создан эскиз?

  • Динамически, когда пользователь запрашивает миниатюрное изображение?
  • Или вы хотите создать файл физического диска всякий раз, когда страна находится в INSERTED/UPDATEd в базе данных.

Если (1) я предлагаю вам создать представление, которое отображает URL-адрес /flagthumbnail/countryid. Тогда метод просмотра должен был бы:

  • Получить экземпляр страны из базы данных
  • Прочитайте изображение флага в изображении PIL и измените его размер.
  • Создайте (и верните) HTTPResponse с правильным типом контента и напишите PIL-образ на ответ.

Всякий раз, когда вам нужно отобразить значок эскиза, просто используйте <a href="/flagthumbnail/countryid">.

Если (2), вы можете подключиться к сигналу Django django.db.models.signals.post_save, а в обработчике сигнала создать и сохранить файл миниатюр.

Ответ 5

Я думаю, это зависит от того, как и когда вы используете свои миниатюры.

Если вы хотите создать несколько миниатюр каждый раз, когда Страна сохранена, вы можете сделать это так:

from django.db import models

# This is to list all the countries
# For starters though, this will be just United Kingdom (GB)
class Country(models.Model):
    name = models.CharField(max_length=120, help_text="Full name of country")
    code = models.CharField(max_length=2, help_text="This is the ISO 3166 2-letter country code (see: http://www.theodora.com/country_digraphs.html)")
    flag = models.ImageField(upload_to="images/uploaded/country/", max_length=150, help_text="The flag image of the country.", blank=True)

    class Meta:
        verbose_name_plural = "Countries"

    def __unicode__(self):
        return self.name

    def save(self, force_insert=False, force_update=False):
        resize_image(self.flag)
        super(Country, self).save(force_insert, force_update)

Если вы не на 100% уверены, какие размеры вам понадобятся вам, вы можете изменить их размер в последнюю минуту. Я видел это эффективно с помощью templatetag (я верю в версию на Pinax). Вы создаете templatetag, который принимает изображение и размер, затем создавайте и сохраняйте изображение соответствующего размера, если вам нужно, или отображаете ранее созданный, если он есть. Он работает очень хорошо.

Ответ 6

Переопределение метода сохранения является хорошим вариантом, но в этом случае у меня было бы больше соблазна использовать signal. Сигналы Django позволяют вам "прослушивать" данный тип типа различных событий; в этом случае вас будет интересовать событие post_save.

Я обычно подписываюсь на такие сигналы в моем файле models.py. Код для вас выглядит примерно так:

from django.db.models.signals import post_save
from models import Country

def resize_image(sender, **kwargs):
    country = kwargs["instance"]
    resize_image(country.flag) # where resize_image generates a thumbnail given a Country instance

post_save.connect(resize_image, sender=Country)

Ответ 7

Ryan - правильные сигналы, это лучший способ пойти, однако преимущество завышенной экономии заключается в том, что мы можем получить старые и новые пути к изображению, посмотреть, изменилось ли изображение (и если оно создало новый миниатюру), сохраните экземпляр модели, а затем удалите старое изображение и миниатюру.

Помните, что django не очищает старые изображения для вас, поэтому, если у вас есть script, чтобы проверить, что изображения/эскизы все еще используются, и очистите все, что не приведет к утечке дискового пространства. (Это может или не может быть проблемой для вас в зависимости от размера и частоты обновлений)

Я не уверен, как вы могли бы сделать это с помощью сигнала post_save, и я не знаю достаточно о сигналах (это исследование сегодня!), чтобы узнать, есть ли подходящий сигнал pre_save. Если я найду его, я переписал вышеприведенный код, чтобы использовать сигналы как общий предварительный список.

Ответ 8

Вы можете попробовать:

обтекатель изображений

функции:

  • Позволяет установить центр внимания изображения... главы больше не будут сокращаться.
  • Видео thumbnail
  • Предотвращает компоновку ссылок между сайтами
  • Простая настройка и использование

Ответ 9

Другой альтернативой является использование Imagemagick напрямую, если вы хотите избежать нескольких трудностей с Pillow и Python 3, таких как этот.

from subprocess import call
call(['convert', img_path_file_name, '-thumbnail', target_size_str, '-antialias', style_path_file_name])

Вы можете вызвать это при сохранении модели или в теге шаблона, чтобы создать одноразовую управляемую копию исходного файла в режиме кэширования файлов или даже задачу сельдерея.

Вы можете получить пример того, как я использовал это в одном из моих проектов:

Ответ 10

Я также клянусь Джастином Дрисколлом django-photologue также отлично подходит для изменения размера. Это:

  • Изменить размеры (это можно масштабировать до высоты или ширины пропорционально)
  • Культуры (вид интеллектуально: из центра, сверху, слева, снизу или справа)
  • Необязательно увеличивает
  • Можно добавить эффекты (например, "цвет", "яркость", "контраст" и "резкость", а также фильтры "Найти края" и "Эмбосс". ваши эскизы черно-белые.)
  • Можно добавить простой водяной знак
  • Кэш результаты

В принципе это потрясающе.