【问题标题】:How to resize the new uploaded images using PIL before saving?如何在保存之前使用 PIL 调整新上传的图像的大小?
【发布时间】:2011-12-19 17:10:57
【问题描述】:

我想将新图像的高度和宽度调整为 800 像素并保存它们。并且应用程序不得存储真实图像。有什么帮助吗?

这是我的代码,它保存原始图像而不是调整大小的照片:

models.py:

class Photo(models.Model):        
    photo = models.ImageField(upload_to='photos/default/')


    def save(self):

        if not self.id and not self.photo:
            return            

        super(Photo, self).save()

        image = Image.open(self.photo)
        (width, height) = image.size

        "Max width and height 800"        
        if (800 / width < 800 / height):
            factor = 800 / height
        else:
            factor = 800 / width

        size = ( width / factor, height / factor)
        image.resize(size, Image.ANTIALIAS)
        image.save(self.photo.path)

【问题讨论】:

  • 您的代码有什么问题?你还没有说它做错了什么。您显示的代码会将图像保存到数据库。如果您不想保存未调整大小的图像,则必须在保存之前调整大小 - 即在表单级别
  • @pastylegs 在表单级别我应该使用哪种方法以及如何使用?
  • 你是通过 django admin 上传还是使用自定义表单?
  • 通过自定义表单。第一个答案解决了我的问题。谢谢。
  • 如果有人出于某种原因使用该代码,在因子部分有一些错误:因子应该是一个浮点数(即 800.0 / 宽度),你应该在设置大小时乘以因子(不是除法!),记得在设置大小时要转换回int,。此外,当然,接受答案的修复。

标签: django python-imaging-library django-models


【解决方案1】:

