Return to index

Skimpy Gimpy PNG image canvas documentation

As a fun add on the Skimpy Gimpy package includes a canvas Python module which makes it very easy to generate certain kinds of PNG images. Not all PNG features are implemented, but enough are available to make the package fun and useful. For large and complex images this package will be slower than other methods because it is implemented in a keep-it-simple-stupid python-only mode, but for small simpler images it is fast enough for many uses.

As an advanced feature, the canvas includes the capability to generate Javascript data structures which can be used to implement mouse tracking over an image created using the canvas. For example the following image of the United States was generated using a canvas and integrated into this document with the javascript data structures to present information about the states when the mouse hovers above each state.

United States Mouse Tracking Example
State information should appear here
when the mouse hovers over one of the states.

This document walks through some example uses of the module and its features.

Where do you get it?

Please go to the Skimpy Gimpy sourceforge project page and click on the download link.

Hello World

The following example generates a "hello world" image file and demonstrates the use of colors and generation of text.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

# set the foreground color to a light blue
c.setColor(99,99,0xff)

# set the background color to a dark green
c.setBackgroundColor(10,50,0)

# name a font "propell" and associate it with a BDF font file
c.addFont("propell", "../fonts/mlmfonts/propell.bdf")

# set the current font to "propell" with scale 2.0 and
# pixel radius 1.3
c.setFont("propell", 2.0, 1.3)

# draw some text
c.addText(0,0, "Hello PNG World")

c.dumpToPNG("png/HelloWorld.png")
generated image png/HelloWorld.png

The first two lines of this example import the canvas module and create a Canvas object. The Canvas object c encapsulates all information drawn to the image as well as state information relating to the how to draw additional objects (such as the current color, font, and rotation).

The last line of the example dumps the contents of the canvas c to a PNG image file "HelloWorld.png". Note that the size of the image is not specified in the example. By default the canvas will make the image size a rectangular region just large enough to contain all the drawn pixels. (See the crop method below to specify the image size.)

The lines in between the first two and the last one deal with colors and drawing text.

Colors

Before any draw operation a canvas should have a foreground "paint color" defined using
c.setColor(R,G,B) # R,G,B in range 0..255
which defines the red, green, and blue intensities for the current drawing color.

A canvas may also have exactly one optional background color specified by

c.setBackgroundColor(R,G,B)
which specifies the red, green, and blue intensities for the pixels in the image which is not explicitly drawn on. If the background color is left out the background of the image will be transparent; so, for example, if the image is in a web page it will show whatever is behind it on the page, as demonstrated in the following example.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

# set the foreground color to a light blue
c.setColor(99,99,0xff)

# NO BACKGROUND COLOR (TRANSPARENT)

# name a font "propell" and associate it with a BDF font file
c.addFont("propell", "../fonts/mlmfonts/propell.bdf")

# set the current font to "propell" with scale 2.0 and
# pixel radius 1.3
c.setFont("propell", 2.0, 1.3)

# draw some text
c.addText(0,0, "Hello Transparent PNG World")

c.dumpToPNG("png/TransparentWorld.png")
generated image png/TransparentWorld.png

Circles and Coordinates

The following example draws a number of circles onto a PNG image using the method
c.addCircle(centerX, centerY, radius)
which draws a circle of radius radius centered at (centerX, centerY) using the current foreground color. The example also demonstrates canvas coordinates and painting over objects.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

# set up the font as cursive, scale 1.0
c.addFont("cursive", "../fonts/cursive.bdf")
c.setFont("cursive", 1.0)
# set the background color to pale yellow
c.setBackgroundColor(255,255,100)

# put circles and a label at (0,0)
c.setColor(255,0,0)
c.addCircle(0,0, 30) # big blue circle
c.setColor(0,255,0)
c.addCircle(0,0, 15) # medium green circle
c.setColor(0,0,255)
c.addCircle(0,0,2) # small blue circle
c.setColor(0,0,0)
c.addText(0,0, "(0,0)") # black text

# put circles and a label at (50,50)
c.setColor(255,0,0)
c.addCircle(50,50, 30) # big blue circle
c.setColor(0,255,0)
c.addCircle(50,50, 15) # medium green circle
c.setColor(0,0,255)
c.addCircle(50,50,2) # small blue circle
c.setColor(0,0,0)
c.addText(50,50, "(50,50)") # black text

