如何将PIL生成的图像发送到浏览器?

69
我正在使用Flask开发我的应用程序。我想发送一张由PIL动态生成的图像给客户端,而无需将其保存到磁盘上。
你有什么好的建议吗?

1
Flask似乎没有对流二进制数据提供坚实的支持,除非你可以使用Python生成器来生成它。你可能需要在内存中缓冲图像并发送它。 - millimoose
6个回答

184
这是一个没有任何暂存文件之类的版本(请参见此处):
from io import StringIO

def serve_pil_image(pil_img):
    img_io = StringIO()
    pil_img.save(img_io, 'JPEG', quality=70)
    img_io.seek(0)
    return send_file(img_io, mimetype='image/jpeg')

在你的代码中使用,只需执行以下操作

@app.route('some/route/')
def serve_img():
    img = Image.new('RGB', ...)
    return serve_pil_image(img)

1
你的意思是必须一次性将整个图像保存在内存中,对于大型图像或其他类型的下载可能会有问题。 - Eli
5
我该如何将它插入到我返回的模板中? - scottydelta
14
Python3需要使用ByteIO:http://fadeit.dk/blog/post/python3-flask-pil-in-memory-image - ozooner
2
现在我该如何在img标签的src属性中引用该文件? - Riley Fitzpatrick
2
对于那些想知道如何在img标签的src属性中引用文件的人,只需使用一个端点来提供图像@app.route('/docs/<filename>'),然后在HTML中使用src="/docs/some_img.jpg"即可。 - Green
显示剩余7条评论

38

Mr. Mr. 实际上做得非常出色。我不得不使用 BytesIO() 而不是 StringIO()。

def serve_pil_image(pil_img):
    img_io = BytesIO()
    pil_img.save(img_io, 'JPEG', quality=70)
    img_io.seek(0)
    return send_file(img_io, mimetype='image/jpeg')

我遇到了一个问题,即BytesIO不是字符串,而send_file需要一个路径字符串。我该如何从BytesIO对象中获取路径? - Riley Fitzpatrick
我认为字节以8位对象的形式输出,可以解释为字符串。无论如何 - 我不确定为什么它对你不起作用,也许尝试StringIO()? - Dan Erez
1
它实际上一直在正常工作,只是我在HTML中的引用不正确。现在使用url_for(),它可以正常工作了。 - Riley Fitzpatrick
我已经按照那样设置了,但当我在 Docker 中运行我的应用程序时,会出现“RuntimeError: Attempted implicit sequence conversion but the response object is in direct passthrough mode.”的错误。有任何想法在哪里设置它吗? - Saintan

20

首先,您可以将图像保存到临时文件中并删除本地文件(如果有):

from tempfile import NamedTemporaryFile
from shutil import copyfileobj
from os import remove

tempFileObj = NamedTemporaryFile(mode='w+b',suffix='jpg')
pilImage = open('/tmp/myfile.jpg','rb')
copyfileobj(pilImage,tempFileObj)
pilImage.close()
remove('/tmp/myfile.jpg')
tempFileObj.seek(0,0)

其次,按照这个stackoverflow问题的建议,将临时文件设置为响应:

from flask import send_file

@app.route('/path')
def view_method():
    response = send_file(tempFileObj, as_attachment=True, attachment_filename='myfile.jpg')
    return response

不再工作:TypeError: 'file' object is not callable - letmaik
这里是动态创建的文件? - user1953366
@user1953366 - 在这个例子中,被创建的文件是myfile.jpg,但我强烈建议查看下面mr-mr的答案,因为它更加高效,不需要创建临时文件。 - Adam Morris
2
临时文件也会写入磁盘?这怎么成为了被接受的响应? - Dan Erez

7
原来 Flask 提供了一种解决方案(提醒自己!):
from flask import abort, send_file
try:
    return send_file(image_file)
except:
    abort(404)

6

我曾经也陷入了同样的困境。最终,我使用了一个WSGI应用程序来解决这个问题,它可以作为“make_response”的参数。

from Flask import make_response

@app.route('/some/url/to/photo')
def local_photo():
    print('executing local_photo...')
    with open('test.jpg', 'rb') as image_file:
        def wsgi_app(environ, start_response):
            start_response('200 OK', [('Content-type', 'image/jpeg')])
            return image_file.read()
        return make_response(wsgi_app)

请使用适当的PIL操作替换“opening image”操作。

1
        return send_file(fileName, mimetype='image/png')

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