Search on blog:

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

If you like it
Buy a Coffee