# put circles and a label at (-50,-50)
c.setColor(255,0,0)
c.addCircle(-50,-50, 30) # big blue circle
c.setColor(0,255,0)
c.addCircle(-50,-50, 15) # medium green circle
c.setColor(0,0,255)
c.addCircle(-50,-50,2) # small blue circle
c.setColor(0,0,0)
c.addText(-50,-50, "(-50,-50)") # black text

# put circles and a label at (50,-50)
c.setColor(255,0,0)
c.addCircle(50,-50, 30) # big blue circle
c.setColor(0,255,0)
c.addCircle(50,-50, 15) # medium green circle
c.setColor(0,0,255)
c.addCircle(50,-50,2) # small blue circle
c.setColor(0,0,0)
c.addText(50,-50, "(50,-50)") # black text

c.dumpToPNG("png/Coords.png")
generated image png/Coords.png

Please note the following features of the canvas demonstrated above

Fonts and text

The method
c.addFont(fontName, BDFFontFilePath)
names a font for internal use and associates the name with an external BDF format font file (such as any of the fonts provided in the Skimpy distribution).

Once a font has been named it may be set as the current font for the canvas using the method

c.setFont(fontName, fontScale=1.0, radius=None)
The fontScale if given adjusts the size of the font and the font radius if given adjusts the pixel radius. BDF format fonts are defined as arrays of pixels, and the canvas represents those pixels using circles. If the radius is not given the canvas attempts to choose a radius that will fill in the glyphs without making them bleed together.

The methods

c.addText(x,y, text)
c.centerText(x,y, text)
c.rightJustifyText(x,y, text)
Draws text on the canvas using the current foreground color and the current font options, left justified, centered, or right justified respectively with respect to the (x,y) location.

The following example exercises various font options.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

# name some fonts
c.addFont("propell", "../fonts/mlmfonts/propell.bdf")
c.addFont("cursive", "../fonts/cursive.bdf")
# set colors
c.setBackgroundColor(255,255,100)
c.setColor(255,0,0)
# draw some cursive
c.setFont("cursive")
c.addText(0,200, "cursive")
c.centerText(0,180, "centered")
c.rightJustifyText(0,160, "right justified")
c.setFont("cursive", 2.5)
c.addText(0,140, "larger")
# draw some propell
c.setColor(0,255,0)
c.setFont("propell")
c.addText(0,100, "propell")
c.centerText(0,80, "centered")
c.rightJustifyText(0,60, "right justified")
c.setFont("propell", 2.5)
c.addText(0,40, "larger")

c.dumpToPNG("png/Fonts.png")
generated image png/Fonts.png

Note that the y coordinate for drawing text always refers to the "base line" for the text.

Rectangles, rotations, translations

The method
c.addRect(llx, lly, width, height)
draws a rectangle using the current forground color starting at lower left corner (llx,lly) and extend for width width and height height. The following example draws a green rectangle over a red one.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)
c.setColor(255,0,0) 
c.addRect(0,0,150,5) # horizontal red rect
c.setColor(0,255,0)
c.addRect(20,-30, 10,160) # vertical green rect

# add some helpful labels
c.addFont("cursive", "../fonts/cursive.bdf")
c.setFont("cursive")
c.setColor(0,0,0)
c.addText(150,5,"(0,0) 150x5")
c.rightJustifyText(20,130,"(20,-30) 10x160")

c.dumpToPNG("png/Rects.png")
generated image png/Rects.png

It is often very convenient to "shift" or "rotate" the coordinate system when rendering graphics. To support such coordinate transformation the canvas provides the following methods
c.translate(dx, dy)
c.rotateRadians(radians)
rotate(degrees)
The following example generates two sets of rectangles like the previous example, one unrotated, and one rotated by 135 degrees.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)
c.addFont("propell", "../fonts/mlmfonts/propell.bdf")
c.setFont("propell", 1.0)

# UNROTATED
c.setColor(255,0,0) 
c.addRect(0,0,150,5) # horizontal red rect
c.setColor(0,255,0)
c.addRect(20,-30, 10,160) # vertical green rect
c.setColor(0,0,0)
c.addText(0,0,"(0,0)")

