Fundamentals of Computer Science I: Media Computing (CS151.01 2008S)
Summary: We consider how one might write Scheme code to give instructions on the core tools associated with GIMP.
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.
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 selection-replace 10 10 180 180) (context-set-fgcolor! "yellow") (image-fill! smiley) (context-set-fgcolor! "black") (context-set-brush! "Circle (09)") (image-stroke! smiley) ; Draw the eyes (image-select-ellipse! smiley selection-replace 50 60 30 20) (image-select-ellipse! smiley selection-add 120 60 30 20) (context-set-fgcolor! "white") (image-fill! smiley) (context-set-fgcolor! "black") (context-set-brush! "Circle Fuzzy (07)") (image-stroke! smiley) (image-select-ellipse! smiley selection-replace 60 60 10 20) (image-select-ellipse! smiley selection-add 130 60 10 20) (context-set-fgcolor! "light steel blue") (image-fill! smiley) ; Smile (image-select-ellipse! smiley selection-replace 40 60 120 100) (image-select-ellipse! smiley selection-subtract 40 45 120 100) (context-set-fgcolor! "white") (image-fill! smiley) (context-set-fgcolor! "red") (context-set-brush! "Calligraphic Brush#3") (image-stroke! 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 sections, colors, and brushes, and then stroking or filling, as appropriate. We discuss each of these operations in the following sections.
DrFu provides a few core operations to help you build and load images.
( creates and returns an image of
a specified width and height. It returns a number that DrFu uses 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))
If you want to work with an existing image that is stored in the file,
you can load the image with
(. For example, you can load
a picture of one of the CS faculty with the
(define prof (image-load "/home/rebelsky/glimmer/samples/rebelsky-stalkernet.jpg"))
When you first create or load an image in DrFu, it is not
visible on the screen. if you want to see the image (and you
will, eventually), use
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
What color is an image when it is first created? The
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.
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 DrFu, 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-brushesgives 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
(context-set-brush! "sets the working brush.
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
(. 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
to get a list of all colors. You can also use
(, to get a
list of colors that contain a particular string, such as
( to get
a list of all colors whose name includes the word “red”.
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-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.
top represent the left
edge and top edge of the rectangle that bounds the ellipse.
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
selection-replace, in which case the old selection is replaced by the new selection;
selection-add, in which case the old selection is extended by the new selection, even if the two selections are non-contiguous;
selection-subtract, in which case any pixels in the the old selection that are also in the new selection are de-selected; and
selection-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-
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,
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.
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 image using the current foreground color with
(image-fill! image). You can trace the exterior
of the image with
(image-stroke! image). You've
seen examples of both commands in the example 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
While a lot of drawing can be done with selection, stroking, and filling, it is also useful to have other tools available. DrFu provides two other simple GIMP commands that focus on simple kinds of drawings.
(image-blot! draws a single
spot at the specified position using the current brush and foreground color.
(image-draw-line! - draw a line from (
r2) using the current brush and foreground color.
None 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.
Copyright (c) 2007-8 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (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
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.