CS101 Lecture Notes
CS101 Lecture Notes
Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
C H A P T E R 3
Object-Oriented
Programming in Python Getting Started with Graphics
David Letscher
Saint Louis University
In Section 1.4.4 we designed a hierarchy for a hypothetical set of drawable objects.
Those classes are not actually built into Python, but we have implemented precisely such a
package for use with this book. Our software is available as a module named cs1graphics ,
which can be downloaded and installed on your computer system. This chapter provides
an introductory tour of the package. Since these graphics classes are not a standard part of
Python, they must first be loaded with the command
The package can be used in one of two ways. To begin, we suggest experimenting in an
interactive Python session. In this way, you will see the immediate effect of each command
as you type it. The problem with working interactively is that you start from scratch each
time. As you progress, you will want to save the series of commands in a separate file
and then use that source code as a script for controlling the interpreter (as introduced in
Section 2.9).
Our tour begins with the Canvas class, which provides the basic windows for dis-
playing graphics. Next we introduce the various Drawable objects that can be added to a
canvas. We continue by discussing techniques to control the relative depths of objects that
appear on a canvas and to perform rotations, scaling, and cloning of those objects. Near
the end of the chapter we discuss more advanced topics, including the use of a Layer class
that provides a convenient way to group shapes into a composite object, techniques to cre-
ate dynamic animations rather than still images, and preliminary support for monitoring a
Upper Saddle River� New Jersey 07458 user’s interactions with the mouse and keyboard.
89
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
Canvas
x−�xis
3.2 Drawable Objects
The cs1graphics module supports a variety of objects that can be drawn onto a canvas.
These objects are organized in a class hierarchy originally modeled in Section 1.4.4. A
more detailed summary of the various classes is given in Figure 3.4. This diagram is packed
y−�xis with information and may seem overwhelming at first, but it provides a nice summary that
can be used as a reference. The use of inheritance emphasizes the similarities, making it
easier to learn how to use each class.
When examining this diagram, remember that classes in a hierarchy inherit methods
from their parent class. For example, each type of FillableShape supports a setFillColor
method. So not only does a Circle support the setRadius method, but also the setFillColor
FIGURE 3.3: The canvas coordinate system, with the origin at the top left corner. method due to its parent class, and the setBorderWidth method due to its grandparent
Shape class. Many of the names should be self-explanatory and formal documentation
for each class can be viewed directly in the Python interpreter as you work. For example,
complete documentation for the Circle class can be viewed by typing help(Circle) at the
The coordinate system
Python prompt.
Before introducing the individual shapes, we discuss the coordinate system used to describe Once you know how to create and manipulate circles, it will not take much effort
the positions of those objects relative to a canvas. We have already seen that each canvas to learn to create and manipulate squares. Circles and squares are not identical; there are
has a width and a height, measured in pixels. To describe the locations of shapes, we con- a few ways in which they differ. But there are significantly more ways in which they are
sider a coordinate system with the x-axis running horizontally and the y-axis vertically. similar. To demonstrate the basic use of the individual classes, we spend the rest of this
The standard coordinate system for computer graphics uses the top left corner of a canvas section composing a simple picture of a house with scenery. This example does not show
as the origin, as shown in Figure 3.3. A typical position in the canvas is then specified as every single feature of the graphics library, but it should provide a good introduction.
a pair of coordinates, with the x-coordinate measuring the number of pixels to the right of
that corner, and the y-coordinate measuring the number of pixels below that corner.
When we want to place one of our graphical objects upon a canvas, we specify the Circle
coordinates for a key reference point of that shape, for example, the center of a rectangle. As our first concrete example of a drawable class, we examine the Circle class. A new
As a physical analogy, assume that our canvas is a bulletin board and that our shapes are circle can be instantiated as
made of paper. Each shape is attached to the canvas by an imaginary thumbtack that pokes
through the shape at its reference point. We specify the location of the overall shape relative
>>> sun = Circle()
to the canvas by designating the placement of that thumbtack in the canvas’s coordinate
system. Later we will discuss how we can even rotate or scale an object about its reference
However, the Circle is not automatically added to our Canvas. In general, a programmer
point.
may wish to have multiple canvases and shapes, choosing which shape is added to which
canvas. So a drawable object is not placed onto any canvas until we explicitly add it. This
is done by using the add method of the Canvas class. Assuming that we are still working
with the original canvas that we created in Section 3.1, the syntax we use is
>>> paper.add(sun)
Do not confuse the coordinate system used for computer graphics with the tradi- Having typed the command to add the sun to the paper, we might wonder where it is. By
tional mathematical convention. The computer graphics system uses the top left default, this circle has a radius of 10 pixels, a black border, transparent interior, and a center
corner as the origin, with the positive y-axis oriented downward from the origin; position of (0,0). So it has indeed been added; you just need to look carefully at the top
the usual mathematics convention uses the bottom left corner as the origin, with left corner of the screen (shown in Figure 3.5). We see only the portion of the circle that
the positive y-axis oriented upward from the origin. is within the canvas view. The rest of the circle is there in spirit, but not currently visible.
Since this is not exactly how we envisioned the sun, let’s go ahead and change the settings
by calling several methods supported by the Circle class.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
�rawable
fillColor points
Rectangle
Another available shape is a Rectangle , which is similar to a Square except that we can
set its width and height independently. By default, a rectangle has a width of 20 pixels,
a height of 10 pixels, and is centered at the origin. However, the initial geometry can be
specified using three optional parameters to the constructor, respectively specifying the
width, the height, and the center point. Adding to our ongoing picture, we place a chimney
on top of our house with the following commands:
We carefully design the geometry so that the chimney appears to rest on top of the right
side of the house. Recall that the facade is a 60-pixel square centered with a y-coordinate
of 130. So the top edge of that facade has a y-coordinate of 100. To rest above, our
chimney is centered vertically at 85 but with a height of 28. So it extends along the y-axis
from 71 to 99. Alternatively, we could have waited until after the rectangle had been
constructed to adjust those settings, using the setWidth and setHeight methods.
FIGURE 3.6: Our scene after reconfiguring the sun.
We take this opportunity to distinguish between the interior of a shape and its border.
The chimney as described above has a red interior but a thin black outline. For all of
the shapes we have seen thus far, we can separately control the color of the interior, via
setFillColor, and the color of the border, via setBorderColor . If we want to get rid of
Point(250,50) . Therefore, we could have partially configured our original sun using the
the border for our chimney, we can accomplish this in one of three ways. The first is
syntax sun = Circle(30, Point(250,50)) . That circle would still have a default border of
chimney.setBorderColor('red'). This does not really get rid of the border but colors
black and a transparent interior. Alternative colors can only be selected after constructing
it the same as the interior. It is also possible to set the border color to a special color
the circle. (As the designers of the cs1graphics package, we could have included further
'Transparent', in which case it is not even drawn. Finally, we can adjust the width
optional parameters for the constructor, but we opted for the simpler design.)
of the border itself. A call to chimney.setBorderWidth(0) should make the border unseen,
even if it were still designated as black.
Square Polygon
As the next piece of our scene, we use a white square as the front of the house. The Square The Polygon class provides a much more general shape, allowing for filled polygons with
class is another example of a FillableShape. In fact, it supports almost the identical behav- arbitrarily many sides. The geometry of a polygon is specified by identifying the position
iors of a circle, except that its size is described based upon the length of a side instead of each corner, as ordered along the boundary. As was the case with other shapes, the
of the radius of the circle. The constructor for the class accepts two parameters, the first initial geometry of a polygon can be specified using optional parameters to the constructor.
being the width (and thus height) of the square and the second being a Point that designates Because polygons may have many points, the constructor accepts an arbitrary number of
the initial placement of the square’s center. If these parameters are not specified, a default points as optional parameters. For example, we might add an evergreen tree to our scene
square is 10 � 10 and centered at �0� 0). We create the front of our house and add it to our by creating a green triangle as follows:
scene as follows:
>>> tree = Polygon(Point(50,80),Point(30,140),Point(70,140))
>>> facade = Square(60, Point(140,130)) >>> tree.setFillColor('darkGreen')
>>> facade.setFillColor('white') >>> paper.add(tree)
>>> paper.add(facade)
The resulting scene is shown in Figure 3.7. When specifying the points, they must come
It is also possible to change the size of a square after it has been created, using a syntax in order as seen on the border of the polygon, but it does not matter whether that order is
such as facade.setSize(60). clockwise or counterclockwise. By default, the polygon’s reference point is its first point.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
Path
A Path is a shape that connects a series of points; in this respect, it is very similar to a
Polygon. However, there are two key differences: the ends of a path are not explicitly con-
nected to each other, and a path does not have an interior. A Path qualifies in our hierarchy
as a Shape but not as a FillableShape. We can change the color by calling setBorderColor ,
and the thickness by calling setBorderWidth . Its behaviors are otherwise similar to those
described for the Polygon (e.g., addPoint , setPoint, deletePoint ). As a simple example of
a path, we add a bit of smoke coming out of the top of our chimney.
A path between two points is simply a line segment (akin to the hypothetical Segment
class modeled in Figure 1.13). Let’s add a few rays around our sun using four simple paths.
We place one ray to the left of and below the sun; we will call this object sunraySW to des-
ignate this as the “southwest” of the four rays. The biggest challenge in this case is to
determine the proper coordinates. We create it as follows:
FIGURE 3.7: Further progress on our scene.
>>> sunraySW = Path(Point(225,75), Point(210,90))
>>> sunraySW.setBorderColor('yellow')
It is also possible to add additional points to a polygon after it is constructed. This is >>> sunraySW.setBorderWidth(6)
done by calling the addPoint method. By default the new point is appended to the end of >>> paper.add(sunraySW)
the current sequence of points. For example, we could have created our tree as follows:
Sun rays emanating in other directions can be declared in similar fashion. Our updated
>>> tree = Polygon() drawing to this point is shown in Figure 3.8.
>>> tree.addPoint(Point(50,80))
>>> tree.addPoint(Point(30,140)) Text and Image classes
>>> tree.addPoint(Point(70,140)) Two additional classes are not used in the current drawing of a house but are very helpful
>>> tree.setFillColor('darkGreen') in general. The first of these is the Text class, used for rendering character strings within
>>> paper.add(tree) the drawing area of the canvas. The constructor takes two parameters: the first is a string
designating the actual message to be displayed, and the second is a font size (12 by default).
The addPoint method can also be used to add a new point into the middle of the existing This class fits into our hierarchy somewhat separately; it does not even qualify as being a
sequence of points. In this case, an optional second parameter is used to designate the Shape (as it does not support a concept of a border). Instead, it has dedicated methods to
index of the new point within the sequence of points. Just as with Python’s lists in Sec- support the manipulation of the message, the font size, and the font color. Once created,
tion 2.2, the first point is considered to have index 0, the next point index 1, and so on. For the text can be repositioned to a desired location. By default, the reference point for a text
example, a concave angle could be added at the bottom of the tree by inserting a new point object is aligned with the center of the displayed message. The summary of its methods is
as included in Figure 3.4 on page 94; we provide an example usage in a later case study in
this chapter.
>>> tree.addPoint(Point(50,120), 2) Although not shown in Figure 3.4, cs1graphics includes an Image class that pro-
vides support for using a raw image loaded from a file. An image object is constructed
An existing point can be replaced with a new value using the setPoint method. The by specifying the underlying file name, as in Image('lightbulb.gif'). However, the
first parameter is the new point value and the second is an integer index. For example, we precise set of supported file formats (e.g., gif, jpg, bmp, tiff) will depend upon your com-
could raise the tip of our tree using the syntax tree.setPoint(Point(50,70), 0). An existing puter system. When controlling the placement of an image on a canvas, the reference point
point can be removed altogether using the syntax deletePoint(i), where i is the index. is aligned with the top left corner of the image.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
100 Chapter 3 Getting Started with Graphics Section 3.2 Drawable Objects 101
FIGURE 3.8: Sun rays and smoke have been added to our scene. FIGURE 3.9: Demonstrating the use of depths in placing the grass, window, and roof.
3.2.1 Colors
The relative ordering of conflicting shapes is controlled by an underlying numeric
We have seen the use of various colors for the borders and interiors of our shapes. Thus attribute representing the “depth” of each drawable object. By default, all objects are
far we have relied upon names like 'skyBlue' when specifying a desired color. A color assigned a depth value of 50 and their relative ordering is arbitrary. However, those depths
is represented behind the scene by what is known as its RGB value. This is a tuple of can be changed to control the image. When two or more objects overlap, the one with the
three numbers that represent the intensity of red, green, and blue respectively (hence the smallest depth will be drawn “nearer” to the viewer. For example, by giving the grass a
acronym, RGB), using a scale from 0 (no intensity) to 255 (full intensity). Names like greater depth than the default, say grass.setDepth(75), it will be drawn behind both the
'skyBlue' are predefined for convenience by the cs1graphics package and mapped to tree and the house. This is not a three-dimensional rendering, so the picture will look the
an appropriate RGB value, for example, (136, 206, 235). same no matter whether we set this depth to 75 or 100 or 1000. In similar spirit, we might
The set of predefined color names is somewhat arbitrary; you may type help(Color) give the window a depth of 30 so that it is drawn in front of the house facade. We set the
in the interpreter for more information. However, you may define new colors by directly depth of the roof to 30 so that it is in front of the facade, yet the chimney a depth of 20 so
specifying the underlying RGB tuple in place of a name. As an example, if we wanted our that it appears even nearer than the roof. Although we have given both the window and the
sky to be slightly brighter blue than the official 'skyBlue', we could use the command roof a depth of 30, those shapes do not overlap so the conflict is irrelevant. The complete
paper.setBackgroundColor( (136, 206, 244) ). Note the use of parentheses here. The caller source code used to generate our latest image is given in Figure 3.10.
is sending a tuple as a single parameter; see Section 2.3.2 for a discussion of tuples.
3.2.2 Depths
To further improve our scene we wish to add grass to the picture. We do so by placing
one very large green rectangle to cover the bottom portion of the sky blue background. We
also add a window, and roof to our house. The updated image we have in mind is shown in When objects have overlapping geometries, their relative appearance on a canvas
Figure 3.9, however, achieving this effect brings up a new issue. While the shapes for the is based upon specified depths. An object with greater depth will appear to be
grass, window and roof are simple, we have to consider the apparent overlap among the obscured by one with lesser depth. Items with equal depths are ordered arbitrarily.
objects. The tree and house must appear to be in front of the grass, the window in front of
the house, and the roof behind the chimney yet in front of the facade of the house.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
102 Chapter 3 Getting Started with Graphics Section 3.3 Rotating, Scaling, and Flipping 103
104 Chapter 3 Getting Started with Graphics Section 3.3 Rotating, Scaling, and Flipping 105
Rotating
With the preceding discussion of the reference point, we are ready to discuss rotation.
Formally, drawable objects support a rotate method that takes a parameter specifying the
clockwise rotation measured in degrees. That value can be an integer or floating-point
number. As an example, we can rotate a Square 20◦ about its center point as follows:
This rotates the square, leaving its center point fixed. If we want to rotate the square about
its bottom left corner, we need to first adjust the reference point. For example, FIGURE 3.11: Rotating a square 20◦ in the clockwise direction. In both diagrams the
original position is drawn with darker border and the resulting position with lighter border.
>>> block = Square(40, Point(100,100)) In the case on the left, the square is rotated about its center point. On the right, the same
>>> block.adjustReference(-20, 20) square is rotated about its lower left corner.
>>> block.rotate(20)
Both of these scenarios are diagrammed in Figure 3.11, with the original and new positions
superimposed and the reference point highlighted.
Scaling
For the most basic shapes, we already have specialized methods to accomplish scaling.
For example, we can directly modify the radius of a circle, or the width and height of a
rectangle. Yet there is a more general approach that applies to all drawable objects. Each
supports a method scale that takes a single parameter specifying a (positive) multiplicative
factor by which the object is to be scaled. If that factor is greater than one, it causes the FIGURE 3.12: Scaling a pentagon about two different reference points. In both diagrams
size to increase; when the factor is less than one it causes the size to decrease. For intricate the original position is drawn with darker border and the resulting position with lighter
shapes, the use of the scale method can help simplify otherwise complicated geometry. border. On the left, the pentagon is scaled relative to its center point. On the right, the
When performing a scale, the reference point remains fixed. All other points in the pentagon is scaled about the rightmost corner.
shape are scaled relative to that reference point. To demonstrate the effect, Figure 3.12
shows two examples of a pentagon being scaled. In each example, the pentagon is scaled
by a factor of 0.5. Yet in the first case, it is scaled with the center as the reference point; in
the second case, the rightmost corner serves as the reference point.
Flipping
Another convenient transformation is to take a mirror image of an object. For this reason,
drawable objects support a flip( ) method. By default, this causes a flip to take place across
a vertical axis of symmetry passing through the reference point. In effect, this causes a
left-to-right, right-to-left flip as shown in the first part of Figure 3.13.
To allow a flip in an arbitrary direction, the method accepts an optional parameter that
specifies the clockwise rotation of the axis of symmetry away from vertical. For example FIGURE 3.13: Flipping a flag about two different axes of symmetry. In both diagrams the
the call flip(10) produces the result shown on the right side of Figure 3.13, with a slightly original position is drawn with darker border and the resulting position with lighter border.
askew axis of symmetry. As is the case with the other transformation, notice that the On the left, the flag is flipped about the vertical axes by default with flip( ). The right shows
reference point always remains fixed by this operation. the result of flip(10).
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
106 Chapter 3 Getting Started with Graphics Section 3.5 Case Study: Smiley Face 107
108 Chapter 3 Getting Started with Graphics Section 3.6 Layers 109
31 leftEyebrow = Path(Point(60,65), Point(70,60), Point(80,65)) Earlier, we explained our drawing package through the analogy of physical shapes
32 leftEyebrow.setBorderWidth(3) being attached to a canvas. We wish to extend this analogy to layers. We consider a layer
33 leftEyebrow.adjustReference(10,15) # set to center of left eyeball to be a thin clear film. We can attach many shapes directly to this film rather than to the
34 leftEyebrow.rotate(−15) canvas. Of course the film itself can be positioned over the canvas and combined with other
35 paper.add(leftEyebrow) shapes to make a complete image. In fact, this is precisely the technology that advanced
36 the creation of animated movies in the early 1900s. Rather than drawing the artwork on
37 rightEyebrow = leftEyebrow.clone( ) a single piece of paper, different layers of the background and characters were separated
38 rightEyebrow.flip( ) # still relative to eyeball center on clear sheets of celluloid. These were commonly called animation cels. The advantages
39 rightEyebrow.move(60,0) # distance between eyeball centers were great. The individual cels could still be layered, with appropriate depths, to produce
40 paper.add(rightEyebrow) the desired result. Yet each cel could be moved and rotated independently of the others to
adjust the overall image. For example, a character could be on a cel of its own and then
moved across a background in successive frames.
FIGURE 3.16 �continuation): Code to draw a smiley face.
We demonstrate the use of the Layer class by adding a car to our earlier house scene.
Before concerning ourselves with the overall scene, we compose the layer itself. A layer is
somewhat similar to a canvas in that it has its own relative coordinate system. Shapes can
The eyebrows are created using a combination of techniques. We start at lines 31 be added to the layer, in which case the shape’s reference point determines the placement of
and 32, creating the left eyebrow as a thickened path which is centered directly above the that shape relative to the layer’s coordinate system (i.e., the shape is tacked to a position on
left eye. To achieve a crooked appearance, we then rotate the eyebrow counterclockwise the layer). We use the origin of the layer as a landmark when placing individual elements.
about the center of the eye itself. This requires careful use of the eyebrow’s reference A sketch of our suggested geometry for the components of a car is given in Figure 3.17. We
point. Upon construction, the path’s reference point is aligned with the first declared point place the car so that the tires rest on the x-axis and so that the car is centered horizontally
of that path: �60� 65) in this case. Since the left eye is centered at �70� 80) we adjust the about the origin. We build this model using the code in Figure 3.18. Three individual parts
eyebrow’s reference point by �10� 15) at line 33. Once that has been done, we rotate 15◦ in are added to a new layer with coordinates based upon our sketch. When placed on a layer,
the counterclockwise direction at line 34. a shape’s depth determines how that shape appears relative to other shapes on the same
Though we could create the right eyebrow using a similar technique, we instead make layer. We set the body’s depth to 60 so that it appears behind the two tires, which have
a clone of the left eyebrow and then flip that clone horizontally. Because the reference point default depth of 50.
for the brow was already re-aligned with the center of the eyeball, the flip at line 38 causes At this point, we have created our layer but not yet displayed it. Yet the layer is itself
the new brow to be cocked to the right rather than the left. However the new brow is still a Drawable object, and so it supports all of the familiar behaviors. If we add the layer to a
located near the left eye. Line 39 translates the new brow rightward, precisely the distance canvas, the reference point of the layer (its origin, by default) represents where that layer
separating the two eyes. This causes the right eyebrow to be aligned above the right eye is tacked upon the canvas. A layer has its own depth attribute, used to determine whether
rather than the left eye.
3.6 Layers
Our next class is an extremely valuable tool (and one of our favorites). It allows us to
treat a collection of other elements as a single composite object that we term a Layer. For
motivation, let’s add a car to our earlier scene. Visually, we could achieve the image of a
car perhaps by using three separate shapes: the car body and two tires that appear nearer
than the body. However, if we want to move the “car” elsewhere we have to move the
body and each tire. Separately moving each piece the same amount is time consuming and
error prone. We might accidentally forget to move a tire, or move it by the wrong amount.
Things get even more complicated when working out the correct geometry for scaling or
rotating the car or when working with a more intricate design using more elements. A much
better programming technique is to group those related shapes into a single composite
object. In this section, we introduce a Layer class for this purpose. A Layer is sort of a FIGURE 3.17: Geometric sketch of a car, within the coordinate system of a layer. The
hybrid of a Canvas and a Drawable . It serves as a container for other shapes while also solid grid lines represent the x-axis and y-axis. The dashed grid lines designate a sepa-
serving as a Drawable object that can itself be added to a Canvas (or even to another Layer ). ration of 10 pixels each.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
110 Chapter 3 Getting Started with Graphics Section 3.7 Animation 111
car = Layer( )
tire1 = Circle(10, Point(−20,−10))
tire1.setFillColor('black')
car.add(tire1)
the layer is considered to be above or below other objects on the canvas. So to add the car FIGURE 3.19: Adding a car in front of our house.
to our house scene, we position in properly relative to the canvas’s coordinate system. This
is accomplished with the following:
car.moveTo(110,180)
car.setDepth(20)
paper.add(car) 3.7 Animation
Most of our efforts thus far have been used to design a single static image, albeit with
The resulting image is portrayed in Figure 3.19. It is important to keep straight the distinc- increasing complexity. Yet graphics can be used to generate intricate animations, involving
tion between the canvas’s frame of reference and the layer’s frame of reference. The car’s multiple scenes and moving objects. Creating an animation can be straightforward. In this
placement on the canvas was made according to the coordinate (110, 180) of the canvas. section we address several new issues that arise in the context of animation.
More specifically it is the reference point of the layer (the hypothetical thumbtack through
the layer’s origin) that is tacked to the canvas at that coordinate. This example also demon- Controlling timing
strates an important subtlety in the use of depths. Recall that when building the layer we
left the tires with the default depth of 50 and gave the car body a depth of 60. That is why When working within an interactive interpreter session, we see the immediate effect of
the body appears behind the tires. However, you will notice that the car body appears in each command displayed on the screen. However, if we type a long series of graphical
front of the house facade, even though that facade has depth 50. The reason is that the commands into a file and execute that file, chances are that all of the intermediate stages
depths of individual components are only relevant when compared to other elements in the will go by in a blink of an eye. For example, we might wish to move a car across the
same context. There are three objects added to the layer and their depths distinguish how screen using a series of calls to the move method. Yet the computer is rather quick and
those objects are interleaved. But the placement of the layer as a whole onto the canvas may display the images too fast for the eye to see.
is based upon the depth attribute of the layer object. In this case we intentionally set the To better control the images seen by a viewer, we can use a sleep function that
layer’s depth to 30 so that the entire car appears nearer than the house. is part of Python’s standard time library. We can load this module using the command
As a Drawable object, a layer can be rotated, flipped, cloned, or scaled about its from time import sleep, as originally described in Section 2.7. Once the library is loaded,
reference point. For example, if we were to scale our car, that imaginary thumbtack through a call to the sleep function causes the program to wait a given number of seconds before
the origin of the layer remains fixed, and so the bottom of the tires remain at the same proceeding. For example, sleep(1.5) will wait one and a half seconds before proceeding.
y-coordinate. So if our car appears to be resting on top of a street or driveway, it will This command can be used not just in graphical programs, but as part of any software.
remain so when scaled. Similarly, a rotated car could be placed properly on a hill by Within the context of graphics, a pause can be used to give the impression of motion.
placing its origin on the edge of the hill. Consider the following code fragment to move our car.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
112 Chapter 3 Getting Started with Graphics Section 3.8 Graphical User Interfaces 113
from time import sleep The cs1graphics package supports a form of double buffering as follows. Until now,
timeDelay = .25 # one-quarter second we have relied upon the default behavior of a Canvas, which is to immediately display each
car.move(−10, 0) change to the screen. We refer to this behavior as “auto-refresh” mode. Although it is the
sleep(timeDelay) default, we can change it by using the setAutoRefresh method. This takes a single boolean
car.move(−30, 0) parameter. If we call paper.setAutoRefresh(False), this turns off the automatic refresh
sleep(timeDelay) mode and instead reverts to a manual refresh mode. When in this mode, no changes are
car.move(−60, 0) rendered to the screen unless an explicit call to paper.refresh( ) is made. In the context of
sleep(timeDelay) an animation, we can enact all of the changes that we want to make behind the scene and
car.move(−100, 0) then refresh the canvas (and perhaps pause) each time we want to show a new frame to the
sleep(timeDelay) viewer. To demonstrate this mode, try the following in an interactive session.
The speed of the animation can be adjusted by altering the time delay or by having a longer
>>> paper = Canvas(200, 200, 'skyBlue')
series of motion. Another use for sleep is to produce an animation with multiple frames.
>>> ball = Circle(25, Point(100,75))
We can develop an intricate scene, and then intentionally pause so that the user can take in
>>> paper.add(ball) # ball immediately appears
the scene for a certain amount of time. After the pause, additional code can alter portions
>>> ball.setRadius(50) # ball immediately changes
of the scene or even compose an entirely new scene for the viewer.
>>> paper.setAutoRefresh(False)
>>> ball.setFillColor('red') # no visible change
Controlling flicker >>> ball.move(50,0) # no visible change
There is a drawback in trying to create multiframe animations as we have described. When >>> paper.refresh() # image is updated
there are a large number of shapes, or a significant number of separate changes that take
place from one frame to the next, there is the possibility that the user may see some partially
composed images that we may not intend to have viewed. For example, if one command When there are relatively few objects in the scene, or relatively few changes being
moves a character’s head and the next moves the body, the viewer might glimpse the frac- made at a time, there is really not much difference between the quality of automatic refresh
tion of a second in which the head is detached. Although the viewer may not know exactly mode and manual refresh. However, if you ever start to notice artifacts like flicker in
what happened, this sort of “flicker” can be distracting. complicated animations, you might try switching to manual refresh. If you want, you can
The use of a layer was one approach to have such motion coordinated, but if several always turn automatic refresh back on using a syntax such as paper.setAutoRefresh(True).
different changes in a scene are to be made, there may still be unwanted artifacts. A
common technique for trying to minimize flicker in computer graphics is called double
buffering. If we need to make ten separate changes when moving from one frame to
the next, the idea is the following: don’t change the displayed image at all in the interim. 3.8 Graphical User Interfaces
While the original frame is still in view, we would like to perform all ten of the hypothetical
changes behind the scene. Only after the next frame is computed internally, should it be When using purely text-based software, the only way a user can interact with a program
displayed on the screen for the viewer. is by entering text into the console, and only when prompted to do so. For most modern
software, users interact with programs through a combination of keyboard strokes and
mouse movements and clicks. Users select from menus, click on buttons, and enter text
within boxes. Such an interface is known as a graphical user interface, or GUI for short
(pronounced “gooey”). In this section, we outline some of the basic support for building
simple GUIs using the cs1graphics package.
In this chapter, we create motion and frames in an ad hoc way, manually repeating One of the most important issues for a graphical interface is being able to detect a
commands to produce the effect. This is not really a good programming style. In user event, such as a mouse click or a keystroke. We will explore this concept of event
Chapter 4, we will introduce a technique known as a loop for more elegantly driven programming as an advanced topic in Chapter 15, but for now we wish to introduce
expressing the repetition of commands. Loops can be extremely useful when a few simple features that you can use right away. All of our Drawable objects support
creating animations. a method, named wait( ), that causes the program to pause indefinitely until an event is
detected upon the given object. To demonstrate the technique, we provide the following
very simple example.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
114 Chapter 3 Getting Started with Graphics Section 3.8 Graphical User Interfaces 115
7 paper.wait( ) Widgets
8 paper.close( )
We note in passing that the cs1graphics package includes several additional classes to
support graphical user interfaces. Typically these tools are known as widgets. A TextBox
In this case, after turning the light green it waits for another event, this time anywhere on allows a user to enter text that can later be retrieved from the program, and a Button serves
the canvas. Note that such an event does not necessarily need to be on the background; as a clickable rectangle with a label. More complete documentation on their use is available
clicking on any part of the canvas (including the circle) suffices. online, and we will discuss their implementations in later chapters. For now, we provide
In the first example, we used wait to control the timing of our program, but we did the following brief demonstration.
not take notice of the type of event or the location of the event. However, the wait( ) method
provides a return value. To see this, try the following interactively in the interpreter: paper = Canvas( )
nameInput = TextBox(150, 50, Point(100,10))
>>> paper = Canvas() paper.add(nameInput)
>>> paper.wait() submit = Button('Enter name', Point(100,80))
paper.add(submit)
After typing the second command, the program is waiting for us to trigger an event (notice submit.wait( )
that we do not yet have a subsequent prompt). If we go ahead and click on the canvas, we welcome = Text('Hello, ' + nameInput.getMessage( ))
get the following continuation: welcome.move(100, 150)
paper.add(welcome)
<cs1graphics.Event object at 0x6d52f0>
>>> This interface displays a textbox and a button. When the button is pressed, it incorporates
the characters previously entered into the text box into a newly displayed greeting.
That return value is an instance of the Event class. Each event stores information about
the type of event that occurred (e.g., mouse, keyboard), and additional characteristics. For
example, a mouse event stores the coordinates of where the click occurred and which but-
ton was used. A keyboard event indicates which key was pressed. Figure 3.20 provides a
brief overview of the accessors supported by the Event class. We will explore use of events
more fully in Chapter 15, but for now we offer a simple example. The getMouseLocation( ) If you call wait( ) on an object such as a circle that has a transparent interior, it
method returns a Point that represents the location of the mouse at the time the event will only respond to clicks that take place on the border.
occurred. For example, here is a program that adds a circle to the canvas centered at the
indicated location.
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
116 Chapter 3 Getting Started with Graphics Section 3.9 Case Study: Flying Arrows 117
118 Chapter 3 Getting Started with Graphics Section 3.9 Case Study: Flying Arrows 119
120 Chapter 3 Getting Started with Graphics Section 3.10 Chapter Review 121
3.10.1 Key Points canvas A graphics window on which objects can be drawn.
clone A copy of an object.
Graphics Primitives
double buffering A technique for avoiding flicker in animation by computing incremen-
• Creating a Canvas instance constructs a window. Various objects can be visualized by
tal changes internally before displaying a new image to the viewer.
calling the add method of the canvas.
event An external stimulus on a program, such as a user’s mouse click.
• The window for a canvas does not close unless the user manually closes the window
through the operating system or the close method of the canvas is called. event-driven programming A style in which a program passively waits for external
events to occur, responding appropriately to those events as needed.
• Coordinates are measured from the top left corner of the window. The x-coordinate speci-
fies the number of pixels to the right of this point and the y-coordinate specifies the number graphical user interface �GUI) A design allowing a user to interact with software
of pixels below this point. through a combination of mouse movements, clicks, and keyboard commands.
• Only Drawable objects can be added to a canvas. pixel The smallest displayable element in a digital picture.
Modifying Drawable Objects reference point A particular location for a cs1graphics.Drawable instance that is used
when determining the placement of the object upon a canvas’s coordinate system.
• There are mutators to change the location, color, border color, position, and other features The reference point remains fixed when the object is scaled, rotated, or flipped.
for each drawable object.
RGB value A form for specifying a color as a triple of integers representing the intensity
• A drawable object can be rotated, scaled, or flipped relative to its reference point. This of the red, green, and blue components of the color. Typically, each color is measured
reference point can be reconfigured using the adjustReference member function. on a scale from 0 to 255.
Depths widget An object that serves a particular purpose in a graphical user interface.
• When two or more drawable objects overlap on a canvas, the relative ordering of those
objects is determined according to their specified depth attribute. Shapes with smaller 3.10.3 Exercises
depths are drawn in front of those with larger depths. Graphics Primitives
Layers Practice 3.1: The following code fragment has a single error in it. Fix it.
• A Layer represents a collection of objects that is treated as a single shape. They layer can
1 from cs1graphics import *
be added, moved, rotated or scaled, just as with any other Drawable instance.
2 screen = Canvas( )
• Depths of objects within a layer only affects how the objects in the layer appear relative to 3 disk = Circle( )
each other. The depth of the layer controls whether all the shapes in that layer appears in 4 disk.setFillColor('red')
front or behind objects that are not part of the layer. 5 disk.add(screen)
Animation
• A time delay for an animation can be achieved by calling the sleep function imported from Practice 3.2: Write a program that draws a filled triangle near the middle of a canvas.
the time module.
For Exercise 3.3 through Exercise 3.7, try to find the errors without using a computer. Then
• Flicker can be reduced in an animation by turning auto-refresh off for the canvas and calling
try running them on the computer to help find the errors or confirm your answers.
refresh each time you want the canvas’s image rendered to the screen.
Exercise 3.3: After starting Python, you immediately enter
Events
• Calling the wait( ) method of the Canvas class causes the program to wait indefinitely until can = Canvas(100,100)
the user triggers an event on the window, such as a mouse click or keypress.
Python reports an error with the last line saying
• Calling the wait( ) method on an individual drawable object waits until the user triggers
and event specifically upon that particular object.
NameError : name 'Canvas' is not defined
• The wait( ) function returns an Event instance that contains information about which mouse
button or key was pressed, and the cursor location at that time. What did you do wrong? How do you fix it?
Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher Excerpt from “Object-Oriented Programming in Python” by Michael H. Goldwasser and David Letscher
122 Chapter 3 Getting Started with Graphics Section 3.10 Chapter Review 123
Exercise 3.4: Assuming that you have already created an instance of the Square class Layers
called sq, what’s wrong with the statement Exercise 3.7: The following runs but does not display anything. What is wrong?
can = Canvas( )
sq.setFillColor(Red)
lay = Layer( )
sq = Square( )
Give two different ways to fix this statement. lay.add(sq)
Exercise 3.5: Assuming that you have already successfully created a Canvas instance
Exercise 3.8: Use the Layer class of the graphics library to create a pine tree. Make copies
called can, you enter the following to draw a blue circle centered in a red square:
of the tree and use it to draw a forest of pine trees.
Exercise 3.9: Redo the smiley face described in the chapter as a layer. Test the code by
sq = Square( ) rotating and scaling the layer and ensuring the face does not get distorted.
sq.setSize(40)
Exercise 3.10: Create an airplane as a layer and animate it flying across the screen and
sq.moveTo(30,30)
doing a loop. Hint: think carefully about the placement of the reference point.
sq.setFillColor('Red')
can.add(sq) Graphics Scenes
Exercise 3.11: Use the graphics library to create a picture of your favorite animal with an
cir = Circle( ) appropriate background scene.
cir.moveTo(50,50) Exercise 3.12: Use the graphics library to draw an analog clock face with numbers at the
cir.setRadius(15) appropriate locations and an hour and minute hand.
cir.setFillColor('Blue') Events
can.add(cir) Exercise 3.13: Display the text �Click Me� centered in a graphics canvas. When the user
clicks on the text, close the canvas.
But the circle never appears. What’s wrong with the above program? Edit the pro- Exercise 3.14: Create a program that draws a traditional traffic signal with three lights
gram so it works as desired. (green, yellow, and red). In the initial configuration, the green light should be on,
but the yellow and red off (that is, black). When the user clicks the mouse on the
Exercise 3.6: Consider the following: signal, turn the green off and the yellow on; when the user clicks again, turn the
yellow off and the red on.
can = Canvas(200,150) Exercise 3.15: Write a program that displays a Text object graphically and adds characters
typed on the keyboard to the message on the canvas.
rect = Rectangle( ) Exercise 3.16: Write a program that allows the user to draw a path between five points,
rect.setWidth(50) with each point specified by a mouse click on the canvas. As each successive click
rect.setHeight(75) is received, display the most recent segment on the canvas.
rect.moveTo(25,25) Projects
Exercise 3.17: Use the graphics library to draw an analog clock face with hour, minute,
rect = Rectangle( )
and second hands. Use the datetime module to start the clock at the current time and
rect.setWidth(100)
animate the clock so that it advances once per second.
rect.setHeight(25)
Exercise 3.18: Write a game of Tic-tac-toe using a graphical interface. The program
can.add(rect) should draw the initial board, and then each time the mouse is clicked, determine
can.add(rect) the appropriate square of the game board for drawing an X or an O. You may allow
the game to continue for each of the nine turns. (As a bonus, read ahead to Chapter 4
and figure out how to stop the game once a player wins.)
Only one rectangle appears? Why? How would you get two different rectangles to Exercise 3.19: Think of your own cool project and have fun with it.
show up? (There are several ways to fix this.)