parent previous next question (Smalltalk textbook 09)

GraphicsContext

This section describes 'GraphicsContext'. You can not draw an object (for example, a square, a circle or a character) directly on the 'DisplaySurface' of a window. Instead, you must instantiate a 'GraphicsContext' and use that to draw an object. You can attach more than one 'GraphicsContext' to a 'DisplaySurface'.

Notice the class structure of 'DisplaySurface'.

----------------------------------------------------
Object 
    DisplaySurface 
        UnmappableSurface 
            Mask
            Pixmap 
        Window 
            ScheduledWindow 
                ApplicationWindow 
----------------------------------------------------

'ScheduledWindow,' which we've already studied, is also a part of 'DisplaySurface'. 'Pixmap' and 'Mask' will be descried in the next section.

In previous examples, you have seen cases of using 'GraphicsContext' like Program 9-1. We take the time now to explain in more detail how it works.


Program-9-1: (GraphicsContext, ScheduledControllers; displayRectangle, 
waitClickButton, display)
-------------------------------------------------------
| activeWindow graphicsContext |
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
graphicsContext displayRectangle: activeWindow bounds.
activeWindow sensor waitClickButton.
activeWindow display
-------------------------------------------------------

'ScheduledControllers activeController' returns an instance of 'StandardSystemController' which is the current controller, i.e. the controller of the currently selected window. The 'view' message returns an instance of 'ScheduledWindow' which is a companion piece of MVC model. Just inspect 'ScheduledControllers activeController view' to see that it returns a ScheduledWindow. ScheduledWindow inherits 'graphicsContext' from DisplaySurface.

Next, this program sends a 'displayRectangle:' message to the instance of 'GraphicsContext' to paint the active window black. After this, program waits for a mouse click and redraws the window.

Program 9-2 shows how to attach several 'GraphicsContext's to one 'DisplaySurface'.


Program-9-2: (GraphicsContext, ScheduledControllers; paint:, 
displayRectangle, waitClickButton, display, gray, black, timesRepeat:)
--------------------------------------------------------------
| activeWindow graphicsContext1 graphicsContext2  |
activeWindow := ScheduledControllers activeController view.
graphicsContext1 := activeWindow graphicsContext.
graphicsContext2 := activeWindow graphicsContext.
graphicsContext1 paint: ColorValue black.
graphicsContext2 paint: ColorValue gray.
2 timesRepeat:
    [graphicsContext1 displayRectangle: activeWindow bounds.
    activeWindow sensor waitClickButton.
    graphicsContext2 displayRectangle: activeWindow bounds.
    activeWindow sensor waitClickButton].
activeWindow display
--------------------------------------------------------------

Clicking the mouse changes the window color to black, gray, black, gray.


Program-9-3: (GraphicsContext, ScheduledControllers; paint:, 
displayRectangle, waitClickButton, display, gray, black, timesRepeat)
----------------------------------------------------------------
| activeWindow graphicsContext |
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
2 timesRepeat: 
    [graphicsContext paint: ColorValue black.
    graphicsContext displayRectangle: activeWindow bounds.
    activeWindow sensor waitClickButton.
    graphicsContext paint: ColorValue gray.
    graphicsContext displayRectangle: activeWindow bounds.
    activeWindow sensor waitClickButton].
activeWindow display
----------------------------------------------------------------

Let us compare Program 9-2 and 9-3. These appear to act the same. However Program 9-2 uses two 'graphicsContext's but Program 9-3 uses only one. Therefore, Program 9-3 has to specify the color of the rectangle (paint:) in the 'timesRepeat: block-closure.

It is important to understand that 'graphicsContext' has all necessary information (color, lineWidth, capStyle, joinStyle, font, tilingMask etc.) to draw an object on a 'DisplaySurface'.

I recommend that you create a 'graphicsContext' for each object you draw rather than using one 'graphicsContext' for several objects. This means that you should copy and change 'graphicsContext' to draw on the 'DisplaySurface' when you get the 'graphicsContext' as an argument. But do not change the original 'graphicsContext' directly because you will have to restore it yourself.

