Functional Problem Solving (CSC 151 2014F) : Readings
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [FAQ] [Teaching & Learning] [Grading] [Rubric] - [Calendar]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Examples] [Handouts] [Labs] [Outlines] [Readings]
Reference: [Setup] [VM] [Errors] - [Functions A-Z] [Functions By Topic] - [Racket] [Scheme Report (R5RS)] [R6RS] [TSPL4]
Related Courses: [Davis (2013F)] [Rebelsky (2014S)] [Weinman (2014F)]
Misc: [Submit Questions] - [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)]
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 groups of shapes with different positions, sizes, and colors; or we can think about images as resulting from a series of operations using the GIMP tools.
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))
; 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! "2. Hardness 100" 9)
(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! "2. Hardness 050" 7)
(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! "2. Block 01" 8)
(image-stroke-selection! smiley)
; Get ready to show
(image-select-nothing! smiley)
(image-show 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.
MediaScript 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 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.
image-new width
height)
>(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 (. For example, you can load
a picture of one of the CS faculty with the
following definition.
image-load
file-name)
>(define prof (image-load "/home/rebelsky/glimmer/samples/rebelsky-pic.jpg"))
Both image-load and image-new
return an image-id, which is an integer
representing the image.
>my-first-image1>prof2
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, returns the
id of the image it shows.
image-show
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 ( and
image-width
image)(.
image-height
image)
>(image-width my-first-image)9>(image-height my-first-image)5
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
( and
context-set-fgcolor!
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-set-bgcolor!
color)(
to get a list of all colors. You can also use
context-list-colors)(, to get a
list of colors that contain a particular string, such as
context-list-colors
pattern)( to get
a list of all colors whose name includes the word “red”.
context-list-colors "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-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.
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!).
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! draws
a single spot at the specified position using the current brush and
foreground color.
image
col row)
(image-draw-line! draws a line from (image c1 r1 c2
r2)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.
a. Copy and paste the instructions (repeated from above) for drawing a simple face into your definitions pane, but do not run them. Read through the instructions to see what image you predict they will draw.
(require gigls/unsafe)
(define smiley (image-new 200 200))
; 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! "2. Hardness 100" 9)
(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! "2. Hardness 050" 7)
(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! "2. Block 01" 8)
(image-stroke-selection! smiley)
; Get ready to show
(image-select-nothing! smiley)
(image-show smiley)
(context-update-displays!)
b. Click and observe what happens. If all has gone well, you should see a new image with a smiling face.
c. In the interactions pane, write instructions for drawing a nose onto the image. Your instructions might look something like the following:
>(image-select-rectangle! smiley REPLACE 90 90 20 20)>(image-select-ellipse! smiley INTERSECT 80 85 30 30)>(context-set-fgcolor! "green")>(image-fill-selection! smiley)
If the instructions don't seem to change the image, try clicking
on the image window or typing (context-update-displays!).
d. It is possible to clear an image by selecting everything and then clearing the selection. Do so now.
>(image-select-all! smiley)>(image-clear-selection! smiley)>(context-update-displays!)
e. Sometimes it's more fun to see an image drawn little-by-little. Instead of clicking the button, you can copy and paste sections of the code from the definitions pane to the interactions pane.
Try this for the first few instructions in the image. We'd recommend
that you copy sections that end with image-fill-selection!
or image-stroke-selection!. You may have to call
context-update-displays! to see the change.