10

I am working on a Flask app which generates a dynamic plot and displays it through a jinja template. I would like to use the template to call a function in the Flask app which returns png data, and then embed the response in a data uri.

This gist is very close to my goal, except I would like to avoid using url_for (and thus routes). Instead, I would like to just display the image data inline using a data uri (img src="data:image/png;base64,...)

alex
  • 2,968
  • 3
  • 23
  • 25

2 Answers2

16

Instead of sending the output back as an image with a response, take the output and encode it to base64:

try:  # Python 3
    from urllib.parse import quote
except ImportError:  # Python 2
    from urllib import quote
from base64 import b64encode
from io import BytesIO

png_output = BytesIO()
canvas.print_png(png_output)
data = b64encode(png_output.getvalue()).decode('ascii')
data_url = 'data:image/png;base64,{}'.format(quote(data))

This has the canvas write to an in-memory file, and the resulting PNG data is then encoded to base64 and interpolated in a data URL.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • also, to clarify the other have of the question, the programs flow is: a small amount of data is passed to the template, which in turn calls a function in the flask app that returns the plot. so i had to use `app.jinja_env.globals.update(..)` in order to call the function from the template. this with your suggestion answers my original question. thanks! – alex Aug 05 '14 at 18:05
7

Complete solution based on your example, tested and working:

from flask import Flask, render_template
import urllib
app = Flask(__name__)

@app.route("/simple.png")
def simple():
    import datetime
    import StringIO
    import random

    from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
    from matplotlib.figure import Figure
    from matplotlib.dates import DateFormatter

    fig=Figure()
    ax=fig.add_subplot(111)
    x=[]
    y=[]
    now=datetime.datetime.now()
    delta=datetime.timedelta(days=1)
    for i in range(10):
        x.append(now)
        now+=delta
        y.append(random.randint(0, 1000))
    ax.plot_date(x, y, '-')
    ax.xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
    fig.autofmt_xdate()
    canvas=FigureCanvas(fig)
    png_output = StringIO.StringIO()
    canvas.print_png(png_output)
    png_output = png_output.getvalue().encode("base64")

    return render_template("test.html", img_data=urllib.quote(png_output.rstrip('\n')))


if __name__ == "__main__":
    app.run()

Template:

<img src="data:image/png;base64,{{img_data}}"/>
scandinavian_
  • 2,496
  • 1
  • 17
  • 19