CSC151.01 2009F Functional Problem Solving : Readings

Programming with GIMP Tools


Summary: We consider how one might write Scheme code to give instructions on the core tools associated with GIMP.

Introduction: Models of Drawing

As we've noted, in order to write algorithms, one must also have a representation for the manipulated data and a collection of primitive procedures with which to manipulate those data.

But both the representation and the collection of related procedures are motivated by a deeper issue, the model we use to think about the data. For example, we have seen a few simple ways to think about images: We can think of images as grids of pixels, we can think of images as constructed by robotic turtles with simple commands, or we can think about images as one might construct them in GIMP.

When thinking about describing algorithms related to GIMP, we might look to move from informal English-language descriptions to descriptions that look more like Scheme. That is, we might represent images by giving sequences of commands that a human might carry out, but that a computer might also carry out. It is this model that we consider in the reading.

In particular, in GIMP, as in most painting programs, artists and designers create new images by selecting appropriate tools and using those tools to draw. Once a base image is created, they may also modify that image using a variety of filters. In the coming weeks, we'll look at how one might create such filters. For now, we'll look more at the initial creation of images.

An Example

If we were using Scheme to communicate with other humans (a scary concept, isn't it?), we might write something like the following to tell someone how to draw a simple variant of our ubiquitous smiley face. (Note: You have not learned most of these commands. That's okay. You should be able to guess what they do from context.)

(define smiley (image-new 200 200))
(image-show smiley)

; Draw the primary circle
(image-select-ellipse! smiley REPLACE 
                       10 10 180 180)
(context-set-fgcolor! "yellow")
(image-fill-selection! smiley)

(context-set-fgcolor! "black")
(context-set-brush! "Circle (09)")
(image-stroke-selection! smiley)

; Draw the eyes
(image-select-ellipse! smiley REPLACE 
                       50 60 30 20)
(image-select-ellipse! smiley ADD
                       120 60 30 20)
(context-set-fgcolor! "white")
(image-fill-selection! smiley)

(context-set-fgcolor! "black")
(context-set-brush! "Circle Fuzzy (07)")
(image-stroke-selection! smiley)

(image-select-ellipse! smiley REPLACE
                       60 60 10 20)
(image-select-ellipse! smiley ADD
                       130 60 10 20)
(context-set-fgcolor! "lightsteelblue")
(image-fill-selection! smiley)

; Smile
(image-select-ellipse! smiley REPLACE 
                       40 60 120 100)
(image-select-ellipse! smiley SUBTRACT
                       40 45 120 100)
(context-set-fgcolor! "white")
(image-fill-selection! smiley)

(context-set-fgcolor! "red")
(context-set-brush! "Calligraphic Brush#3")
(image-stroke-selection! smiley)

; Get ready to show
(image-select-nothing! smiley)
(context-update-displays!)

What's going on here? As you've seen, the model for much of the drawing in GIMP is that you select portions of the image and then either fill them or stroke (trace the edges of) them. We draw the face by selecting appropriate areas, colors, and brushes, and then stroking or filling the selected region. We discuss each of these operations in the following sections.

Creating and Loading Images

MediaScript provides a few core operations to help you build and load images.

(image-new width height) creates and returns an image of a specified width and height. It returns a number that the GIMP and MediaScript use to identify the image. You will find it easiest to assign a name to the image, as in the following.

> (define my-first-image (image-new 9 5))

What color is an image when it is first created? The image-new procedure makes all the pixels in the image the same color as the background color. You'll learn about setting the background color a little bit later in this reading.

If you want to work with an existing image that is stored in the file, you can load the image with (image-load file-name). For example, you can load a picture of one of the CS faculty with the following definition.

> (define prof (image-load "/home/rebelsky/glimmer/samples/rebelsky-stalkernet.jpg"))

If you've created the library.scm suggested in the first Scheme laboratory, you could also type the following instructions.

> (load "/home/username/Desktop/library.scm")
> (define prof (image-load prof-photo-filename))

Both image-load and image-new return an image-id, which is an integer representing the image.

> my-first-image
1
> prof
2

When you first create or load an image in MediaScript, it is not visible on the screen. If you want to see the image (and you will, eventually), use (image-show image-id).

> (image-show my-first-image)
1
> (image-show prof)
2

As you'll note, image-show returns the id of the image it shows.

If you're working with an image that someone else created, of if you have forgotten the size of your image, you can find out the width and height of that image with (image-width image) and (image-height image).

> (image-width my-first-image)
9
> (image-height my-first-image)
5

GIMP Context

As the example above suggests, before putting digital ink in the screen, it is important to set contextual information that guides how GIMP interprets drawing actions. The most important contexts for drawing are the foreground color, the background color, and the brush.

When you draw in a painting program, the program often assumes that you want to draw using a selected brush. As is the case with physical brushes, different digital brushes give very different effects. For example, a square brush across the image will give you a very different kind of line than will a fuzzy circle.

