Serving Dynamic Images with Python CGI and Graphic

Serving Dynamic Images with Python CGI and Graphics Libraries
   
Having random images is mostly a novelty, but dynamic images are more useful in
more places (
CAPTCHA
s, 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.
Step Zero: Setting Up
   
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.
   
Step One: Create a simple dynamic image
   
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()
   
   Save this code as dynimg1.py in any folder and run it.  The output image out.png should be different every time.
   
Step Two: Add CGI
   
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()
    Save this as dynimg2.py and load
http://loopback:8000/cgi-bin/dynimg2.py
,
    you should see a dynamically created gradient everytime you refresh.
   
    So now that we can see it is possible to use PIL to dynamically create
    an image let's try something a little more advanced.
Step Three: Do Something More Useful
   
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 """No input file given"""
    Save the file as dynimg3.py and load the following URLs:
   

  • http://loopback:8000/cgi-bin/dynimg3.py?filename=log1.txt
          

  • http://loopback:8000/cgi-bin/dynimg3.py?filename=log2.txt
          

  • http://loopback:8000/cgi-bin/dynimg3.py?filename=log3.txt
       

    A dynamic image will be created for each file; if you change the data in the files it is reflected in the graph.
   
Next steps
   
    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.).
   
Conclusion
   
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.
-- Steven Kryskalla
Back
to the lost-theory Python pages.
Back
to the lost-theory main page.