Let's look more carefully at 'graphicsContext'. The parameters for 'graphicsContext' are as follows. Let's take each in turn: lineWidth ( line width) capStyle (edge style of a line) joinStyle (join style of a line) paint (color) translation (translation of axes) clippingBounds (clipping bounds) font clientData (context parameters dictionary)

Program 9-4 shows how to change the lineWidth. A line gets thicker and thicker as you click the mouse. Notice the line width increases in both segments.


Program-9-4: (GraphicsContext, OrderedCollection; clear, lineWidth:, displayPolyline:)
-------------------------------------------------------------
| pointCollection activeWindow graphicsContext |
pointCollection := OrderedCollection new.
pointCollection add: 100 @ 50.
pointCollection add: 60 @ 90.
pointCollection add: 100 @ 100.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
1 to: 10 by: 2 do:
    [:lineWidth |
    graphicsContext clear.
    graphicsContext lineWidth: lineWidth.
    graphicsContext displayPolyline: pointCollection.
    activeWindow sensor waitClickButton].
activeWindow display
-------------------------------------------------------------

Program 9-5 shows how to change the capStyle. Click the mouse to cycle through various styles.


Program-9-5: (GraphicsContext, OrderedCollection; capStyle:, lineWidth:,
displayPolyline:, capButt, capProjecting, capRound)
--------------------------------------------------------------
| pointCollection activeWindow graphicsContext capStyles |
pointCollection := OrderedCollection new.
pointCollection add: 100 @ 50.
pointCollection add: 60 @ 90.
pointCollection add: 100 @ 100.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
graphicsContext lineWidth: 10.
capStyles := OrderedCollection new.
capStyles add: graphicsContext class capButt.
capStyles add: graphicsContext class capProjecting.
capStyles add: graphicsContext class capRound.
capStyles do:
    [:capStyle |
    graphicsContext clear.
    graphicsContext capStyle: capStyle.
    graphicsContext displayPolyline: pointCollection.
    activeWindow sensor waitClickButton].
activeWindow display
--------------------------------------------------------------

Program 9-6 shows how to change the joinStyle.


Program-9-6: (GraphicsContext, OrderedCollection; joinStyle:, lineWidth:,
displayPolyline:, joinBevel, joinMiter, joinRound)
--------------------------------------------------------------
| pointCollection activeWindow graphicsContext joinStyles |
pointCollection := OrderedCollection new.
pointCollection add: 100 @ 50.
pointCollection add: 60 @ 90.
pointCollection add: 100 @ 100.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
graphicsContext lineWidth: 10.
joinStyles := OrderedCollection new.
joinStyles add: graphicsContext class joinBevel.
joinStyles add: graphicsContext class joinMiter.
joinStyles add: graphicsContext class joinRound.
joinStyles do:
    [:joinStyle |
    graphicsContext clear.
    graphicsContext joinStyle: joinStyle.
    graphicsContext displayPolyline: pointCollection.
    activeWindow sensor waitClickButton].
activeWindow display
--------------------------------------------------------------

Program 9-7 shows how to change the foreground color. As you move the mouse around, lines are drawn from the top-left corner of the window to the current mouse position in various colors. Click to end the demo.


Program-9-7: (GraphicsContext, ColorValue, Random; red, green, blue, 
black, paint:, displayLineFrom:to:)
--------------------------------------------------------------
| activeWindow graphicsContext aSensor colorValues aRandom newPoint oldPoint 
  anIndex |
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
aSensor := activeWindow sensor.
colorValues := OrderedCollection new.
colorValues add: ColorValue red.
colorValues add: ColorValue green.
colorValues add: ColorValue blue.
colorValues add: ColorValue black.
aRandom := Random new.
oldPoint := nil.
[aSensor noButtonPressed] whileTrue: 
    [newPoint := aSensor cursorPoint.
    oldPoint = newPoint ifFalse: 
        [anIndex := (aRandom next * colorValues size) asInteger.
        anIndex := anIndex + 1.
        graphicsContext paint: (colorValues at: anIndex).
        graphicsContext displayLineFrom: Point zero to: newPoint].
    oldPoint := newPoint].
activeWindow display
--------------------------------------------------------------

