Previous Page Next Page

Chapter 9
GRAPHICS

You have already seen some Smalltalk/V graphics when running the Graphics Demo application and in early exercises in these tutorials. In this chapter, you will learn how to produce Smalltalk/V graphics and animation.

You can find the sample code for this tutorial in the disk file chapter.9. You will again be adding new methods and classes to the environment during this tutorial, so be sure to save the image when you exit Smalltalk/V.

Some Basic Concepts

Smalltalk/V graphics are built on the graphics interface language of the operating environment. The graphics interface language of Windows is Graphics Device Interchange (GDI). Smalltalk/V calls the GDI library of functions to implement Smalltalk graphical operations.

Smalltalk/V supports both bit-mapped (also known as raster) and vector graphics. A character stored as a bitmap is formed from a collection of dots or pixels, in raster graphics.

A character in vector graphics is represented by a mathematical formula describing direction and length of the path along its edges. Any graphical shape can be represented and transformed by abstract geometrical description, without regard for the dot resolution of the eventual display or output hardware. This feature of vector graphics promotes the goal of device independent graphics.

By virtue of device independence, Smalltalk/V, working with the host graphics language, makes it easy to render your graphics on one medium, usually your display screen, and then output the graphics to a printer, plotter or film recorder. The beauty of the device independence of vector graphics is that the quality--the resolution and color--of the resulting graphic is a function of the output device, not the device on which the graphic was created.

The vector graphics described above implement an abstract graphic model. Certain output and display devices, such as the mechanical pen plotter, actually implement vector graphics as vectors-the pen moves continuously from position A to position B, for example. But many output and display devices are dependent on creating images composed of dots--a line consists of turning on each dot along the path between point A and point B. Consequently, vector graphics are rendered often, as best they can be, by a raster output device. That is why understanding Smalltalk graphics, whether vector or raster, is best grounded in understanding the basics of bitmap graphics. The extension of bitmapped graphics concepts to vector capabilities then is relatively easy.

The bitmapped dots referred to above are displayed on your screen as pixels, a term short for "picture elements." Pixels are stored internally as a Bitmap. A Bitmap is a matrix of bits, with a value 1 representing white and 0 representing black. To refer to an individual dot within a Bitmap, you use Points. To move a group of dots from one place to another (either within the same Bitmap or between different Bitmaps), you use Rectangles to denote the areas involved.

Thus Point, Rectangle and Bitmap are basic Smalltalk graphic data structures, each represented by a class in Smalltalk/V. Objects which render graphical images using point, rectangle and bitmap data structures are instances of the subclasses of GraphicsTool. GraphicsTool subclasses include TextTool and its subclass Pen, Pen's subclass RecordingPen and RecordingPen's subclass Commander. Each graphics class has an appropriate collection of instance variables and methods to implement the required graphical operations.

When you decide you want or need to go beyond the core of graphical capabilities of Smalltalk/V, refer to Appendix 2, Windows Classes and API Calls. Smalltalk/V will support any function described in the Microsoft Windows Software Development Kit Reference. Any functions not defined in the base image can be easily added as described in the above- mentioned Appendix.

Point

A Point refers to a position within a two-dimensional array. It has two instance variables: x, the horizontal coordinate, and y, the vertical coordinate. In order for a graphics tool to address the pixels on a display or output device, there must be a coordinate system imposed on the device. The coordinate system used by Smalltalk/V is shown in Figure 9.1.

Figure 9.1
Coordinate System

The origin (0, 0) can be mapped to anywhere on the device medium. By default, it is aligned at the upper left corner of the device. The units of the coordinate system are selectable. The available choices depend on the graphical interface. The default unit is the pixel. Additional coordinate system information is found in Chapter 14.

To create a Point, you use the binary message @. For example, the expression:

5 @ 10

creates a Point referencing the fifth pixel to the right and the tenth Pixel down from the coordinate system's origin. Evaluate the following expressions:

(5 @ 10) x
(5 @ 10) y

These expressions return the values 5 and 10, respectively. You can also translate, multiply, divide, or compare Points, as in these examples:

(1 @ 2) rightAndDown: (1 @ 2)
(1 @ 2) leftAndUp: (1 @ 2)
(1 @ 2) * (1/2 @ (1/2))
(1 @ 2) < (3 @ 4)
(3 @ 5) > (3 @ 4)

These expressions combine or compare x of the receiver with x of the argument and y of the receiver with y of the argument. This is why the last expression returns false; the first x, 3, is not greater than the second, also 3.

You can mix a Point with a scalar:

(1 @ 2) + 1
(1 @ 2) // 2

which applies the scalar to both x and y of the Point. You cannot, however, compare Point coordinates and a scalar. For example, try evaluating this expression:

(1 @ 2) < 3

To alter one of the two coordinates, simply use the messages x: and y:. For example, evaluate the following expressions as a group:

| aPoint |
aPoint := (5 @ 10).
aPoint x: 1.
aPoint y: 2.
^aPoint

Rectangle

A Rectangle is represented by two Points: an origin, the upper left Point, and a corner, the lower right Point. With this information, Smalltalk can determine the extent of the Rectangle, the width and height of the elements contained within the Rectangle. The extent is calculated as:

corner - origin

To create a Rectangle, you normally send messages to a Point, as in:

1 @ 1 corner: 100 @ 100

Or equivalently:

1 @ 1 extent: 99 @ 99

Rectangles to be displayed on the screen should be expressed using the coordinate system-independent protocol, such as the following, which draws a box from 1@1 to 100@100:

(Display boundingBox leftTop rightAndDown: 1@1) extentFromTopLeft: 99@99

A Rectangle includes the pixels inside the Rectangle. The Rectangle itself is imposed on gaps between pixels. For example, the Rectangle

2 @ 3 corner: 5 @ 5

contains 6 bits (3 horizontal and 2 vertical) as illustrated in Figure 9.2.

Figure 9.2
Pixels Rectangles

The origin is always included in its rectangle and the corner is always excluded.

There are many operations you can perform on Rectangles. For example, try evaluating each of the following:

(0 @ 0 extent: 100 @ 100) center 
(0 @ 0 extent: 100 @ 100) insetBy: 10 
(-5 @ -10 extent: 20 @ 20)
intersect: (1 @ 2 extent: 20 @ 20)
(-5 @ -10 extent: 20 @ 20)
   containsPoint: 0 @ 0

(These operations are not the focus of this tutorial, however. Refer to Part 3, The Smalltalk/V Windows Reference, for more detailed descriptions.)

Bitmap

A Point or a Rectangle refers to a position or an area of positions. An object that can actually hold a graphical image is an instance of class Bitmap. The host system raster graphics capabilities are implemented using bitmap data structures. A bitmap is an array of bits that represent an image.

When a bitmap image is a simple monochrome graphic, the binary 0 and 1 bits are all that is needed to describe the image. Color, using 4, 8 or 24 bits per pixel, adds a level of bulk and complexity to bitmap data structures. You can use bitmaps to display images that are otherwise too cumbersome to display using GDI functions. More complete information on bitmaps can be found in Chapter 14 as well as in Microsoft Windows Software Development Kit Reference.

Take a couple of minutes to look over the methods that make up the Bitmap class, which is a subclass of GraphicsMedium, before evaluating the following examples. You will see clearly how Smalltalk/V communicates with DLL facilities. Smalltalk/V provides an unusually open and extensive interface between its application programming language and the underlying operating system.

This first example creates a bitmap and draws onto it with its own pen-the bitmap in memory is acting like a physical device, such as a window or printer which are written to with their own pens for displaying text or graphics on the device. The example then uses the Display pen and the displayAt: method to draw the bitmap on your screen at point 10 @ 100:

| bitmap |
bitmap := Bitmap screenExtent: 100 @ 100. 
"creates a Bitmap with same colors per pixel as the screen."
bitmap pen
home;
fill: ClrRed;
foreColor: ClrGreen;
ellipse: 40 minor: 20;
displayText: 'Hi'.
bitmap displayAt: 10 @ 100 with: Display pen.
bitmap release   "Delete bitmap from memory"