# ROTATE THE COORDINATE SYSTEM
c.rotate(135)

c.setColor(255,0,0) 
c.addRect(0,0,150,5) # horizontal red rect
c.setColor(0,255,0)
c.addRect(20,-30, 10,160) # vertical green rect
c.setColor(0,0,0)
c.addText(0,0,"(0,0) rotated")

c.dumpToPNG("png/RotRects.png")
generated image png/RotRects.png

Similarly the following example draws two pairs of rectangles, one translated to the alternate coordinate system with origin at (-50,50).
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)
c.addFont("propell", "../fonts/mlmfonts/propell.bdf")
c.setFont("propell", 1.0)

# UNSHIFTED
c.setColor(255,0,0) 
c.addRect(0,0,150,5) # horizontal red rect
c.setColor(0,255,0)
c.addRect(20,-30, 10,160) # vertical green rect
c.setColor(0,0,0)
c.addText(0,0,"(0,0) shifted")

# SHIFT THE COORDINATE SYSTEM
c.translate(-50,50)

c.setColor(255,0,0) 
c.addRect(0,0,150,5) # horizontal red rect
c.setColor(0,255,0)
c.addRect(20,-30, 10,160) # vertical green rect
c.setColor(0,0,0)
c.addText(0,0,"(0,0)")

c.dumpToPNG("png/ShiftRects.png")
generated image png/ShiftRects.png

Furthermore multiple rotations and translations can be combined as demonstrated below.

Saving and restoring the canvas state

At any time the method
c.saveState()
method will store the state of the canvas (such as its current font and coordinate transformation) to make it easy to restore the state later on using
c.restoreState()
For example the following example saves and restores the state in order to return to the default coordinate transform after a rotation and a translation.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)
c.addFont("cursive", "../fonts/cursive.bdf")
c.addFont("propell", "../fonts/mlmfonts/propell.bdf")
c.setFont("cursive", 1.0, 1.2)
c.setColor(0,255,255) 

# SAVE THE STATE
c.saveState()

# CHANGE THE CANVAS STATE
c.translate(-50,50)
c.rotate(135)
c.setFont("propell")

# DRAW IN CHANGED STATE
c.setColor(255,0,0) 
c.addRect(0,0,150,5) # horizontal red rect
c.setColor(0,255,0)
c.addRect(20,-30, 10,160) # vertical blue rect
c.setColor(0,0,0)
c.addText(0,0,"(0,0)")

# RESTORE STATE
c.restoreState()

# DRAW IN RESTORED STATE
c.addRect(0,0,150,5) # horizontal cyan rect
c.setColor(255,0,255)
c.addRect(20,-30, 10,160) # vertical magenta rect
c.setColor(0,0,0)
c.addText(0,0,"(0,0)")

c.dumpToPNG("png/State.png")
generated image png/State.png

Filling Polygons

The canvas method
c.fillPolygon(pointSequence)
Fills a closed polygonal region bounded by the line segments between successive vertices of the point sequence with the current color.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)
c.setColor(0,0,255)
pointSequence = [ (0,0), (50,50), (75,0), (100,25), (50,-50)]
c.fillPolygon(pointSequence)

c.dumpToPNG("png/fillPoly.png")
generated image png/fillPoly.png

Drawing lines

The canvas method
c.addLine((x1,y2), (x2,y2))
draws a line segment starting at (x1,y1) and ending at (x2,y2) using the current foreground color, line cap, and line width. The methods
c.setCap(capLength)
c.setWidth(lineWidth)
The line cap specifies a distance to overshoot the end of a line segment. The following example demonstrates these methods by drawing a thin blue line on a thick red line.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)

# thick red line with no cap
c.setColor(255,0,0)
c.setCap(0)
c.setWidth(20)
c.addLine( (0,0), (100,100) )

# thin blue line with cap=10
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
c.addLine( (0,0), (100,100) )

c.dumpToPNG("png/Line.png")
generated image png/Line.png

The canvas object also has the method
c.addLines(points)
c.addLines(points, closed=True)
which draws a polyline if closed is not specified or a polygon if closed=True built from the points sequence. For example the following draws a blue triangle over a red zigzag.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)

