Flask: How to display image without saving in file using BytesIO and base64 string in url
To display image in web browser without saving image on disk you can use BytesIO to create file-like object in memory RAM which can be used like normal file to work with it without saving on disk.
This example uses matplotlib to create PNG in memory
import io
import matplotlib.pyplot as plt
import random
def generate_image():
# genereate matplotlib image
y = [random.randint(-10, 10) for _ in range(10)]
x = list(range(10))
plt.plot(x, y)
# create PNG image in memory
img = io.BytesIO() # create file-like object in memory to save image without using disk
plt.savefig(img, format='png') # save image in file-like object
img.seek(0) # move to beginning of file-like object to read it later
return img
And later you can use img.read() or img.getvalue() to get data already converted to PNG and use them to different way: save in file, send to web browser (as normal file or base64 string or POST in HTTP) send by socket, or even send to database.
As first example you can save it on disk - you have to use standard open(), write(), close() because image is already converted to PNG data. You have to write it in bytes mode which means "wb".
# ... code ... #
img = generate_image()
#data = img.read()
data = img.getvalue()
fh = open('output.png', 'wb')
fh.write(data)
fh.close()
As second example you can use base64.b64encode() to convert PNG data to base64 string which you can use directly in HTML: on web page, in email or in any program which can display HTML.
import base64
# ... code ... #
img = generate_image()
data = img.getvalue() # get data from file (BytesIO)
data = base64.b64encode(data) # convert to base64 as bytes
data = data.decode() # convert bytes to string
print('<img src="data:image/png;base64,{}">'.format(data))
You can also send this data using POST in HTTP
import requests
# ... code ... #
img = generate_image()
data = img.getvalue() # get data from file (BytesIO)
r = resuqests.post(url, files={'name': data})
Or using socket
import socket
# ... code ... #
img = generate_image()
data = img.getvalue() # get data from file (BytesIO)
soc.send(data)
Here example with Flask which generates image in memory and sends to client using different methods.
It shows how to get image: - from url, - from local file, - from form on page, - generate new image and edit it with pillow and send to client - as image file (so you can use standard <img src="url">) - as normal file (so browser will ask where to save it) - as string Base64 (directly in HTML or use in JavaScript with object Canvas)
In similar way you can work with other files: music, PDF, excel, csv, etc. or with stream from webcam.
from flask import Flask, send_file, request #render_template, import io import random import base64 import matplotlib.pyplot as plt from PIL import Image, ImageDraw import urllib.request app = Flask(__name__) @app.route('/') def index(): return """Examples<br>""" + """- <a href="/send_image">send image</a><br>""" + """- <a href="/send_file">send image to download</a><br>""" + """- <a href="/base64">img base64</a><br>""" + """- <a href="/form1">form1 (image without changes)</a><br>""" + """- <a href="/form2">form2 (image with changes)</a><br>""" def generate_image(): """Generate plot. Save in BytesIO""" # create random plot plt.cla() # to clear it because it may keep it between sessions y = [random.randint(-10, 10) for _ in range(10)] x = list(range(10)) plt.plot(x, y) # convert to file-like object obj = io.BytesIO() # file in memory to save image without using disk plt.savefig(obj, format='png') # save in file (BytesIO) obj.seek(0) # move to beginning of file (BytesIO) to read it return obj def generate_image(): """Load file to Pillow OR generate empty file in Pillow. Draw. Save in BytesIO""" # read image from file #image = Image.open('lenna.png') # OR: create empty image image = Image.new('RGB', (300, 300), (0,0,0)) # RGB, 300x300, black # draw something draw = ImageDraw.Draw(image) # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html #draw.rectangle([(10,10), (90,90)], fill=(255,0,0)) draw.rectangle([(20,20), (280,280)], outline=(255,0,0), width=3) # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.rectangle draw.text((5,5), "Hello World!", anchor="ms") # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.text # convert to file-like object obj = io.BytesIO() # file in memory to save image without using disk # image.save(obj, format='png') # save in file (BytesIO) # https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save obj.seek(0) # move to beginning of file (BytesIO) to read it # return obj def generate_image(): """Get image from url. Doesn't have .getvalue() but still has .read()""" # get from url response = urllib.request.urlopen('https://picsum.photos/300/300') return response def generate_image(): """Get image from url. Read to Pillow. Draw. Save in BytesIO""" # get from url response = urllib.request.urlopen('https://picsum.photos/300/300') # read to pillow image = Image.open(response) # # draw something draw = ImageDraw.Draw(image) # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html draw.rectangle([(20,20), (280,280)], outline=(255,0,0), width=3) # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.rectangle draw.text((15,5), "Hello World!") # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.text # convert to file-like data obj = io.BytesIO() # file in memory to save image without using disk # image.save(obj, format='png') # save in file (BytesIO) # https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save obj.seek(0) # move to beginning of file (BytesIO) to read it # return obj def generate_image(): """Get image from POST data""" request # get from url response = urllib.request.urlopen('https://picsum.photos/300/300') return response @app.route('/base64') def example1(): """Convert iamge to BASE64 url""" # get images img1 = generate_image() img2 = generate_image() # convert to bases64 data1 = img1.read() # get data from file (BytesIO) data1 = base64.b64encode(data1) # convert to base64 as bytes data1 = data1.decode() # convert bytes to string data2 = img2.read() # get data from file (BytesIO) data2 = base64.b64encode(data2) # convert to base64 as bytes data2 = data2.decode() # convert bytes to string # convert to <img> with embed image img1 = '<img src="data:image/png;base64,{}">'.format(data1) img2 = '<img src="data:image/png;base64,{}">'.format(data2) # use in HTML html = img1 + img2 return html @app.route('/send_image') def example2(): """Send to client as image which browser will display""" # get image img = generate_image() # send to client as image # it needs object which has method `read() return send_file(img, 'file.png') # name `file.png` assigned to `mimetype` @app.route('/send_file') def example3(): """Send to client as file which browser will try to download""" # get image img = generate_image() # send to client as download file (with mimetype='application/octet-stream') # it needs object which has method `read() return send_file(img, as_attachment=True, attachment_filename='file.png') # mimetype='application/octet-stream') # without name as second argument because it was mimetype @app.route('/form1', methods=['GET', 'POST']) def example4(): """Get from POST (form)""" img = '- empty -' if request.method == 'POST': if "image" in request.files: print(request.files) # get image img = request.files["image"] # it has methodn .read() so it doesn't need to # convert to bases64 data = img.read() # get data from file (BytesIO) data = base64.b64encode(data) # convert to base64 as bytes data = data.decode() # convert bytes to string # convert to <img> with embed image img = '<img src="data:image/png;base64,{}">'.format(data) return '<form method="POST" enctype="multipart/form-data"><input type="file" name="image"><button type="submit">Send</button></form><br>' + img @app.route('/form2', methods=['GET', 'POST']) def example5(): """Get from POST (form)""" img = '- empty -' if request.method == 'POST': if "image" in request.files: print(request.files) # get image img = request.files["image"] # it has methodn .read() so it doesn't need to # read to pillow image = Image.open(img) # # draw something draw = ImageDraw.Draw(image) # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html draw.rectangle([(20,20), (280,280)], outline=(255,0,0), width=3) # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.rectangle draw.text((15,5), "Hello World!") # https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.text # convert to file-like data obj = io.BytesIO() # file in memory to save image without using disk # image.save(obj, format='png') # save in file (BytesIO) # https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save obj.seek(0) # convert to bases64 data = obj.read() # get data from file (BytesIO) data = base64.b64encode(data) # convert to base64 as bytes data = data.decode() # convert bytes to string # convert to <img> with embed image img = '<img src="data:image/png;base64,{}">'.format(data) return '<form method="POST" enctype="multipart/form-data"><input type="file" name="image"><button type="submit">Send</button></form><br>' + img if __name__ == '__main__': app.run(debug=True) # matplotlib need to work in main thread so flask can't use debug
Notes:
Stackoverflow: How to create dynamic plots to display on Flask?
