如何将PIL的“Image”转换为Django的“File”?

57

我试图将一个UploadedFile对象转换为PIL Image对象进行缩略图处理,然后将我的缩略图函数返回的PIL Image对象转换回File对象。如何实现这一操作?


2
@anand 将PIL的“Image”实例转换为Django的“File”实例。Django的“File”是Python“File”类的子类。 - orokusaki
2
这里提供 Python 3 的解决方案:https://dev59.com/uYvda4cB1Zd3GeqPWjlV#30435175 - madzohan
7个回答

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,这花费了我大部分时间来正确地找到解决方案。


30
使用Django的ContentFile类可以进一步减少工作量。要使用该类,请从django.core.files.base导入ContentFile并执行以下操作:thumb_file = ContentFile(thumb_io.getvalue()) - Bialecki
@Bialecki 谢谢。当使用ContentFile(thumb_io.getvalue())时,您如何指定文件名?在那行之后应该做什么? - Akseli Palén
3
好的,我会尽力进行翻译。根据您提供的内容,似乎是在讨论Python编程中的模型图像字段保存方法。原文意思是:“@Bialecki不用了,我找到答案了。我们在保存到模型图像字段时分配名称,如下所示:mymodel.myimagefield.save(myfilename, imagecontentfile, save=True)。” - Akseli Palén
6
@madzohan 你是如何在Python 3中使用StringIO的?我不得不使用BytesIO(由于它没有_len()_函数,所以必须使用_tell()_来确定其长度)。 - John C
4
@JohnC,是的,你应该使用 BytesIO。请尝试使用我在这里提供的答案:https://dev59.com/uYvda4cB1Zd3GeqPWjlV#30435175。顺便说一句,之前的评论已经过时并被删除了。 - madzohan
显示剩余5条评论

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)

11
非常不优化,你在步骤1、2、3、4中进行了IO操作。 - Srikar Appalaraju

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(...)

3
为了满足像我这样想要将其与Django的FileSystemStorage配对使用的人,需要完成以下步骤(在此我上传一张图片,将其缩小为2维并保存两个文件): utils.py
def resize_and_save(file):
    size = 1024, 1024
    thumbnail_size = 300, 300
    uploaded_file_url = getURLforFile(file, size, MEDIA_ROOT)
    uploaded_thumbnail_url = getURLforFile(file, thumbnail_size, THUMBNAIL_ROOT)
    return [uploaded_file_url, uploaded_thumbnail_url]

def getURLforFile(file, size, location):
    img = Image.open(file)
    img.thumbnail(size, Image.ANTIALIAS)
    thumb_io = BytesIO()
    img.save(thumb_io, format='JPEG')
    thumb_file = InMemoryUploadedFile(thumb_io, None, file.name, 'image/jpeg', thumb_io.tell, None)
    fs = FileSystemStorage(location=location)
    filename = fs.save(file.name, thumb_file)
    return fs.url(filename)  

views.py 文件中。
if request.FILES:
        fl, thumbnail = resize_and_save(request.FILES['avatar'])
        #delete old profile picture before saving new one
        try:
            os.remove(BASE_DIR + user.userprofile.avatarURL)
        except Exception as e:
            pass         
        user.userprofile.avatarURL = fl
        user.userprofile.thumbnailURL = thumbnail
        user.userprofile.save()

如果您想使用FileSystemStorage在保存图像后获取图像URL,这非常有用。这正是我正在寻找的-谢谢! - knowledge_seeker

3
组织 Python 3+ 的注释和更新
from io import BytesIO
from django.core.files.base import ContentFile
import requests

# Read a file in

r = request.get(image_url)
image = r.content
scr = Image.open(BytesIO(image))

# Perform an image operation like resize:

width, height = scr.size
new_width = 320
new_height = int(new_width * height / width)
img = scr.resize((new_width, new_height))

# Get the Django file object

thumb_io = BytesIO()
img.save(thumb_io, format='JPEG')
photo_smaller = ContentFile(thumb_io.getvalue())

1

对于那些使用django-storages/-redux将图像文件存储在S3上的人,这是我采取的路径(下面的示例创建了现有图像的缩略图):

from PIL import Image
import StringIO
from django.core.files.storage import default_storage

try:
    # example 1: use a local file
    image = Image.open('my_image.jpg')
    # example 2: use a model's ImageField
    image = Image.open(my_model_instance.image_field)
    image.thumbnail((300, 200))
except IOError:
    pass  # handle exception

thumb_buffer = StringIO.StringIO()
image.save(thumb_buffer, format=image.format)
s3_thumb = default_storage.open('my_new_300x200_image.jpg', 'w')
s3_thumb.write(thumb_buffer.getvalue())
s3_thumb.close()

1

这里有一个可以实现此功能的应用程序:django-smartfields

from django.db import models

from smartfields import fields
from smartfields.dependencies import FileDependency
from smartfields.processors import ImageProcessor

class ImageModel(models.Model):
    image = fields.ImageField(dependencies=[
        FileDependency(processor=ImageProcessor(
            scale={'max_width': 150, 'max_height': 150}))
    ])

如果您想保留旧文件,请确保将keep_orphans=True传递给该字段,否则在替换时它们将被清理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接