# thick red line with no cap
c.setColor(255,0,0)
c.setCap(4)
c.setWidth(9)
zigzag = [ (0,25), (25,50), (50,25), (75,50), (100,25) ]
c.addLines( zigzag )

# thin blue line with cap=10
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
triangle = [ (0,0), (50,50), (100,0) ]
c.addLines( triangle, closed=True )

c.dumpToPNG("png/Lines.png")
generated image png/Lines.png

Filling regions

The method
c.growFill(x,y, stopColorRGB=None)
Fills a region in an image, much like the "paint bucket" tool often fills a region in image creation tools like Microsoft Paint. If the stopColorRGB is not specified the fill will start at the (x,y) position provided and grow until the first change in color in the image, as demonstrated in the following example which fills the lower part of the triangle up to the zigzag.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)

# thick red zigzag
c.setColor(255,0,0)
c.setCap(4)
c.setWidth(9)
zigzag = [ (0,25), (25,50), (50,25), (75,50), (100,25) ]
c.addLines( zigzag )

# blue triangle
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
triangle = [ (0,0), (50,50), (100,0) ]
c.addLines( triangle, closed=True )
c.setColor(0,255,255)

# fill to any color change
c.growFill(20,10)

c.dumpToPNG("png/fill1.png")
generated image png/fill1.png

If the stopColorRGB is specified the fill will proceed until a pixel with the given stopColorRGB is encountered. For example the following fill stops only at the triangle, painting over the part of the zigzag that is inside the triangle.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)

# thick red line with no cap
c.setColor(255,0,0)
c.setCap(4)
c.setWidth(9)
zigzag = [ (0,25), (25,50), (50,25), (75,50), (100,25) ]
c.addLines( zigzag )

# blue triangle
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
triangle = [ (0,0), (50,50), (100,0) ]
c.addLines( triangle, closed=True )
c.setColor(0,255,255)

# fill to blue
c.growFill(20,10, stopColorRGB=(0,0,255))

c.dumpToPNG("png/fill2.png")
generated image png/fill2.png

Specifying the view region (cropping)

By default the canvas generates a rectangular image just large enough to include all drawn pixels. If you desire an image size different from that you may specify the size using the method
c.crop(minx, miny, maxx, maxy):
which forces the image to range between x values minx..maxx and y values miny..maxy.

For example the following shows the previous image on a larger background.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundColor(255,255,100)

# thick red zigzag
c.setColor(255,0,0)
c.setCap(4)
c.setWidth(9)
zigzag = [ (0,25), (25,50), (50,25), (75,50), (100,25) ]
c.addLines( zigzag )

# blue triangle
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
triangle = [ (0,0), (50,50), (100,0) ]
c.addLines( triangle, closed=True )
c.setColor(0,255,255)

# fill to blue
c.growFill(20,10, stopColorRGB=(0,0,255))
c.crop(-20,-20, 120,70)

c.dumpToPNG("png/crop1.png")
generated image png/crop1.png

The following shows only the right side of the previous image with a transparent background.
Program
from skimpyGimpy import canvas
c = canvas.Canvas()

# thick red zigzag
c.setColor(255,0,0)
c.setCap(4)
c.setWidth(9)
zigzag = [ (0,25), (25,50), (50,25), (75,50), (100,25) ]
c.addLines( zigzag )

# blue triangle
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
triangle = [ (0,0), (50,50), (100,0) ]
c.addLines( triangle, closed=True )
c.setColor(0,255,255)

# fill to blue
c.growFill(20,10, stopColorRGB=(0,0,255))
c.crop(50,-20, 120,70)

c.dumpToPNG("png/crop2.png")
generated image png/crop2.png

Javascript mouse tracking

The canvas object encapsulates a number of methods to support the implementation of Javascript mouse event callbacks for images embedded in web pages. To help implement Javascript mouse event callbacks the canvas method provides the following: the ability to generate the image, the ability to generate a Javascript data structure associated with the image, and a collection of utility routines canvas.js. The HTML which embeds the image must: name the image, include the canvas.js utility routines, include the generated data structure, and define a callback function to receive the mouse events.