Of course, you don't usually want to draw bitmap images directly on the screen as was done here. Graphics under a graphic host interface are most often associated with a window, that is, the graphic appears in a pane within an open window. When you are finished using a loaded bitmap, you should specifically release it's memory as shown.

We'll demonstrate drawing a bitmap within a window as well as add the pizzaz of a little "flipbook" animation. In this example, an array, Pictures, is created to hold eight bitmaps in memory-again drawn using the bitmaps' own pens off-screen. The array of bitmaps are then drawn in flipbook fashion to the newly created Spinning Bitmap window. Drawing in the window is done by the Turtle pen using a simple iterative expression with the displayAt: method to show each of the bitmaps in succession:

| major minor |
Pictures := Array new: 8.
1 to: Pictures size do: [: i |
Pictures at: i put: (Bitmap screenExtent: 100 @ 100).  
major := (i - 1 * 10) integerCos // 2. 
minor := (i - 1 * 10) integerSin // 2. 
(Pictures at: i) pen
home;
fill: ClrRed;
foreColor: ClrGreen;
ellipseFilled: major minor: minor].
Window turtleWindow: 'Flexing Ellipses'.
10 timesRepeat: [ 1 to: Pictures size do: [:i | 
   (Pictures at: i) displayAt: 10 @ 100 with: Turtle] ]

Distinguishing Display and Screen GraphicsMediums

The Smalltalk/V graphics model can be metaphorically related to the "graphics model" of a pen and paper. A graphics tool is like the pen and a graphics medium is like the paper.

In Smalltalk/V, a graphics medium can be anything that is capable of displaying or storing graphics. It can be a monitor screen, a printer, a plotter, a file, or a portion of computer memory. In addition, each window object is treated as a separate medium. If you take a moment to poke around in the Class Hierarchy Browser, you may think this sounds a bit confusing. GraphicsMedium has as its subclasses Bitmap, Printer, Screen and StoredPicture. Further down alphabetically in the class hierarchy list pane, you find Window and its numerous subclasses.

Window and its subclasses are, in fact, graphics mediums. They have been "elevated" to the status of a direct subclass of Object to make them easy to find in the hierarchy and to recognize the fundamental importance of these objects in application programming. While not inheriting the variables and methods of GraphicsMedium as a subclass, all Window subclasses do implement the full protocol of GraphicsMedium.

Anything you can do with a subclass of GraphicsMedium, you can do with a subclass of Window. In fact, that is what you did in your bitmap examples above. First, you drew a bitmap to the global Display which is the sole instance of the Screen class under GraphicsMedium. Next, you drew a series of bitmaps to a Window instance using the same pen and displayAt: techniques used with the Screen instance.

While the Display instance of Screen exhibits the programmable graphics capabilities of a GraphicsMedium, it is generally considered "poor form" to directly draw on the screen. Graphic user interface guidelines encourage you to keep graphical activity within specific windows.

Graphics Tools

The objects you use to do the actual drawing in your windows are grouped under the abstract class GraphicsTool. No instances are ever created of an abstract class; its function is to provide variables and methods for inheritance by its subclasses. GraphicsTool contains variables and methods common to all graphics tools as well as methods that implement BitBlt-like operations. (BitBlt operations including fill patterns, clipping rectangles as well as color and pattern mixing rules are beyond the scope of this tutorial. See the section Graphics Classes in Chapter 14 for more information and examples of these features.)

GraphicsTool has several subclasses, each of which extends the functionality of its superclass. An instance of TextTool works like a typewriter and only displays characters. A Pen instance is capable of not only typing but also drawing graphics. An instance of RecordingPen has all the capabilities of TextTool and Pen and, in addition, is capable of recording graphics operations and replaying them later.

Before you can use a graphics tool you have to first associate it with a graphics medium. For convenience, every time you create a new instance of a medium, a default graphics tool is created and associated with it. This default graphics tool is usually accessed by sending the message pen to the medium instance. For example:

(Bitmap width: 100 height: 100) pen
Printer new pen
Display pen

When you send drawing messages to one of these pens, the result is displayed on its associated medium. A graphics tool always has its instance variable graphicsMedium pointing to the associated medium and the medium always has the variable graphicsTool pointing back to the associated graphics tool.