一个干净的解决方案是在保存之前使用此方法调整表单中的图像大小:(您需要pip install pillow

import os
from io import BytesIO
from PIL import Image as PilImage
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile

def resize_uploaded_image(image, max_width, max_height):
    size = (max_width, max_height)

    # Uploaded file is in memory
    if isinstance(image, InMemoryUploadedFile):
        memory_image = BytesIO(image.read())
        pil_image = PilImage.open(memory_image)
        img_format = os.path.splitext(image.name)[1][1:].upper()
        img_format = 'JPEG' if img_format == 'JPG' else img_format

        if pil_image.width > max_width or pil_image.height > max_height:
            pil_image.thumbnail(size)

        new_image = BytesIO()
        pil_image.save(new_image, format=img_format)

        new_image = ContentFile(new_image.getvalue())
        return InMemoryUploadedFile(new_image, None, image.name, image.content_type, None, None)

    # Uploaded file is in disk
    elif isinstance(image, TemporaryUploadedFile):
        path = image.temporary_file_path()
        pil_image = PilImage.open(path)

        if pil_image.width > max_width or pil_image.height > max_height:
            pil_image.thumbnail(size)
            pil_image.save(path)
            image.size = os.stat(path).st_size

    return image

然后在你的表单中image字段的clean方法中使用:

class ImageForm(forms.Form):
    IMAGE_WIDTH = 450
    IMAGE_HEIGHT = 450
    
    image = forms.ImageField()

    def clean_image(self):
        image = self.cleaned_data.get('image')
        image = resize_uploaded_image(image, self.IMAGE_WIDTH, self.IMAGE_HEIGHT)
        return image

【讨论】:

    【解决方案2】:

    我尚未测试此解决方案,但它可能会有所帮助!

    #subidas.py
    import PIL
    from PIL import Image
    def achichar_tamanho(path):
        img = Image.open(path)
        img = img.resize((230,230), PIL.Image.ANTIALIAS)
        img.save(path)
    

    在您的 models.py 中,进行以下更改:

    from subidas import achicar_tamanho
    
    class Productos(models.Model):
        imagen = models.ImageField(default="emg_bol/productos/interrogacion.png", upload_to='emg_bol/productos/', blank=True, null=True, help_text="Image of the product")
        tiempo_ultima_actualizacion = models.DateTimeField( help_text="Cuando fue la ultima vez, que se modificaron los datos de este producto", auto_now=True)
        prioridad = models.IntegerField(max_length=4, default=99, help_text="Frecuencia (medida en unidad), con que el producto es despachado para la venta")
        proveedor = models.ForeignKey(Proveedores, help_text="El que provello del producto'")  
    
        def save(self, *args, **kwargs):
            super(Productos,self).save(*args, **kwargs)
            pcod = "%s_%s" % ( re.sub('\s+', '', self.descripcion)[:3], self.id)
            self.producto_cod = pcod
            achichar_tamanho(self.imagen.path)
            super(Productos,self).save(*args, **kwargs)
    

    我真的不知道,实施这些更改之前和之后有什么不同。

    【讨论】:

      【解决方案3】:

      我搜索了一种在保存之前调整上传照片大小的解决方案。这里和那里(在 StackOverflow 中)有很多信息。然而,没有完整的解决方案。这是我认为适合需要它的人的最终解决方案。

      发展亮点

      • 使用 Pillow 进行图像处理(需要两个包:libjpeg-dev、zlib1g-dev)
      • 使用 Model 和 ImageField 作为存储
      • 将 HTTP POST 或 PUT 与 multipart/form 一起使用
      • 无需手动将文件保存到磁盘。
      • 创建多个分辨率并存储它们的尺寸。
      • 没有修改模型本身

      安装枕头

      $ sudo apt-get install libjpeg-dev
      $ sudo apt-get install zlib1g-dev
      $ pip install -I Pillow
      

      myapp/models.py

      from django.db import models
      
      class Post(models.Model):
          caption = models.CharField(max_length=100, default=None, blank=True)
          image_w = models.PositiveIntegerField(default=0)
          image_h = models.PositiveIntegerField(default=0)
          image = models.ImageField(upload_to='images/%Y/%m/%d/', default=None, 
                      blank=True, width_field='image_w', height_field='image_h')
          thumbnail = models.ImageField(upload_to='images/%Y/%m/%d/', default=None,
                      blank=True)
      

      此模型保存带有缩略图和可选标题的照片。这应该类似于现实世界的用例。

      我们的目标是将照片调整为 640x640 并生成 150x150 的缩略图。我们还需要在 Web API 中返回照片的尺寸。 image_wimage_h 是表中的缓存维度。请注意,我们需要在声明 ImageField 时添加配额 '。但是,thumbnail 不需要宽度和高度字段(因此您可以看到不同)。

      这就是模型。我们不需要重写或向模型添加任何函数。

      myapp/serializers.py

      我们将使用ModelSerializer 来处理来自 HTTP POST 的传入数据。

      from rest_framework import serializers
      from myapp.models import Post
      
      class PostSerializer(serializers.ModelSerializer):
          class Meta:
              model = Post
              fields = ('caption',)
      

      我们不希望序列化程序处理上传的照片 - 我们会自己处理。因此,无需在字段中包含“图像”。

      myapp/views.py

      @api_view(['POST'])
      @parser_classes((MultiPartParser,))
      def handle_uploaded_image(request):
          # process images first.  if error, quit.
          if not 'uploaded_media' in request.FILES:
              return Response({'msg': 'Photo missing.'}, status.HTTP_400_BAD_REQUEST)
          try:
              im = Image.open(StringIO(request.FILES['uploaded_media'].read()))
          except IOError:
              return Response({'msg': 'Bad image.'}, status.HTTP_400_BAD_REQUEST)
      
          serializer = PostSerializer(data=request.DATA, files=request.FILES)
          if not serializer.is_valid():
              return Response({'msg': serializer.errors}, status.HTTP_400_BAD_REQUEST)
      
          post = Post.create()
          if serializer.data['caption'] is not None:
              post.caption = serializer.data['caption']
      
          filename = uuid.uuid4()
          name = '%s_0.jpg' % (filename)
          post.image.save(name=name, content=resize_image(im, 640))
          name = '%s_1.jpg' % (filename)
          post.thumbnail.save(name=name, content=resize_image(im, 150))
      
          post.save()
          return Response({'msg': 'success',
              'caption': post.caption,
              'image': {
                  'url': request.build_absolute_uri(post.image.url),
                  'width': post.image_w,
                  'height': post.image_h,
              }
              'thumbnail': request.build_absolute_uri(post.thumbnail.url),
          }, status.HTTP_201_CREATED)
      

      共享 py 中的辅助函数

      def resize_image(im, edge):
          (width, height) = im.size
          (width, height) = scale_dimension(w, h, long_edge=edge)
          content = StringIO()
          im.resize((width, height), Image.ANTIALIAS).save(fp=content, format='JPEG', dpi=[72, 72])
          return ContentFile(content.getvalue())
      
      def scale_dimension(width, height, long_edge):
          if width > height:
              ratio = long_edge * 1. / width
          else:
              ratio = long_edge * 1. / height
          return int(width * ratio), int(height * ratio)
      

      在这里,我们不使用 Image.thumbnail,因为它会改变原始图像。如果我们要存储多个分辨率(例如用于不同目的的低分辨率和高分辨率),则此代码适用。我发现两次调整传入消息的大小会降低质量。

      我们也不将图像直接保存到磁盘。我们通过 ContentFile(内存中的内容的 File 对象)将图像推送到 ImageField 并让 ImageField 完成它的工作。我们希望图像的多个副本具有相同的文件名和不同的后缀。

      学分

      以下是我在构建此解决方案时参考的代码链接:

      如果您还想验证图像,请参阅:https://stackoverflow.com/a/20762344/3731039

      【讨论】:

        【解决方案4】:

        这对我有用,灵感来自 django-resized

        @receiver(pre_save, sender=MyUser)
        @receiver(pre_save, sender=Gruppo)
        def ridimensiona_immagine(sender, instance=None, created=False, **kwargs):
            foto = instance.foto
        
            foto.file.seek(0)
            thumb = PIL.Image.open(foto.file)
            thumb.thumbnail((
                200, 
                200
                ), PIL.Image.ANTIALIAS)
        
        
            buffer = StringIO.StringIO()
            thumb.save(buffer, "PNG")
            image_file = InMemoryUploadedFile(buffer, None, 'test.png', 'image/png', buffer.len, None)
        
            instance.foto.file = image_file
        

        【讨论】:

          【解决方案5】:

          我在我的项目中使用django-resized

          【讨论】:

          • +1 因为这个项目与 django-storages+boto 完美开箱即用。太好了,让我省了很多麻烦!
          • 这个项目无法使用 django storages + boto 开箱即用,我从 api 中断了文件上传
          • 我也喜欢它;由于项目很小,实际上我只是复制了表单类并使用它...
          【解决方案6】:
          image = image.resize(size, Image.ANTIALIAS)
          

          resize 是非破坏性的,它会返回一个新图像。

          【讨论】:

          • 我知道,但是如何在不保存原件的情况下保存调整大小的照片?
          • 用 "image = image.resize(size, Image.ANTIALIAS)" 替换行 "image.resize(size, Image.ANTIALIAS)" 现在你正在调整大小但忽略结果并保存原始图像。
          【解决方案7】:

          如果你使用 python

          from __future__ import division
          

          在这种情况下,除法的结果数将是浮点数。

          【讨论】:

          • 使用// 可能是更好的选择,因为其余代码可能假设除法结果将是浮动的。
          • 这似乎完全是题外话:-/
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-16
          • 2012-12-14
          • 2011-09-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-03
          相关资源
          最近更新 更多