The canvas object distinguishes different regions of the image using string call back identifiers. Strings are associated with drawn pixels in much the same way as color are associated with drawn pixels using the following methods. The method

c.setCallBack(callBackString)
specifies a string to deliver to the mouse tracking call back function any pixel drawn afterwards. The method
c.resetCallBack()
Specifies that subsequently drawn pixels should not be associate with an explicit callback string. Finally, the method
c.setBackgroundCallback(callBackString)
specifies a string to deliver to the mouse tracking call back function for any pixel with no explicitly defined callback string.

Once the image drawing is complete, generate the Javascript data structure to support mouse callbacks using the canvas method

c.dumpJavascript(toFileName, imageName, functionName)
where toFileName provides the path for the Javascript file to store the data structure, imageName provides the HTML identifier used to locate the image in the HTML file, and functionName names the Javascript function to handle the mouse events.

The example shown below generates an image with associated callback strings and also prints and HTML fragment which embeds the image and the appropriate Javascript references inside an HTML file.

from skimpyGimpy import canvas
c = canvas.Canvas()

c.setBackgroundCallback("MOUSE OVER BACKGROUND")
c.setBackgroundColor(255,255,100)

# thick red zigzag (with callback)
c.setCallBack("MOUSE OVER ZIGZAG")
c.setColor(255,0,0)
c.setCap(4)
c.setWidth(9)
zigzag = [ (0,25), (25,50), (50,25), (75,50), (100,25) ]
c.addLines( zigzag )

# blue triangle (no callback for border)
c.resetCallBack()
c.setColor(0,0,255)
c.setCap(10)
c.setWidth(2)
triangle = [ (0,0), (50,50), (100,0) ]
c.addLines( triangle, closed=True )
c.setColor(0,255,255)

# fill to blue (with callback)
c.setCallBack("MOUSE INSIDE TRIANGLE")
c.growFill(20,10, stopColorRGB=(0,0,255))
c.crop(-20,-20, 120,70)

# store the png
c.dumpToPNG("png/mouse.png")

# store the Javascript
c.dumpJavascript("png/mouse.js", "mouseExample", "mouseExampleCallback")

# print out the html fragment using the generated image and javascript
# for mouse tracking.
HTML_FRAGMENT = """

<!-- the image using the id "mouseExample" -->
<img src="png/mouse.png" id="mouseExample">

<!-- a div for displaying callback information -->
<div id="mouseExampleDiv">
Mouse callback information will display here.
</div>

<!-- the Javascript callback "mouseExampleCallback" -->
<script>
function mouseExampleCallback(alertString, x, y, event, image) {
        // display event information in the mouseExampleDiv
	var span = document.getElementById("mouseExampleDiv");
	span.innerHTML = "mouseChart called with "+alertString+
            "<br>coords "+x+" "+y+
            " <br>event.type="+event.type+
            " <br>image.id="+image.id;
}
</script>

<!-- the Javascript utility functions -->
<script src="canvas.js"></script>

<!-- the generated data structures -->
<script src="png/mouse.js"></script>

"""
print HTML_FRAGMENT
Below the HTML code generated by the example is embedded in the present document. Drag the mouse over the image to see the mouse events reported below the image (on supported browsers, with Javascript enabled).
Mouse callback information will display here.
Note that the generated data structures make reference to the callback function and the image object and therefore must follow the definition of both the image and the callback function.

The callback function should have the following signature

function mouseExampleCallback(alertString, x, y, event, image)
where alertString provides the string associated for the region under the mouse, (x,y) provide the coordinates for the pixel in the image under the mouse, event is the Javascript event object, and image is the HTML/DOM image object.

Bar Chart Example

As an extended demonstration of how to use the canvas capabilities SkimpyGimpy ships with two experimental modules pngBarChart and pngPieChart which generates charts. Please look at the source of this module for more information. An example bar chart.

Entity Relationship Model Example

The skimpyGimpy.erd package demonstrates the use of the canvas to create entity relationship database design diagrams such as the following

Please see the source files for this package for more information.

Railroad Diagrams

The skimpyGimpy.railroad package demonstrates the use of the canvas to create railroad diagrams representing the syntax of computer languages such as the following

Please see the source files for this package for more information.