Having random images is mostly a novelty, but dynamic images are more useful in more places (CAPTCHAs, graphically displaying a log file, Sparklines, automatically resize pictures for a photo gallery, etc.)
Using the ideas from Serving Random Images, and help from Python's third party graphics libraries, serving up and creating dynamic images is a pretty straightforward process.
Before we jump in there are a few things to set up. First, we will need the third party graphics libraries. There are a few options for Python, but this tutorial will focus on using the Python Imaging Library (PIL). Other options include Agg, ImageMagick, Gnuplot, etc.; the process will be mostly the same.
Download PIL for your version of Python from the PIL site. If you are unfamiliar with PIL a manual is available on the site.
As with the previous article, we will be using Python's built in CGI server for testing. Type python -c "import CGIHTTPServer;CGIHTTPServer.test()" from the command line to start the test server in your current directory.
To start we will try to get acquainted with some basic PIL functionality such as opening an image, creating
a drawing object, drawing to the image, and writing back to a file. If you are already acquainted with using PIL you
can skip this first step.
First, we need to open up an image file. You can either use an already existing file and open it using the Image.open
function, or use Image.new to create a new image. Once you have an Image object created you can use ImageDraw.draw
to open up a draw object, which provides simple 2d graphics such as lines, ellipses, text, etc (PIL ImageDraw Documentation).
We will use some random numbers and draw.line to produce a different looking image each time.
import Image,ImageDraw from random import randint as rint def randgradient(): img = Image.new("RGB", (300,300), "#FFFFFF") draw = ImageDraw.Draw(img) r,g,b = rint(0,255), rint(0,255), rint(0,255) dr = (rint(0,255) - r)/300. dg = (rint(0,255) - g)/300. db = (rint(0,255) - b)/300. for i in range(300): r,g,b = r+dr, g+dg, b+db draw.line((i,0,i,300), fill=(int(r),int(g),int(b))) img.save("out.png", "PNG") if __name__ == "__main__": randgradient()
Now that we can make some kind of dynamic image we can put it inside of a CGI script to display a new image every time the page is refreshed. Because we would be putting this script on a web server it is bad form to write the image to a file every time (for security, disk space, and speed to name a few). Instead, we will manipulate all of the data in memory using the cStringIO module, which provides file-like objects that reside completely in memory.
import Image,ImageDraw import cStringIO from random import randint as rint def randgradient(): img = Image.new("RGB", (300,300), "#FFFFFF") draw = ImageDraw.Draw(img) r,g,b = rint(0,255), rint(0,255), rint(0,255) dr = (rint(0,255) - r)/300. dg = (rint(0,255) - g)/300. db = (rint(0,255) - b)/300. for i in range(300): r,g,b = r+dr, g+dg, b+db draw.line((i,0,i,300), fill=(int(r),int(g),int(b))) f = cStringIO.StringIO() img.save(f, "PNG") print "Content-type: image/png\n" f.seek(0) print f.read() if __name__ == "__main__": randgradient()
Now that the basics are down let's try something more useful. Let's say we have some third party program
that produces a set of logs that record bandwidth at some regular interval. We could stick a Python script in the directory,
start up a web server, and view dynamically generated graphs remotely over the internet.
Doing this is not much more complicated than the gradients created in the last section. Values from the log
will be read from the file, then plotted using ImageDraw.line across the axis. The one new thing we will
add is the ability for the script to take arguments. Arguments are read using the cgi module's FieldStorage
object. When FieldStorage is initialized all of the information sent to the script (user info from a form, text inside the query string, etc.)
is loaded for the script to use. The FieldStorage object works much like a dictionary, taking keys and returning values.
Our script will be used by loading the script with one argument, the log's filename (which should be in the root of the webserver).
Once we have the filename we can open the file, read the values, plot them, then send the graph to the browser.
Here are three sample log files to use with this script: log1.txt, log2.txt, log3.txt
import Image,ImageDraw import cStringIO import cgi X,Y = 500, 275 #image width and height def graph(filename): img = Image.new("RGB", (X,Y), "#FFFFFF") draw = ImageDraw.Draw(img) #draw some axes and markers for i in range(X/10): draw.line((i*10+30, Y-15, i*10+30, 20), fill="#DDD") if i % 5 == 0: draw.text((i*10+15, Y-15), `i*10`, fill="#000") for j in range(1,Y/10-2): draw.text((0,Y-15-j*10), `j*10`, fill="#000") draw.line((20,Y-19,X,Y-19), fill="#000") draw.line((19,20,19,Y-18), fill="#000") #read in file and graph it log = file(r"c:\python\random_img\%s" % filename) for (i, value) in enumerate(log): value = int(value.strip()) draw.line((i+20,Y-20,i+20,Y-20-value), fill="#55d") #write to file object f = cStringIO.StringIO() img.save(f, "PNG") f.seek(0) #output to browser print "Content-type: image/png\n" print f.read() if __name__ == "__main__": form = cgi.FieldStorage() if "filename" in form: graph(form["filename"].value) else: print "Content-type: text/html\n" print """<html><body>No input file given</body></html>"""
From here, one of the next steps you can take is using your script with an accompanying form (the form's action attribute must be set to the script name). You could also provide additional arguments to the script (size, colors, etc.).
Python's features make it a great web scripting language. You can write useful scripts very easily, and the language's diverse range of web frameworks make writing web apps even easier. Also, Python's great third party graphics libraries give you the ability to generate useful visual output with very little work.