如何在保存前使用PIL调整新上传的图像大小?

时间:2011-11-01 17:55:02

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

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

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

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)

7 个答案:

答案 0 :(得分:11)

image = image.resize(size, Image.ANTIALIAS)

调整大小是非破坏性的,它会返回一个新图像。

答案 1 :(得分:11)

我将django-resized用于我的项目。

答案 2 :(得分:2)

我在保存之前搜索了调整上传照片大小的解决方案。这里和那里有很多信息位和位(在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

答案 3 :(得分:1)

这是对我有用的东西,从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

答案 4 :(得分:0)

如果你正在使用python&lt; 3,你应该考虑使用:

from __future__ import division

在这种情况下,你的师的结果编号将是浮动的。

答案 5 :(得分:0)

我还没有测试过,但它可能会有所帮助!!

#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)

在你的模特中

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)

我真的不知道,保存之前或之后有什么不同。

答案 6 :(得分:0)

一个干净的解决方案是使用此方法在保存之前调整表单中的图像大小:(您需要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