我搜索了如何在保存之前调整上传照片大小的解决方案。在StackOverflow上有很多信息,但没有完整的解决方案。这是我认为适用于需要此功能的人的最终解决方案。
开发亮点:
- 使用Pillow进行图像处理(需要两个软件包:libjpeg-dev、zlib1g-dev)
- 使用Model和ImageField作为存储方式
- 使用multipart/form的HTTP POST或PUT
- 无需手动将文件保存到磁盘
- 创建多个分辨率并存储其尺寸
- 未修改模型本身
安装Pillow:
$ 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_w和image_h是表中缓存的尺寸。请注意,声明ImageField时需要添加引号。但是,缩略图不需要宽度和高度字段(因此您可以看到不同之处)。
这就是模型。我们不需要覆盖或添加任何函数到模型中。
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',)
我们不希望序列化器处理上传的照片,我们将自己处理。因此,在字段中不需要包含'image'。
myapp/views.py
@api_view(['POST'])
@parser_classes((MultiPartParser,))
def handle_uploaded_image(request):
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(内存中内容的文件对象)将图像推送到ImageField,并让ImageField完成其工作。我们希望多个图像副本具有相同的文件名和不同的后缀。
鸣谢
以下是我参考构建此解决方案的代码链接:
如果您还想验证图像,请参见此内容:https://dev59.com/kmIi5IYBdhLWcg3w_AfV#20762344