Multiple graphics tools can be associated with a single medium. A graphics tool can also be disassociated from its current medium and re-associated with a different medium. Therefore you can first associate a graphics tool with a window, compose your picture interactively and later re-associate it with a graphics printer to produce a final hard copy of the picture.

TextTool

Class TextTool works like a typewriter except that it inherits methods from GraphicsTool which allow it to do region filling and block moves. A TextTool instance is normally associated with TextPane instances.

The inherited location variable is used by a TextTool as the base point of the first character in a string to be drawn. In Smalltalk/V, position is kept by the host system, but you can send messages to move and query a TextTool's position.

Evaluate the following to open a TextWindow and use its TextTool to display characters at various locations in the window:

| aWindow textTool |
aWindow := TextWindow windowLabeled: 'TextTool Examples' 
frame: (100 @ 100 extent: 400 @ 200).
aWindow nextPutAll: 'Hi!'; cr.            "display initial text"
textTool := aWindow pane pen.         "access the TextTool"
textTool place: 10 @ 10;            "set position"
displayText: 'Welcome'.            "display string at position" 
textTool displayText: 'to'
at: textTool location + (50 @ 30).    "down and right from location."
textTool centerText: 'Smalltalk/V'
at: textTool extent // 2.            "display a string at center"
textTool lineDisplay: 'Windows'
at: (textTool width - 40) @ (textTool height - 20).
"in lower right corner and blank the rest of the line" 

Before closing the TextTool Examples window, click the Minimize icon and then doubleclick its V-icon to restore the window. Notice that only the "Hi!" string is restored. This string was written to the window with the nextPutAll: method which adds its argument to the TextWindow's file variable which is used to maintain the state of the text in the pane when a getContents event occurs. The other text was written to the pane by the TextTool but is not being maintained in the pane's restorable state.

TextTool adds the font instance variable to those inherited from its GraphicsTool superclass. The font variable can be assigned to any available font and is used as the display font for characters drawn in the TextTool's associated GraphicsMedium.

Evaluate the following example:

| font aWindow textTool |
aWindow := TextWindow windowLabeled: 'TextTool Examples' 
frame: (100 @ 100 extent: 400 @ 200).
textTool := aWindow pane pen.
font := Font chooseAFont: 'Select a font, please.'.  
font isNil ifTrue: [ ^self ].
textTool
font: font;
foreColor: ClrLightgray;
displayText: 'Hello, World! ' at: (60 @ (textTool height // 2 ) ) ;
foreColor: ClrBlack;
   displayText: 'Hello, World! 'at: (60 @ (textTool height // 2 + font height ) ).

When the "Select a font, please." dialog pops up, select a font from those listed, and click on the OK button.

Pen

Class Pen inherits all the capabilities of TextTool. In addition, it provides a line drawing capability based on turtle graphics. Traditional geometry describes simple figures using Cartesian coordinates. A box can be drawn by connecting lines from point 0,0 to 0,10 to 10,10 to 10,0 and back to 0,0. In turtle geometry, you describe the figure as if you were riding the back of a turtle who is tracing the figure with its tail dipped in ink. The box is drawn by "going down 10 units and turning left 90 degrees, three times."

You can position the pen to a desired place and then tell it to draw a line, a box, or some other graphical figure. Besides location, a Pen also maintains its direction, down state, and pen style and pen width. Location tells it where to start for the next movement. Direction allows it to calculate the ending point when the go: message is used, where only units of movement are specified. The angle is expressed in degrees, with "east" equal to 0 and "south" equal to 90. If downState is true, the Pen draws while it moves; otherwise, it moves without drawing.

A newly initialized Pen begins at location 0 @ 0 in the upper left corner of a new window. Evaluate the following to move the pen around, resulting in some simple line drawing:

Window turtleWindow: 'Pen Examples'.
Turtle
goto: 30 @ 30;
box: Turtle extent - 30;   "extent is height and width of pane"
home.   "home is center of window"
10 timesRepeat: [
4 timesRepeat: [
Turtle
go: 100;
turn: 90 ].
   Turtle turn: 36]

Position the Pen Examples window and the workspace window in which you are evaluating example code so that they are side by side.

Additional methods support color fills, line coloring and line width attributes. Evaluate the following:

Turtle
fill: ClrBlue;   "fill pane with blue color" 
home;
foreColor: ClrDarkgray; 
defaultNib: 8;
boxOfSize: 100 @ 100;
foreColor: ClrYellow;
defaultNib: 4;
home;
Circle: 50;
   displayText: 'Hello' at: 10 @ 10 

Note, in addition to the new drawing behaviors, Pen inherits the text display capabilities of TextTool. Minimize and restore the Pen Examples window. Like its TextTool superclass, the Pen's drawing is transient. Close the Pen Examples window and go on to the next section.

RecordingPen

To achieve restorable graphics, comparable to the restorable text in a TextWindow, you can use an instance of RecordingPen. This class implements all the capabilities of its superclasses and it adds variables and methods for recording graphics.

TextTool and Pen implement non-retained graphics. RecordingPen implements retained graphics using graphic segments. A graphic segment is a collection of GDI functions encoded in binary form. Fortunately, you do not have to deal at this basic level of functionality. Simply open a segment, draw graphics in it, and close it.

RecordingPen adds some instance variables and methods which easily allow graphics to be displayed and re-drawn as your windows are collapsed, restored, sized and brought to the top of a stack of overlapping windows. Evaluate the following to open a new window and draw a mandala which will be retained in a replayable segment:

Window turtleWindow: 'RecordingPen Examples'.
Turtle drawRetainPicture: [
Turtle
fill: ClrBlue;
home;
      mandala: 16 diameter: 250 ]

Minimize and restore the RecordingPen Examples window, and cover it with other windows. Bring the RecordingPen Examples window to the top from under overlapping windows. Each time, the mandala and its background are instantly redrawn.

The Network Example Revisited

With all the graphics techniques you have learned so far (and some that are explained further in the Graphics Classes section of Chapter 14), you can now draw a diagram of the network system introduced in Chapter 7.

Evaluate the following expression:

(File pathName: 'tutorial\nodes9.st') fileIn; close 

This adds the following methods to class NetworkNode:

drawWith: aPen 
"Draw the receiver node with a
circle around its name."
| major minor |
major := (SysFont stringWidth: name) + 16 // 2. 
minor := SysFont height + 16 // 2.
pen
setTextAlign: TaTop; 
place: position; 
ellipseFilled: major minor: minor 
      centerText: name.
position
"Answer the position of the receiver node." 
   ^position

The method drawWith: draws a black-edged, solid white ellipse large enough to contain the node name in its center. The method position answers the position of the node.

Evaluate the following expression:

(File pathName: 'tutorial\network9.st') fileIn; close

This adds a draw method in class Network to draw lines between nodes according to the connections defined in a network instance and to draw a node using drawWith:. Note that each line and node drawing expression is captured as a segment by being included in a block argument to drawRetainPicture:. The draw method is as follows:

draw
"Draw the network.  For each node, it draws 
all the arcs and then the node.  All the
nodes visited are remembered to avoid double 
drawing."
| visited pen |
pen := (Window turtleWindow: 'Net') pen.
pen erase.
visited := Set new.
pen drawRetainPicture: [
connections keys do: [ :nodeA |
visited add: nodeA.
(connections at: nodeA) do: [:nodeB | 
(visited includes: nodeB)
   ifFalse: [
      pen place: nodeA position;
         goto: nodeB position] ].
      nodeA drawWith: pen] ]

In Chapter 7, you assigned a network of nodes to the global variable Net. To see that network drawn by a RecordingPen using retained graphics, evaluate the following:

Net draw

Figure 9.3
Displaying a Network

Commander

Commander is a subclass of RecordingPen. A Commander commands an array of RecordingPens. It has messages to fan out all the RecordingPens under its command. It also re-implements messages like place:, turn:, down, up, go:, and goto: to pass the message to all of its RecordingPens. When the units of movement are small, this creates an illusion of all pens drawing simultaneously. For example, evaluate the following example which draws five dragons fanning out:

Window turtleWindow: 'Commander Dragons'.
(Commander pen: 8 forDC: Turtle handle medium: Turtle graphicsMedium) 
up;
home;
down;
fanOut;   "set fan out direction"
up;
go: 60;   "set starting point"
down;
   dragon: 9         "draw dragon" 

The result is as follows.

Figure 9.4
Dragon Curves

AnimatedObject and AnimationPane

Animation--the smooth, automated movement of graphic elements within a window--is a unique form of Smalltalk/V graphics. Animation in Smalltalk/V is implemented in two cooperating classes. One class implements the graphical objects which are animated within the window. The second is a specialized subclass of the Smalltalk/V graphics subpane.

AnimationPane is subclass of GraphPane. An AnimationPane contains one or more instances of AnimatedObject. An AnimatedObject contains a collection of Bitmap instances called frames. The frames of an AnimatedObject depict incremental images of the object in motion--the eye-popping double-take of a character displayed sequentially in a cartoon flipbook, for instance.

An AnimatedObject is manipulated directly by sending it messages which affect its direction, make it turn or set its degree heading, and which determine its speed and steps per frame. The best way to understand Smalltalk/V animation, especially to appreciate the behavior of AnimatedObjects, is by exploration.

Take a few minutes to investigate the source code of the methods in Animated Object and AnimationPane before running the following examples. When you have an idea of how they work, evaluate the following to prepare a window for your animation examples:

Animation := AnimationPane openWindow: 'Animation Examples'

Move and resize the Animation Examples window and the workspace window in which you are running these examples so they are side by side and not overlapping.

Next, add a bouncing AnimatedObject to the AnimationPane by evaluating the following:

Bouncer := AnimatedObject new 
bouncer;
direction: 45;
speed: 1;
stepsPerFrame: 1;
frames: Pictures;
position: Animator pen extent // 2.
Animator addObject: Bouncer.
Bouncer animate

The new global variable, Bouncer, will begin careening off the borders of the Animation Examples window.

You can manipulate an AnimatedObject "on the fly" by sending it appropriate messages. Evaluate each of the following, one at a time, and notice the effect on Bouncer:

Bouncer speed: 5
Bouncer direction: 95
Bouncer stepsPerFrame: 16
Bouncer stop

Another form of Smalltalk/V animation is a chasing AnimatedObject. When an AnimatedObject is told to be a chaser, its movement follows the cursor movement resulting from your mouse action.

Evaluate the following to add animated dogs to your system:

"Load the dog bitmaps from disk files."
| dogPictures |
TutorialPictures := Dictionary new.
dogPictures := Array new: 4. 
1 to: dogPictures size do:  [ :i | 
dogPictures at: i put: ( 
Bitmap fromFile: 'tutorial\dog', i printString, '.bmp' ) ]. 
TutorialPictures at: 'dogs' put: dogPictures

Evaluate the following to add an animated dog to your Animation Examples window:

| dogs |
dogs  := TutorialPictures at: 'dogs'.
Chaser := AnimatedObject new
chaser;
direction: 45;
frames: dogs.
Animator addObject: Chaser.
Chaser animate

The dog will "heel" to your cursor. For even more action, you can start Bouncer up again while walking the dog.

Bouncing and chasing are just two ways an animated object might behave. By extending AnimatedObject, you can implement any animated behavior you can imagine. You might create Repellers and Attractors, for instance. Depending on your needs, interests and programming ability, AnimatedObjects can come alive under your direction.

Before ending this tutorial, remember that global variables and bitmaps have varying amounts of memory assigned to them. You can free the memory assigned to the bitmaps of the animated objects by evaluating the following:

Bouncer frames do: [:each |
each release].
Chaser frames do: [:each |
   each release].

Then, after you have finished exploring the animation in this chapter, evaluate the following:

Animator := Chaser := Bouncer := nil      "get rid of the instance"

This sets these global variables to nil, the undefined object, freeing up memory as you continue.

What You've Now Learned

You are now familiar with:

As always, you can review any of these topics by repeating the corresponding section of the tutorial or by referring to a detailed description in Part 3 of this manual.

If you are going to exit Smalltalk/V before proceeding on to the next tutorial, be sure to save the image.

Previous Page Next Page