parent previous next question (Smalltalk Textbook 13)

EngiVisualTransporter

Let us study how to move a displayable object on a 'DisplaySurface'. For example, let us see how to move an image (taken from the screen) around the screen following the mouse. The easiest way is to repeatedly display the image and restore the background as the mouse moves around the screen. Examine the following pseudocode:

-----------------------------------------------------
oldPoint := newPoint := get current coordinates of the mouse.
display the image.
[repeat condition]
	whileTrue:
		[newPoint := get current coordinates of the mouse.
		newPoint = oldPoint
			ifFalse:
				[repair background.
				display the image.
				oldPoint := newPoint]].
repair background.
-------------------------------------------------------

Program 13-1 is the actual program. Please pay careful attention to the use of block closures. Notice that the image is only displayed inside the window in which this code was evaluated.


Program-13-1: (Image, GraphicsContext; display:at:, cursorPoint, 
intersect:, clippingBox, value:)
----------------------------------------------------------
| anImage activeWindow aSensor displayContext
  displayBlock restoreBlock oldPoint newPoint |
anImage := Image fromUser.
activeWindow := ScheduledControllers activeController view.
aSensor := activeWindow sensor.
displayContext := activeWindow graphicsContext.
displayBlock :=
	[:aPoint |
		displayContext
			display: anImage
			at: aPoint].
restoreBlock  := 
	[:aPoint | 
		| tempContext clippingBox |
		tempContext := displayContext copy.
		clippingBox := anImage bounds translatedBy: aPoint.
		clippingBox := clippingBox
			intersect: displayContext medium bounds.
		tempContext clippingRectangle: clippingBox.
		tempContext medium displayOn: tempContext].
oldPoint := newPoint := aSensor cursorPoint.
displayBlock value: newPoint.
[aSensor noButtonPressed]
	whileTrue: 
		[newPoint := aSensor cursorPoint.
		newPoint = oldPoint
			ifFalse: 
				[restoreBlock  value: oldPoint.
				displayBlock value: newPoint.
				oldPoint := newPoint]].
restoreBlock  value: oldPoint.
----------------------------------------------------------

Program 13-1 redraws the background of an image whenever the image is moved. The program does not have to save the background image to redraw. Therefore even if several of these display programs are running in parallel, the view will be ok. However, you probably noticed a problem of flicker caused by repeated painting the screen.

To fix the flicker problem, change the program to save the background of an image to redraw it. The pseudocode is as follows. The only change is to save the background before displaying the image.

-----------------------------------------------------
oldPoint := newPoint := get current coordinates of the mouse.
save the background.
dispay the image.
[repeat condition]
	whileTrue:
		[newPoint := get current coordinates of the mouse.
		newPoint = oldPoint
			ifFalse:
				[repair background.
				save the new background.
				display the image.
				oldPoint := newPoint]].
repair background.
-------------------------------------------------------

Program 13-2 is the revised version. This program saves the background of the image as an instance of 'Pixmap' in order to redraw the background quickly. However, if several of these programs are running in parallel, one of them can change the background between the time another program saves the background and restores it. So, Program 13-2 is a simple and economical program suitable only for a single process.


Program-13-2: (Image, Pixmap, GraphicsContext; display:at:, cursorPoint, 
copyArea:from:sourceOffset:destinationOffset:, value:)
-------------------------------------------------------------
| anImage activeWindow aSensor displayContext storePixmap 
  storeContext displayBlock restoreBlock storeBlock
  oldPoint newPoint |
anImage := Image fromUser.
activeWindow := ScheduledControllers activeController view.
aSensor := activeWindow sensor.
displayContext := activeWindow graphicsContext.
storePixmap := Pixmap extent: anImage bounds extent.
storeContext := storePixmap graphicsContext.
displayBlock :=
		[:aPoint |
		displayContext
			display: anImage
			at: aPoint].
restoreBlock :=
		[:aPoint |
		displayContext
			copyArea: storePixmap bounds
			from: storeContext
			sourceOffset: Point zero
			destinationOffset: aPoint].
storeBlock :=
		[:aPoint |
		storeContext
			copyArea: storePixmap bounds
			from: displayContext
			sourceOffset: aPoint
			destinationOffset: Point zero].
oldPoint := newPoint := aSensor cursorPoint.
storeBlock value: newPoint.
displayBlock value: newPoint.
[aSensor noButtonPressed]
	whileTrue: 
		[newPoint := aSensor cursorPoint.
		newPoint = oldPoint
			ifFalse: 
				[restoreBlock value: oldPoint.
				storeBlock value: newPoint.
				displayBlock value: newPoint.
				oldPoint := newPoint]].