In MediaScript, there are a few primary procedures for working with brushes.

  • (context-list-brushes) gives a list of the names of all the available brushes.
  • (context-list-brushes name) gives a list of the names of all the available brushes that include name. For example, (context-list-brushes "circle") lists all the brushes whose name includes the word "circle".
  • (context-set-brush! brush-name) sets the working brush. For example, (context-set-brush! "Pepper").

We recommend that you explore the strengths and weaknesses of each brush for a variety of situations.

You can set the foreground and background colors with (context-set-fgcolor! color) and (context-set-bgcolor! color). At this point in your programming career, you can assume that colors are given by strings, and you should stick to the primary colors. However, if you want to explore a bit further, you can use (context-list-colors) to get a list of all colors. You can also use (context-list-colors pattern), to get a list of colors that contain a particular string, such as (context-list-colors "red") to get a list of all colors whose name includes the word “red”.

Selecting Regions

As the example suggests, a lot of simple drawing can be done by selecting interesting regions and then stroking or filling those regions. There are two basic operations for selecting regions, image-select-ellipse! and image-select-rectangle!. The two procedures take exactly the same list of parameters:

  • image, the image in which to select a region;
  • operation, the way in which we want to update the selection;
  • left, the left edge of the selected region;
  • top, the top edge of the selected region;
  • width, the width of the selected region; and
  • height, the height of the selected region.

For ellipses, left and top represent the left edge and top edge of the rectangle that bounds the ellipse.

Except for operation, all of the remaining parameters should be obvious. The operation stems from an interesting, but useful, design decision in GIMP: Often, we build more complex images by selecting more complex regions. However, if our only building blocks for describing selections are rectangles and ovals, we need appropriate ways to combine those building blocks. That's where the operation comes into play. The operation can be

  • REPLACE, in which case the old selection is replaced by the new selection;
  • ADD, in which case the old selection is extended by the new selection, even if the two selections are non-contiguous;
  • SUBTRACT (or SUB), in which case any pixels in the the old selection that are also in the new selection are de-selected; and
  • INTERSECT, in which case only pixels that are in both the old and new selections remain selected.

There are also a few procedures that permit one to select more broadly:

  • (image-select-nothing! image) de-selects everything.
  • (image-select-all! image) selects everything in the image.

Detour: The GIMP Coordinate System

Note that GIMP has an interesting perspective on how to number positions in the image. In particular, while column numbers go from left to right (as you might expect), row numbers go from top to bottom, rather than bottom to top. We also start both column numbers and row numbers with 0. When we refer to one position on the grid, we do so in terms of its column number and its row number. Hence, in an image that is 9 pixels wide and 5 pixels high,

  • The top-left pixel is indexed (0,0)
  • The top-right pixel is indexed (8,0)
  • The bottom-left pixel is index (0,4)
  • The bottom-right pixel is indexed (8,4)
  • The center pixel is indexed (4,2)

Why is (0,0) the top-left position, rather than the bottom-left position, as in the Cartesian plane? One possibility is that images were originally described for television tubes, and televisions scan from top to bottom. Another is that whoever was responsible for designing the notation was familiar with linear algebra, and was using the typical notation for elements of a matrix.

Why do we start counting rows and columns with zero rather than with one? It turns out that some computations are easier with such a numbering system. Computer scientists almost always start counting with 0, rather than with 1.

Working with Selections

Once you've made an appropriate selection, what can you do with that selection? Only a few things. You can fill the interior of the selection using the current foreground color with (image-fill-selection! image). You can trace the exterior of the selection with (image-stroke-selection! image). You've seen examples of both commands in the code above. The third possibility is that you can “clear” the selection with image-clear-selection!. In simple images, clearing a selection results in the selection getting filled by the background color. In layered images, clearing the selection may reveal images from lower layers.

Because designers may not want all of the changes to an image to appear piecemeal, GIMP does not automatically update the displayed version of the image after one of these commands. The updates only become visible after either (a) you click on the image or (b) you call (context-update-displays!).

A Few Other Useful Tools

While a lot of drawing can be done with selection, stroking, and filling, it is also useful to have other tools available. MediaScript provides two other simple GIMP commands that focus on simple kinds of drawings.

(image-blot! image col row) draws a single spot at the specified position using the current brush and foreground color.

(image-draw-line! image c1 r1 c2 r2) draws a line from (c1,r1) to (c2,r2) using the current brush and foreground color.

Neither of these procedures can easily be simulated with the selection approach to drawing, so they provide a useful addition to the algorithmic designer's toolbox.

Creative Commons License

Samuel A. Rebelsky, rebelsky@grinnell.edu

Copyright (c) 2007-9 Janet Davis, Matthew Kluber, Samuel A. Rebelsky, and Jerod Weinman. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)

This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.

This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.