69

I'm using flask for my application. I'd like to send an image (dynamically generated by PIL) to client without saving on disk.

Any idea how to do this ?

Soviut
  • 88,194
  • 49
  • 192
  • 260
  • 1
    Flask doesn't seem to have solid support for streaming binary data that you can't generate with a Python generator. You'll probably have to buffer the image in memory and sent that. – millimoose Oct 25 '11 at 00:46

6 Answers6

184

Here's a version without any temp files and the like (see here):

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')

To use in your code simply do

@app.route('some/route/')
def serve_img():
    img = Image.new('RGB', ...)
    return serve_pil_image(img)
snoob dogg
  • 2,491
  • 3
  • 31
  • 54
Mr. Mr.
  • 2,786
  • 3
  • 19
  • 12
36

Mr. Mr. did an excellent job indeed. I had to use BytesIO() instead of 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')
Dan Erez
  • 1,364
  • 15
  • 16
  • I'm having an issue with the fact that BytesIO is not a string and `send_file` requires a string for the path. How can I get the path from the BytesIO object? – Riley Fitzpatrick Oct 02 '19 at 15:23
  • I think bytes come out as a 8 bit object that can be interpreted as a string. Anyways - I'm not really sure why it's not working for you, try StringIO() maybe? – Dan Erez Oct 02 '19 at 16:23
  • 1
    It actually was working fine, I just wasn't referencing it correctly in my html. Now using `url_for()` and it is working fine. – Riley Fitzpatrick Oct 02 '19 at 19:42
  • I have it setup like that, but when I run my application inside docker i get `RuntimeError: Attempted implicit sequence conversion but the response object is in direct passthrough mode.` Any ideas where to set it? – Saintan Sep 22 '20 at 09:08
20

First, you can save the image to a tempfile and remove the local file (if you have one):

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)

Second, set the temp file to the response (as per this stackoverflow question):

from flask import send_file

@app.route('/path')
def view_method():
    response = send_file(tempFileObj, as_attachment=True, attachment_filename='myfile.jpg')
    return response
Community
  • 1
  • 1
Adam Morris
  • 8,265
  • 12
  • 45
  • 68
7

It turns out that flask provides a solution (rtm to myself!):

from flask import abort, send_file
try:
    return send_file(image_file)
except:
    abort(404)
Funk Forty Niner
  • 74,450
  • 15
  • 68
  • 141
cybertoast
  • 1,343
  • 13
  • 19
6

I was also struggling in the same situation. Finally, I have found its solution using a WSGI application, which is an acceptable object for "make_response" as its argument.

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)

Please replace "opening image" operations with appropriate PIL operations.

cocoatomo
  • 5,432
  • 2
  • 14
  • 12
1
        return send_file(fileName, mimetype='image/png')
Gank
  • 4,507
  • 4
  • 49
  • 45