Flask: Jak wyświetlić obraz bez zapisywania go w pliku korzystając z BytesIO oraz obrazu w postaci stringu BASE64 w url
Można użyć BytesIO do stworzenia obiektu podobnego do pliku (file-like object) ale trzymanego w pamięci RAM, który może być użyty do działań na pliku obrazka bez zapisywania na dysku.
Ten przykład używa matplotlib do stworzenia PNG w pamięci
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
A następnie można użyć img.read() lub img.getvalue() do działań z obrazkiem już zamienionym na PNG.
Można taki obrazek zapisać na dysk - należy użyć standrdowych open(), write(), close() ponieważ obrazek jest już zamieniony na dane PNG. Wymaga on także zapisu w trybie bajtowym bytes mode - który oznacza "wb"
# ... code ... #
img = generate_image()
#data = img.read()
data = img.getvalue()
fh = open('output.png', 'wb')
fh.write(data)
fh.close()
Można użyć base64.b64encode() do zmaiany danych w base64 string, który można użyć bezpośrednio w HTML na stronie, w emailu lub w każdym innym programie, który potrafi wyświetlać 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))
Moża to wysłać przez sieć używając metody POST w HTTP
import requests
# ... code ... #
img = generate_image()
data = img.getvalue() # get data from file (BytesIO)
r = resuqests.post(url, files={'name': data})
lub gniad (socket)
import socket
# ... code ... #
img = generate_image()
data = img.getvalue() # get data from file (BytesIO)
soc.send(data)
Oto przykład z Flask, który działa z obrazem w pamięci i wysyła do klienta na różne sposoby.
Przykład pokazuje jak pobrać obraz - z adresu url, - z pliku, - z formularza na stronie, - wygenerować nowy obraz a następnie edytować go z użyciem pillow i wysłać go do klienta - jako pliku obrazu (i można go użyć ze standardowym <img src="url">) - jako normlay plik (i przeglądarka będzie pytała gdzie go zapisać) - jako string Base64 (bezpośrednio w HTML lub wykorzystać w JavaScript z obiektem Canvas)
W podobny sposób można pracować też z innymi plikami: muzyka, PDF, excel, csv, itp. albo ze strumienime z web kamery.
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
Notatki:
Stackoverflow: How to create dynamic plots to display on Flask?
