【问题标题】:Image resizing with django?用 django 调整图像大小?
【发布时间】:2009-07-22 12:32:54
【问题描述】:

我是 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?...)放在哪里以及它在上下文中如何工作? ...我只是需要一些帮助来理解和解决这个问题。

谢谢!

【问题讨论】:

    标签: python django image python-imaging-library


    【解决方案1】:

    如果你没问题,有一个 Django 应用程序已经准备好了,可以做你想做的事: https://github.com/sorl/sorl-thumbnail

    【讨论】:

    • 谢谢leafnode,我听说过这个,我会看看。但我希望首先尝试了解做事的基本方式,希望能让我更好地理解这一切是如何运作的……也许不值得这样做? ...但我认为这样做是为了让我成为更好的 Python/Django 开发人员吗?
    • 如果您想了解详细信息,请查看其他答案,但请记住它适用于用户端表单。
    • 这需要你设置内存缓存。如果您只有一个小网站并且只想调整图片大小会怎样?
    • 最近sorl有一个新的mantainer,在开发版本中有很多新的工作。 github.com/mariocesar/sorl-thumbnail
    • 对于这么简单的任务,我认为包含一个庞大的第三方应用程序不是一个好选择。未来的更新呢?也许 sorl 被抛弃了……如果可能的话,保持代码简单和独立肯定是更好的选择。
    【解决方案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   
    

    【讨论】:

    • image.thumbnail(size) 函数可以为您执行此操作,您实际上不需要计算宽度/高度/比率等。
    【解决方案3】:

    不确定您发送的代码,因为我从不使用模型,但还有另一种方法。

    您可以实现自己的FileUploadHandler 来处理图像文件上传。例子是 here。 在第 37 行 (dest.close()) 之后使用 thumbnail(upload_dir + upload.name) 函数(您发送的那个)。

    希望对你有帮助。

    【讨论】:

      【解决方案4】:

      一个关键问题是:何时应该生成缩略图?

      1. 在用户请求缩略图时动态显示?
      2. 或者您是否想在数据库中插入/更新一个国家/地区时创建一个物理磁盘文件。

      如果 (1) 我建议您创建一个映射到 url /flagthumbnail/countryid 的视图。然后视图方法必须:

      1. 从数据库中获取国家实例
      2. 将标志图像读入 PIL 图像并调整其大小。
      3. 创建(并返回)具有正确内容类型的 HTTPResponse,并将 PIL 图像写入响应。

      当您需要显示缩略图标志时,只需使用&lt;a href="/flagthumbnail/countryid"&gt;

      如果 (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% 确定您需要什么样的图片尺寸,您可以在最后一刻调整它们的尺寸。我已经看到使用模板标签有效地完成了这一点(我相信 Pinax 上的一个版本)。您创建一个带有图像和大小的模板标签,然后根据需要创建并保存适当大小的图像,或者显示先前创建的图像(如果存在)。效果很好。

        【讨论】:

        • 你给我的上面的代码更多的是我所追求的......所以当模型被保存时,文件被上传,保存,然后缩略图也被制作和保存。它会经常发生变化,这似乎是最有意义的。因此,我将在哪里保留 resize_image() 函数?在一个名为 resize_image.py 的文件中?我将把该文件放在哪里并从哪里导入?在我的 Django 项目中?或者在我的站点包或其他东西中? (这也是我有点困惑的地方)
        • ...也...我以前用你放的那种代码见过这个... def save(self, *args, **kwargs): 我见过这个*args 和 **kwargs 几次?这是什么意思?如果我把它们放在里面或外面等会有什么影响吗?
        • @littlejim84: 自从你问以来已经有一段时间了.. 但是,你的回答:args 通常表示函数的未命名参数,而 kwargs 将是命名参数(例如 my_func(arg1, arg2, namearg1 ='dd', namearg2='ddd') ..... 然后在 my_func(self, *args, **kwargs), args=[arg1,arg2], kwargs={'namearg1':'dd', ' namearg2':'ddd'}... 将这些传递给被覆盖的函数很有用,这样您就不会破坏原始函数中的任何功能。
        【解决方案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)
        

        【讨论】:

        • 这有点令人困惑,不是吗?也许重命名 resize_image 函数调用?
        • 这更像是一种观点,但信号在 Django 中被称为反模式。避开他们。您真正应该使用它们的唯一时间是当您尝试在多个模型上实现相同的功能时。然后你必须问问自己你的模型是否正确规范化。
        【解决方案7】:

        你可以试试:

        image headcut

        特点:

        • 允许您设置图像的注意力中心......头部不会再被切割了。
        • 视频缩略图
        • 防止跨站图片链接
        • 简单的设置和使用

        【讨论】:

          【解决方案8】:

          Ryan 是正确的,信号是更好的方法,但是覆盖保存的优点是我们可以获取新旧图像路径,查看图像是否已更改(以及是否已创建新缩略图),保存模型实例,然后删除旧图像和缩略图。

          请记住 django 不会为您清理旧图像,因此除非您有脚本来检查图像/缩略图是否仍在使用中并清除任何不使用的图像,否则您将泄漏磁盘空间。 (这对您来说可能是也可能不是问题,具体取决于图像大小和更新频率)

          我不确定您如何使用 post_save 信号来做到这一点,而且我对信号的了解还不够(这是今晚的研究!)以了解是否有合适的 pre_save 信号。如果我找到了,那么我将重写上面的代码,将信号用作通用的预保存侦听器。

          【讨论】:

          • 是的,还有一个 pre_save 信号。我发现信号通常更适合使用,但肯定有优先使用覆盖 save() 的时候。
          【解决方案9】:

          另一种选择是直接使用 Imagemagick,如果您想避免 Pillow 和 Python 3 的一些困难,例如 this one

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

          您可以在模型保存或模板标签上调用它,以文件缓存方式生成原始文件的一次性操作副本,甚至是 celery 任务。

          您可以获取我在其中一个项目中如何使用它的示例:

          【讨论】:

            【解决方案10】:

            我还发誓 Justin Driscoll 的 django-photologue 也非常适合调整大小。它:

            1. 调整大小(即可以按比例缩放到高度或宽度)
            2. 作物(有点智能:从中心、顶部、左侧、底部或右侧)
            3. 可选择升迁
            4. 可以添加效果(例如“颜色”、“亮度”、“对比度”和“锐度”以及“查找边缘”和“浮雕”等滤镜。锐化您的图像。制作你的缩略图是黑白的。)
            5. 可以添加简单的水印
            6. 缓存结果

            基本上它很棒。

            【讨论】:

              【解决方案11】:

              一种非常简单的方法是使用django-imagefit 调整和/或裁剪显示的图像。

              它将保留原始图像,因此您可以拥有多个版本,稍后重构您的前端,它也适用于非模型图像。

              【讨论】:

                【解决方案12】:

                您可以使用 'Pillow' 在数据库模型中保存数据后更改照片大小,例如我要去 要将用户个人资料图像更改为 300 x 300 ,我只需在模型中定义 save() 方法并更改大小,只需按照以下步骤操作:

                1- 首先确保安装了“Pillow”。

                2- 这是我在配置文件模型类中的代码:

                from PIL import Image
                
                class Profile(models.Model):
                
                    PRF_image = models.ImageField(upload_to='profile_img', blank=True, null=True)
                
                
                
                    def save(self , *args , **kwargs):
                        # change profile image size
                        img = Image.open(self.PRF_image.path)
                        if img.width > 300 or img.height > 300:
                            output_size = (300, 300)
                            img.thumbnail(output_size)
                            img.save(self.PRF_image.path)
                

                这只是示例,根据您的情况/要求进行更改。

                别忘了在你的视图中使用 your_object.save() 来使用这个方法。

                希望对你有帮助

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2017-01-01
                  • 2011-10-24
                  • 2020-05-10
                  • 2015-08-06
                  • 1970-01-01
                  • 2010-11-20
                  • 1970-01-01
                  相关资源
                  最近更新 更多