让 ImageField 更懂我的心

492 阅读2分钟

前言介绍

Django 的 ORM 提供了一个 ImageField 为我们在图片的存取上带来了极大的便利。不过在 Web 中,我们经常有这样的一个需求,就是不同的页面可能呈现不同尺寸的图片,例如一个图片浏览器通常会在列表页中显示方形的缩略图而在详情页中才展现完整尺寸的图片,为了页面显示的美观,我们必须在满足不同比例显示的同时保证美观,也就是不能让图片变形。一种处理方式就是在图片上传的时候就自动生成所需的缩略图,然后根据需求调用不同的图片达到所需的效果。要让 ImageField 更懂我们的心,看来需求对它动动手脚。

要想定制它,首先需要了解 ImageField 是如何工作的。通过阅读官方文档,可以快速的知晓,当我们在对 ImageField 做操作时,实际的奥秘来源于 ImageFieldFile。之后再阅读一下源码,基本来说就大概知道其工作原理了,比如为什么能够通过 model.image_field.url 来获得基于 MEDIA_URL 的链接。 这次我们实现一个通过orm的多方式去上传图片并加水印的功能:

版本对比

  1. 默认流程
 from django.db import models

 class UploadImages(models.Model):

     images = models.ImagesFields(upload_to='media', verbose_name='上传的图片')

     desc = models.CharFields(max_length=255, verbose_name='描述信息')

这样我们的图片通过前端调用: curl -x 192.168.10.197:8888 -F "file=@a.jpeg" -F "usernote=mislaaa" -F "rx=100.0" -F "ry=300.5" http://localhost/upload 即可实现。

2.改造后

import tempfile
from PIL import Image
from django.core.files.base import ContentFile
from django.db import models
from django.db.models.fields.files import ImageFieldFile


class WaterMarkFieldFile(ImageFieldFile):

    def save(self, name, content, save=True):
        # Repopulate the image dimension cache.
    temp_file = tempfile.TemporaryFile()
    target = Image.new('RGB', (1080, 1080), (0, 0, 0, 0))
    # 打开上传的图片
    nike_image = Image.open(content)
    nike_image = nike_image.resize((1080, 1080))
    # 打开Logo
    hnu_image = Image.open("/Users/wanglei/Desktop/django_demo/static/WechatIMG5479.png")

    # 设置水印logo的大小
    image_x, image_y = nike_image.size
    watermark_x, watermark_y = hnu_image.size
    scale = 12
    watermark_scale = max(image_x / (scale * watermark_x), image_y / (scale * watermark_y))
    new_size = (int(watermark_x * watermark_scale), int(watermark_y * watermark_scale))
    rgba_watermark = hnu_image.resize(new_size, resample=Image.ANTIALIAS)

    # 分离透明通道
    r, g, b, a = rgba_watermark.split()
    # 将头像贴到底图
    nike_image.convert("RGB")
    target.paste(nike_image, (0, 0))

    # 将装饰贴到底图
    hnu_image.convert("RGB")
    target.paste(rgba_watermark, (
        nike_image.size[0] - rgba_watermark.size[0] - 30,
        nike_image.size[1] - rgba_watermark.size[1] - 20
    ), mask=a)

    # 保存图片
    target.save(temp_file, 'jpeg')
    temp_file.seek(0)
    content2 = ContentFile(temp_file.read())
    content.close()
    temp_file.close()
    super(WaterMarkFieldFile, self).save(name, content2, save)

class WaterMarkImageField(models.ImageField):

attr_class = WaterMarkFieldFile

def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs):
    self.width_field, self.height_field = width_field, height_field
    models.ImageField.__init__(self, verbose_name, name, width_field, height_field, ** kwargs)

class TestImageModel(models.Model):

images = WaterMarkImageField(upload_to='media')
可以给全局的图片上传增加对的图片的特殊处理方式.

#### 效果展示

没加水印

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/56066a26596642c1b8d642b6d17d4074~tplv-k3u1fbpfcp-watermark.image?)

加了水印(这里以图片水印展示为例子)

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8fb2783727f2453dafad87d64059b210~tplv-k3u1fbpfcp-watermark.image?)

是不是挺简单的呢。其实,很多时候,我们只需要开动脑筋,发挥创造力,就能达到一些想要的效果。不过真要了解其工作原理,还可以仔细阅读一下官方文档中关于 ImageField、FiledFile 和 FileStorage 相关的介绍。