如何将PIL `Image` 转换为Django `File`?

57 投票
7 回答
36920 浏览
提问于 2025-04-16 04:12

我想把一个UploadedFile(上传的文件)转换成一个PIL的Image(图像)对象,以便生成缩略图,然后再把我的缩略图函数返回的PILImage对象转换回File(文件)对象。我该怎么做呢?

7 个回答

11

这是一个适用于python 3.5django 1.10的实际工作示例。

在views.py文件中:

from io import BytesIO
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import InMemoryUploadedFile

def pill(image_io):
    im = Image.open(image_io)
    ltrb_border = (0, 0, 0, 10)
    im_with_border = ImageOps.expand(im, border=ltrb_border, fill='white')

    buffer = BytesIO()
    im_with_border.save(fp=buffer, format='JPEG')
    buff_val = buffer.getvalue()
    return ContentFile(buff_val)

def save_img(request)
    if request.POST:
       new_record = AddNewRecordForm(request.POST, request.FILES)
       pillow_image = pill(request.FILES['image'])
       image_file = InMemoryUploadedFile(pillow_image, None, 'foo.jpg', 'image/jpeg', pillow_image.tell, None)
       request.FILES['image'] = image_file  # really need rewrite img in POST for success form validation
       new_record.image = request.FILES['image']
       new_record.save()
       return redirect(...)
14

我在处理这个问题时分了几个步骤,PHP里的imagejpeg()也需要类似的过程。虽然有办法把东西保存在内存里,但这种方法可以让你同时得到原始图片和缩略图的文件引用(这样做通常是个好主意,以防你需要回去修改缩略图的大小)。

  1. 先保存文件
  2. 然后用PIL从文件系统打开它
  3. 接着用PIL保存到一个临时目录
  4. 最后以Django文件的形式打开它,这样才能正常工作。

模型:

class YourModel(Model):
    img = models.ImageField(upload_to='photos')
    thumb = models.ImageField(upload_to='thumbs')

用法:

#in upload code
uploaded = request.FILES['photo']
from django.core.files.base import ContentFile
file_content = ContentFile(uploaded.read())
new_file = YourModel() 
#1 - get it into the DB and file system so we know the real path
new_file.img.save(str(new_file.id) + '.jpg', file_content)
new_file.save()

from PIL import Image
import os.path

#2, open it from the location django stuck it
thumb = Image.open(new_file.img.path)
thumb.thumbnail(100, 100)

#make tmp filename based on id of the model
filename = str(new_file.id)

#3. save the thumbnail to a temp dir

temp_image = open(os.path.join('/tmp',filename), 'w')
thumb.save(temp_image, 'JPEG')

#4. read the temp file back into a File
from django.core.files import File
thumb_data = open(os.path.join('/tmp',filename), 'r')
thumb_file = File(thumb_data)

new_file.thumb.save(str(new_file.id) + '.jpg', thumb_file)
110

要实现这个功能,而不需要先把文件写回到文件系统,然后再通过打开文件的方式把它读回内存,我们可以使用 StringIO 和 Django 的 InMemoryUploadedFile。下面是一个简单的示例,展示你可以怎么做。这个示例假设你已经有一个名为 'thumb' 的缩略图:

import StringIO

from django.core.files.uploadedfile import InMemoryUploadedFile

# Create a file-like object to write thumb data (thumb data previously created
# using PIL, and stored in variable 'thumb')
thumb_io = StringIO.StringIO()
thumb.save(thumb_io, format='JPEG')

# Create a new Django file-like object to be used in models as ImageField using
# InMemoryUploadedFile.  If you look at the source in Django, a
# SimpleUploadedFile is essentially instantiated similarly to what is shown here
thumb_file = InMemoryUploadedFile(thumb_io, None, 'foo.jpg', 'image/jpeg',
                                  thumb_io.len, None)

# Once you have a Django file-like object, you may assign it to your ImageField
# and save.
...

如果你需要更多的解释,请告诉我。我现在在我的项目中已经成功实现了这个功能,使用 django-storages 上传到 S3。这让我花了大半天的时间才找到合适的解决方案。

撰写回答