The 'paint:' message takes an instance of 'ColorValue'. You can also supply a pattern (an instance of 'Pattern') as illustrated in program 9-8. More precisely, it accepts any instance of a subclass of 'Paint'. 'ColorValue' and 'Pattern' are both subclasses of 'Paint'.

Program 9-8 first prompts the user for a rectangle area, then paints the graphicsContext with that pattern and waits for a mouse click.


Program-9-8: (GraphicsContext, ColorValue, Image, Pattern; paint:, 
displayRectangle:, asPattern)
--------------------------------------------------------------
| aPattern activeWindow graphicsContext |
aPattern := Image fromUser asPattern.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
graphicsContext paint: aPattern.
graphicsContext displayRectangle: activeWindow bounds.
activeWindow sensor waitClickButton.
activeWindow display
--------------------------------------------------------------

Next is 'translation'. Program 9-9 prompts the user to select an area of the display and draws it at 'Point zaro' of the window. But by sending the 'translation: 50@50' message, the image is shown offset by 50@50.


Program-9-9: (GraphicsContext, Image; translation:, fromUser)
--------------------------------------------------------------
| anImage activeWindow graphicsContext |
anImage := Image fromUser.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
graphicsContext clear.
graphicsContext translation: 50 @ 50.
graphicsContext displayImage: anImage at: Point zero.
activeWindow sensor waitClickButton.
activeWindow display
--------------------------------------------------------------

Next is 'clippingBounds'. Program 9-10 again prompts for a rectangle (select a pretty large one so that the example works) and draws it in the window like Program 9-9. However, this time it only draws a part of the picture, namely from 50@50 to 150@150.


Program-9-10: (GraphicsContext, Image; translation:, fromUser, 
clippingRectangle:, displayImage:at:)
--------------------------------------------------------------
| anImage activeWindow graphicsContext aRectangle |
anImage := Image fromUser.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
aRectangle := 50 @ 50 extent: 100 @ 100.
graphicsContext clear.
graphicsContext clippingRectangle: aRectangle.
graphicsContext displayImage: anImage at: Point zero.
activeWindow sensor waitClickButton.
activeWindow display
--------------------------------------------------------------

Program 9-11 cycles through various font sizes for the word 'Smalltalk'.


Program-9-11: (GraphicsContext, FontDescription; displayString:at:, 
pixelSize, font)
--------------------------------------------------------------
| aString activeWindow graphicsContext aFont |
aString := 'Smalltalk'.
activeWindow := ScheduledControllers activeController view.
graphicsContext := activeWindow graphicsContext.
#(9 10 12 14 18 20 24 36 48 72) do: [:pixelSize |
    aFont := FontDescription default copy.
    aFont pixelSize: pixelSize.
    graphicsContext font: aFont.
    graphicsContext clear.
    graphicsContext displayString: aString at: 50 @ 100.
    activeWindow sensor waitClickButton].
activeWindow display
--------------------------------------------------------------

Before we end this section, here is a list of messages that draw geometrical figures on a 'DisplaySurface' using a 'GraphicsContext'. The receiver of these messages is an instance of 'GraphicsContext'. Please experiment with these messages.

draw a line
    displayLineFrom:to:
    
draw a rectangle
    displayRectangularBorder:
    displayRectangularBorder:at:
    
fill a rectangle
    displayRectangle:
    displayRectangle:at:
    
draw a plylgon line
    displayPolyline:
    displayPolyline:at:
    
fill a poligon
    displayPolygon:
    displayPolygon:at:
    
draw an arc
    displayArcBoundedBy:startAngle:sweepAngle:
    displayArcBoundedBy:startAngle:sweepAngle:at:
    
fill an arc
    displayWedgeBoundedBy:startAngle:sweepAngle:
    displayWedgeBoundedBy:startAngle:sweepAngle:at:

To draw a picture ('Image') or characters ('String') on a 'DisplaySurface' through a 'GraphicsContext', use the following messages.

Image
    displayImage:at:
    
String
    displayString:at:
    displayString:from:to:at:

Finally I present the message used to draw an instance of an object which is able to show itself on a 'DisplaySurface' through a 'GraphicsContext'. Actually objects that are able to display themselves are either subclasses of 'VisualComponent' or have implemented a 'display' message.

Displayable object
    display:at:


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves