In this post, we are going to implement a simple image placeholder service like placehold.it. Use-cases for disposable images extend beyond placeholder services, commonly used to encode text into an image in order to protect websites from being scraped for valuable information.
A simple approach could be to generate an image, write it to disc and then serve the static file. One could improve the solution by using temporary files, however the process still remains inefficient. The solution, of course, is to use in-memory files to cut out the process of writing the file to the hard drive.
We are using Pillow, a fork of Python’s PIL (Python Imaging Library) that is actively maintained. While there is information out there on how to write an image into memory using StringIO module for Python2, using Python3 version io.StringIO results in an error:
Traceback (most recent call last): File "image.py", line 10, in <module> image.save(string_io, 'PNG') File "~/.pyenv/versions/3.4.0/lib/python3.4/site-packages/PIL/Image.py", line 1685, in save save_handler(self, fp, filename) File "~/.pyenv/versions/3.4.0/lib/python3.4/site-packages/PIL/PngImagePlugin.py", line 631, in _save fp.write(_MAGIC) TypeError: string argument expected, got 'bytes'
As hinted in the error message, the solution is to use io.BytesIO instead. See a minimal working sample below:
from io import BytesIO from PIL import Image, ImageDraw image = Image.new("RGB", (300, 50)) draw = ImageDraw.Draw(image) draw.text((0, 0), "This text is drawn on image") byte_io = BytesIO() image.save(byte_io, 'PNG')
Putting it all together, let’s implement a simple Flask service that takes one parameter, extracts dimensions, generates an image and writes the passed parameter as a text on the image. Flask is amazing, allowing us to write fully functional web service in just 30 lines of code:
import re from io import BytesIO from flask import Flask, abort, send_file from PIL import Image, ImageDraw app = Flask(__name__) @app.route('/<dimensions>') def generate_image(dimensions): #Extract digits from request variable e.g 200x300 sizes = [int(s) for s in re.findall(r'\d+', dimensions)] if len(sizes) != 2: abort(400) width = sizes height = sizes image = Image.new("RGB", (width, height)) draw = ImageDraw.Draw(image) #Position text roughly in the center draw.text((width/2 - 25, height/2 - 5), dimensions) byte_io = BytesIO() image.save(byte_io, 'PNG') byte_io.seek(0) return send_file(byte_io, mimetype='image/png') if __name__ == '__main__': app.run(debug=True)
To run this code, you may need to install few dependencies:
#Install dependencies sudo apt-get install virtualenv python3.4-dev python-pip #Create virtual environment virtualenv -p /usr/bin/python3.4 venv #Activate virtual environment source venv/bin/activate #Install libraries pip install flask Pillow #Run server python server.py #http://localhost:5000/200x300