restoreBlock value: oldPoint.
storePixmap close.
-------------------------------------------------------------

The flicker problem is now much better than before. Let's try to make further improvements. The new pseudocode is as follows.

-----------------------------------------------------
oldPoint := newPoint := get current coordinates of the mouse.
save background
dispay the image.
[repeat condition]
	whileTrue:
		[newPoint := get current coordinates of the mouse.
		newPoint = oldPoint
			ifFalse:
				[ image display area overlaps previous ?
					ifTrue:
						[repair, save, display 
						to prevent flickering]
					ifFalse:
						[repair background.
						save background.
						display the image.
						oldPoint := newPoint]].
repair background.
-------------------------------------------------------

[repair, save, display to prevent flickering] is a special way to redraw the image as follows.

  1. prepare a work space which is twice as large as the size of the image to display.
  2. redraw and save the background into the work space.
  3. transfer the contents of the work space to the display surface.

You can prevent the flickering by displaying the result of the workspace only if the display area of the image overlaps the previous display area. Program 13-3 shows the revision.


Program-13-3: (Image, Pixmap, GraphicsContext; display:at:, cursorPoint, 
copyArea:from:sourceOffset:destinationOffset:, merge:, value:)
-----------------------------------------------------------------
| anImage activeWindow aSensor displayContext storePixmap
  storeContext workPixmap workContext displayBlock restoreBlock
  storeBlock workBlock oldPoint newPoint newBounds oldBounds |
anImage := Image fromUser.
activeWindow := ScheduledControllers activeController view.
aSensor := activeWindow sensor.
displayContext := activeWindow graphicsContext.
storePixmap := Pixmap extent: anImage bounds extent.
storeContext := storePixmap graphicsContext.
workPixmap := Pixmap extent: anImage extent * 2.
workContext := workPixmap graphicsContext.
displayBlock :=
		[:aPoint |
		displayContext
			display: anImage
			at: aPoint].
restoreBlock :=
		[:aPoint |
		displayContext
			copyArea: storePixmap bounds
			from: storeContext
			sourceOffset: Point zero
			destinationOffset: aPoint].
storeBlock :=
		[:aPoint |
		storeContext
			copyArea: storePixmap bounds
			from: displayContext
			sourceOffset: aPoint
			destinationOffset: Point zero].
workBlock := 
		[:oldBox :newBox | 
		| mergeBox |
		mergeBox := oldBox merge: newBox.
		workContext
			copyArea: (Point zero extent: mergeBox extent)
			from: displayContext
			sourceOffset: mergeBox origin
			destinationOffset: Point zero.
		workContext
			copyArea: storePixmap bounds
			from: storeContext
			sourceOffset: Point zero
			destinationOffset: oldBox origin - mergeBox origin.
		storeContext
			copyArea: storePixmap bounds
			from: workContext
			sourceOffset: newBox origin - mergeBox origin
			destinationOffset: Point zero.
		workContext
			display: anImage
			at: newBox origin - mergeBox origin.
		displayContext
			copyArea: (Point zero extent: mergeBox extent)
			from: workContext
			sourceOffset: Point zero
			destinationOffset: mergeBox origin].
oldPoint := newPoint := aSensor cursorPoint.
storeBlock value: newPoint.
displayBlock value: newPoint.
[aSensor noButtonPressed]
	whileTrue: 
		[newPoint := aSensor cursorPoint.
		newPoint = oldPoint
			ifFalse: 
				[newBounds := newPoint extent: anImage extent.
				oldBounds := oldPoint extent: anImage extent.
				(newBounds intersects: oldBounds)
					ifTrue:
						[workBlock
							value: oldBounds
							value: newBounds]
					ifFalse: 
						[restoreBlock value: oldPoint.
						storeBlock value: newPoint.
						displayBlock value: newPoint].
				oldPoint := newPoint]].
restoreBlock value: oldPoint.
workPixmap close.
storePixmap close.
-----------------------------------------------------------------

I believe you'll notice better movement of the image you select from the screen. If the image does not move smoothly, it means your computer is too slow to run Smalltalk programs and I recommend you get a faster computer!

Before ending this section I present you with a useful class called 'EngiVisualTransporter'. This implements a transportor running on a display surface to carry any object which accepts 'bounds' and 'displayOn:at:' messages. The program is in Appendix 06.

Example 1 of Appendix 06 moves an image with the mouse cursor, like the above examples. Example 2 moves an image from 0@0 to 200@200 at a specified speed. Example 3 moves an image from 0@0 to 200@00 by a specified number of steps. Try to guess how many steps are used to move the image. Example 4 moves an image along the path of a line you draw on the screen. The image moves rather quickly, so draw a long line, perhaps in the shape of a spiral.


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