59

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
  • 79,529
  • 41
  • 166
  • 227
  • 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

172

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

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)
Mr. Mr.
  • 2,626
  • 3
  • 17
  • 12
21

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
  • 7,396
  • 8
  • 43
  • 65
20

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
  • 914
  • 9
  • 13
  • 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
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
  • 73,764
  • 15
  • 63
  • 131
cybertoast
  • 1,093
  • 10
  • 18
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,062
  • 2
  • 12
  • 12
0
        return send_file(fileName, mimetype='image/png')
Gank
  • 5,056
  • 4
  • 43
  • 44