【问题标题】:resize image on save保存时调整图像大小
【发布时间】:2010-10-12 01:01:08
【问题描述】:

如何在 Django 中上传图片后轻松调整其大小?我正在使用 Django 1.0.2 并且我已经安装了 PIL。

我正在考虑重写模型的 save() 方法来调整它的大小,但我真的不知道如何开始并重写它。

有人能指出我正确的方向吗?谢谢:-)

@Guðmundur H: 这不起作用,因为 django-stdimage 包在 Windows 上不起作用:-(

【问题讨论】:

    标签: django django-forms image-manipulation


    【解决方案1】:

    我推荐使用来自django-stdimage 的 StdImageField,它应该会为你处理所有的脏活。它易于使用,您只需在字段定义中指定调整后图像的尺寸即可:

    class MyModel(models.Model):
        image = StdImageField(upload_to='path/to/img',  size=(640, 480))
    

    查看文档 - 它也可以制作缩略图。

    【讨论】:

    • 这似乎不适用于 Windows 机器 :-( 这是 django-stdimage 包中的一个已知错误。存在某种递归错误
    • Windows 无法递归。这是 linux 独有的功能 :)
    • 在 osx 10.7 上失败,超时而不抛出错误,或提供任何有意义的调试信息。权限绝对正确/之前使用普通文件字段。
    • 这种情况下不会调整大小以保持纵横比
    【解决方案2】:

    您应该使用一种方法来处理上传的文件,如Django documentation所示。

    在这种方法中,您可以将块连接到一个变量中(而不是直接将它们写入磁盘),从该变量创建一个 PIL 图像,调整图像大小并将其保存到磁盘。

    在 PIL 中,您应该查看 Image.fromstringImage.resize

    【讨论】:

      【解决方案3】:

      我使用此代码来处理上传的图像,在内存中调整它们的大小(而不将它们永久保存在磁盘上),然后将拇指保存在 Django ImageField 上。 希望能有所帮助。

          def handle_uploaded_image(i):
              import StringIO
              from PIL import Image, ImageOps
              import os
              from django.core.files import File
              # read image from InMemoryUploadedFile
              image_str = “”
              for c in i.chunks():
                  image_str += c
      
              # create PIL Image instance
              imagefile  = StringIO.StringIO(image_str)
              image = Image.open(imagefile)
      
              # if not RGB, convert
              if image.mode not in (“L”, “RGB”):
                  image = image.convert(“RGB”)
      
              #define file output dimensions (ex 60x60)
              x = 130
              y = 130
      
              #get orginal image ratio
              img_ratio = float(image.size[0]) / image.size[1]
      
              # resize but constrain proportions?
              if x==0.0:
                  x = y * img_ratio
              elif y==0.0:
                  y = x / img_ratio
      
              # output file ratio
              resize_ratio = float(x) / y
              x = int(x); y = int(y)
      
              # get output with and height to do the first crop
              if(img_ratio > resize_ratio):
                  output_width = x * image.size[1] / y
                  output_height = image.size[1]
                  originX = image.size[0] / 2 - output_width / 2
                  originY = 0
              else:
                  output_width = image.size[0]
                  output_height = y * image.size[0] / x
                  originX = 0
                  originY = image.size[1] / 2 - output_height / 2
      
              #crop
              cropBox = (originX, originY, originX + output_width, originY + output_height)
              image = image.crop(cropBox)
      
              # resize (doing a thumb)
              image.thumbnail([x, y], Image.ANTIALIAS)
      
              # re-initialize imageFile and set a hash (unique filename)
              imagefile = StringIO.StringIO()
              filename = hashlib.md5(imagefile.getvalue()).hexdigest()+’.jpg’
      
              #save to disk
              imagefile = open(os.path.join(‘/tmp’,filename), ‘w’)
              image.save(imagefile,’JPEG’, quality=90)
              imagefile = open(os.path.join(‘/tmp’,filename), ‘r’)
              content = File(imagefile)
      
              return (filename, content)
      
      #views.py
      
          form = YourModelForm(request.POST, request.FILES, instance=profile)
              if form.is_valid():
                  ob = form.save(commit=False)
                  try:
                      t = handle_uploaded_image(request.FILES[‘icon’])
                      ob.image.save(t[0],t[1])
                  except KeyError:
                      ob.save()
      

      【讨论】:

      • 更新: 这将接受 utf8 图像文件名
      • 你的方法效果很好。感谢您的代码!不过,我建议您将上面的变量名从 str 更改为其他名称,因为它会影响 python BIF 函数 str()。如果有人对您编写的代码稍作改动并在变量声明下方使用 BIF,则会导致错误(python 会说 str 不可调用)
      • 嗨,我有一个问题:在这里,您将图像上传到 tmp 目录: imagefile = open(os.path.join('/tmp',filename), 'w') 再次,在这里ob.image.save(t[0],t[1]) ... 那么 tmp 目录中的图像接下来会发生什么???
      【解决方案4】:

      我强烈推荐sorl-thumbnail 应用程序来轻松透明地处理图像大小调整。它出现在我开始的每个 Django 项目中。

      【讨论】:

      【解决方案5】:

      这是使用表单的完整解决方案。我为此使用了管理员视图:

      class MyInventoryItemForm(forms.ModelForm):
      
          class Meta:
              model = InventoryItem
              exclude = ['thumbnail', 'price', 'active']
      
          def clean_photo(self):
              import StringIO
              image_field = self.cleaned_data['photo']
              photo_new = StringIO.StringIO(image_field.read())
      
              try:
                  from PIL import Image, ImageOps
      
              except ImportError:
                  import Image
                  import ImageOps
      
              image = Image.open(photo_new)
      
              # ImageOps compatible mode
              if image.mode not in ("L", "RGB"):
                  image = image.convert("RGB")
      
              image.thumbnail((200, 200), Image.ANTIALIAS)
      
              image_file = StringIO.StringIO()
              image.save(image_file, 'png')
      
              image_field.file = image_file
      
              return image_field
      

      我的库存模型如下所示:

      class InventoryItem(models.Model):
      
          class Meta:
              ordering = ['name']
              verbose_name_plural = "Items"
      
          def get_absolute_url(self):
              return "/products/{0}/".format(self.slug)
      
          def get_file_path(instance, filename):
      
              if InventoryItem.objects.filter(pk=instance.pk):
                  cur_inventory = InventoryItem.objects.get(pk=instance.pk)
                  if cur_inventory.photo:
                      old_filename = str(cur_inventory.photo)
                      os.remove(os.path.join(MEDIA_ROOT, old_filename))
      
              ext = filename.split('.')[-1]
              filename = "{0}.{1}".format(uuid.uuid4(), ext)
              return os.path.join('inventory', filename)
              #return os.path.join(filename)
      
          def admin_image(self):
              return '<img height="50px" src="{0}/{1}"/>'.format(MEDIA_URL, self.photo)
          admin_image.allow_tags = True
      
          photo = models.ImageField(_('Image'), upload_to=get_file_path, storage=fs, blank=False, null=False)
          thumbnail = models.ImageField(_('Thumbnail'), upload_to="thumbnails/", storage=fs,     blank=True, null=True)
      

      ....

      我结束了覆盖模型的保存功能,而是保存照片和拇指,而不是仅仅调整照片的大小:

      def save(self):
      
          # Save this photo instance first
          super(InventoryItem, self).save()
      
          from PIL import Image
          from cStringIO import StringIO
          from django.core.files.uploadedfile import SimpleUploadedFile
      
          # Set our max thumbnail size in a tuple (max width, max height)
          THUMBNAIL_SIZE = (200, 200)
      
          # Open original photo which we want to thumbnail using PIL's Image object
          image = Image.open(os.path.join(MEDIA_ROOT, self.photo.name))
      
          if image.mode not in ('L', 'RGB'):
              image = image.convert('RGB')
      
          image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
      
          # Save the thumbnail
          temp_handle = StringIO()
          image.save(temp_handle, 'png')  # image stored to stringIO
      
          temp_handle.seek(0)  # sets position of file to 0
      
           # Save to the thumbnail field
           suf = SimpleUploadedFile(os.path.split(self.photo.name)[-1],
              temp_handle.read(), content_type='image/png')  # reads in the file to save it
      
          self.thumbnail.save(suf.name+'.png', suf, save=False)
      
          #Save this photo instance again to save the thumbnail
          super(InventoryItem, self).save()
      

      两者都工作得很好,但取决于你想做什么:)

      【讨论】:

        【解决方案6】:

        我知道这是旧的,但是对于任何偶然发现它的人,有一个包,django-thumbsDjango-thumbs - Easy powerful thumbnails for Django integrated with StorageBackend,它会自动生成您指定大小的缩略图,如果您不这样做,则不会生成。然后,您可以使用所需的尺寸调用所需的缩略图。

        例如,如果您希望图像具有 64x64 和 128x128 的缩略图,您只需导入 thumbs.models.ImageWithThumbsField,并使用它来代替 ImageField。在字段定义中添加参数sizes=((64,64),(128,128)),然后您可以从模板中调用:

        {{ ClassName.field_name.url_64x64 }}
        

        {{ ClassName.field_name.url_128x128 }}
        

        显示缩略图。瞧!所有工作都在这个包中为您完成。

        【讨论】:

        • 如果您使用 South 进行数据库维护,您还需要添加一个自省,例如:
        • from south.modelsinspector import add_introspection_rules add_introspection_rules([ ( [models.ImageField], # Class(es) these apply to [], # Positional arguments (not used) { # Keyword argument "sizes": ["sizes", {"default": None}], }, ), ], ["^django_thumbs\.db\.models\.ImageWithThumbsField"])
        【解决方案7】:

        如果您使用的是 Django Rest Framework,这可能有用:

        首先定义压缩和调整图像大小的函数

        def compress_image(photo):
        # start compressing image
        image_temporary = Image.open(photo)
        output_io_stream = BytesIO()
        image_temporary.thumbnail((1250, 1250), Image.ANTIALIAS)
        
        # change orientation if necessary
        for orientation in ExifTags.TAGS.keys():
            if ExifTags.TAGS[orientation] == 'Orientation':
                break
        exif = dict(image_temporary._getexif().items())
        # noinspection PyUnboundLocalVariable
        if exif.get(orientation) == 3:
            image_temporary = image_temporary.rotate(180, expand=True)
        elif exif.get(orientation) == 6:
            image_temporary = image_temporary.rotate(270, expand=True)
        elif exif.get(orientation) == 8:
            image_temporary = image_temporary.rotate(90, expand=True)
        
        # saving output
        image_temporary.save(output_io_stream, format='JPEG', quality=75, optimize=True, progressive=True)
        output_io_stream.seek(0)
        photo = InMemoryUploadedFile(output_io_stream, 'ImageField', "%s.jpg" % photo.name.split('.')[0],
                                     'image/jpeg', getsizeof(output_io_stream), None)
        return photo
        

        其次,现在你可以使用 Serializers 中的函数了:

        class SomeSerializer(serializers.ModelSerializer):
        def update(self, instance, validated_data):
            # сжимаем рисунок
            if 'photo' in validated_data:           
                validated_data.update({'photo': compress_image(validated_data['photo'])})
        
            return super(SomeSerializer, self).update(instance, validated_data)
        
        def create(self, validated_data):
            # сжимаем рисунок
            if 'photo' in validated_data:
                validated_data.update({'photo': compress_image(validated_data['photo'])})
        
            return super(SomeSerializer, self).create(validated_data)
        

        【讨论】:

        • 这个答案是否真的是为了解决问题的问题,或者您在大约之后猜测解决方案。 10 年后问这个问题?
        猜你喜欢
        • 1970-01-01
        • 2011-10-24
        • 1970-01-01
        • 2017-08-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-06
        • 2013-07-26
        相关资源
        最近更新 更多