1.2 Drawing
Drawing in PLT Scheme requires a device context (DC), which is an instance of the dc<%> interface. For example, the get-dc method of a canvas returns a dc<%> instance for drawing into the canvas window. Other kinds of DCs draw to different kinds of devices:
bitmap-dc% – a bitmap DC draws to an offscreen bitmap.
post-script-dc% – a PostScript DC records drawing commands to a PostScript file.
printer-dc% – a printer DC draws to a platform-specific printer device (Windows, Mac OS X).
Tools that are used for drawing include the following: pen% objects for drawing lines and shape outlines, brush% objects for filling shapes, bitmap% objects for storing bitmaps, and dc-path% objects for describing paths to draw and fill.
The following example creates a frame with a drawing canvas, and then draws a round, blue face with square, yellow eyes and a smiling, red mouth:
; Make a 300 x 300 frame |
[width 300] |
[height 300])) |
; Make the drawing area |
; Get the canvas's drawing context |
|
; Make some pens and brushes |
(define no-pen (make-object pen% "BLACK" 1 'transparent)) |
(define no-brush (make-object brush% "BLACK" 'transparent)) |
(define blue-brush (make-object brush% "BLUE" 'solid)) |
(define yellow-brush (make-object brush% "YELLOW" 'solid)) |
(define red-pen (make-object pen% "RED" 2 'solid)) |
|
; Define a procedure to draw a face |
(define (draw-face dc) |
(send dc draw-ellipse 50 50 200 200) |
|
(send dc draw-rectangle 100 100 10 10) |
(send dc draw-rectangle 200 100 10 10) |
|
|
; Show the frame |
; Wait a second to let the window get ready |
(sleep/yield 1) |
; Draw the face |
(draw-face dc) |
The sleep/yield call is necessary under X because drawing to the canvas has no effect when the canvas is not shown. Although the (send frame show #t) expression queues a show request for the frame, the actual display of the frame and its canvas requires handling several events. The sleep/yield procedure pauses for a specified number of seconds, handling events while it pauses.
One second is plenty of time for the frame to show itself, but a better solution is to create a canvas with a paint callback function (or overriding on-paint). Using a paint callback function is better for all platforms; when the canvas in the above example is resized or temporarily covered by another window, the face disappears. To ensure that the face is redrawn whenever the canvas itself is repainted, we provide a paint callback when creating the canvas:
; Make a 300 x 300 frame |
[width 300] |
[height 300])) |
|
; Make the drawing area with a paint callback |
(define canvas |
[paint-callback |
(lambda (canvas dc) (draw-face dc))])) |
|
; ... pens, brushes, and draw-face are the same as above ... |
|
; Show the frame |
Suppose that draw-face creates a particularly complex face that takes a long time to draw. We might want to draw the face once into an offscreen bitmap, and then have the paint callback copy the cached bitmap image onto the canvas whenever the canvas is updated. To draw into a bitmap, we first create a bitmap% object, and then we create a bitmap-dc% to direct drawing commands into the bitmap:
; ... pens, brushes, and draw-face are the same as above ... |
|
; Create a 300 x 300 bitmap |
(define face-bitmap (make-object bitmap% 300 300)) |
; Create a drawing context for the bitmap |
(define bm-dc (make-object bitmap-dc% face-bitmap)) |
; A bitmap's initial content is undefined; clear it before drawing |
|
; Draw the face into the bitmap |
(draw-face bm-dc) |
|
; Make a 300 x 300 frame |
[width 300] |
[height 300])) |
|
; Make a drawing area whose paint callback copies the bitmap |
(define canvas |
[paint-callback |
(lambda (canvas dc) |
(send dc draw-bitmap face-bitmap 0 0))])) |
|
; Show the frame |
For all types of DCs, the drawing origin is the top-left corner of the DC. When drawing to a window or bitmap, DC units initially correspond to pixels, but the set-scale method changes the scale. When drawing to a PostScript or printer device, DC units initially correspond to points (1/72 of an inch).
More complex shapes are typically best implemented with paths. The following example uses paths to draw the PLT Scheme logo. It also enables smoothing, so that the logo’s curves are anti-aliased when smoothing is available. (Smoothing is always available under Mac OS X, smoothing is available under Windows XP or when "gdiplus.dll" is installed, and smoothing is available under X when Cairo is installed before MrEd is compiled.)
(require mzlib/math) ; for pi |
|
; Construct paths for a 630 x 630 logo |
|
(define left-lambda-path ; left side of the lambda |
p)) |
|
(define left-logo-path ; left side of the lambda and circle |
p)) |
|
(define bottom-lambda-path |
p)) |
|
(define bottom-logo-path |
p)) |
|
(define right-lambda-path |
p)) |
|
(define right-logo-path |
(send p arc 0 0 630 630 (* 157/180 2 pi) (* 121/360 2 pi) #t) |
p)) |
|
(define lambda-path ; the lambda by itself (no circle) |
(let ([t (make-object dc-path%)]) |
p)) |
|
; This function draws the paths with suitable colors: |
(define (paint-plt dc) |
; Paint white lambda, no outline: |
; Paint outline and colors... |
; Draw red regions |
; Draw blue region |
|
; Create a frame to display the logo on a light-purple background: |
(define c |
[parent f] |
[paint-callback |
(lambda (c dc) |
(send dc set-background (make-object color% 220 200 255)) |
(send dc set-smoothing 'smoothed) |
(send dc set-origin 5 5) |
(paint-plt dc))])) |
(send c min-client-width (/ 650 2)) |
(send c min-client-height (/ 650 2)) |
(send f show #t) |
Drawing effects are not completely portable across platforms or across types of DC. Drawing in smoothed mode tends to produce more reliable and portable results than in unsmoothed mode, and drawing with paths tends to produce more reliable results even in unsmoothed mode. Drawing with a pen of width 0 or 1 in unsmoothed mode in an unscaled DC produces relatively consistent results for all platforms, but a pen width of 2 or drawing to a scaled DC looks significantly different in unsmoothed mode on different platforms and destinations.