Drawing Toolbox Overview
Drawing in MrEd 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 × 300 frame (define frame (instantiateframe%("Drawing Example") (width300) (height300))) ;; Make the drawing area (define canvas (instantiatecanvas%(frame))) ;; Get the canvas's drawing context (define dc (send canvasget-dc)) ;; Make some pens and brushes (define no-pen (instantiatepen%("BLACK" 1 'transparent))) (define no-brush (instantiatebrush%("BLACK" 'transparent))) (define blue-brush (instantiatebrush%("BLUE" 'solid))) (define yellow-brush (instantiatebrush%("YELLOW" 'solid))) (define red-pen (instantiatepen%("RED" 2 'solid))) ;; Define a procedure to draw a face (define (draw-face dc) (send dcset-penno-pen) (send dcset-brushblue-brush) (send dcdraw-ellipse50 50 200 200) (send dcset-brushyellow-brush) (send dcdraw-rectangle100 100 10 10) (send dcdraw-rectangle200 100 10 10) (send dcset-brushno-brush) (send dcset-penred-pen) (let ([pi(atan0 -1)]) (send dcdraw-arc75 75 150 150 (* 5/4pi) (* 7/4pi)))) ;; Show the frame (send frameshow#t) ;; Wait a second to let the window get ready (sleep/yield1) ;; 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
expression queues a show request for the frame, the actual display of
the frame and its canvas requires handling several events. The
show #t)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 × 300 frame (define frame (instantiateframe%("Drawing Example") (width300) (height300))) ;; Make the drawing area with a paint callback (define canvas (instantiatecanvas%(frame) (paint-callback(lambda (canvas dc) (draw-face dc))))) ;; ... pens, brushes, anddraw-faceare the same as above ... ;; Show the frame (send frameshow#t)
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, anddraw-faceare the same as above ... ;; Create a 300 × 300 bitmap (define face-bitmap (instantiatebitmap%(300 300))) ;; Create a drawing context for the bitmap (define bm-dc (instantiatebitmap-dc%(face-bitmap))) ;; A new bitmap's initial content is undefined, so clear it before drawing (send bm-dcclear) ;; Draw the face into the bitmap (draw-face bm-dc) ;; Make a 300 × 300 frame (define frame (instantiateframe%("Drawing Example") (width300) (height300))) ;; Make the drawing area with a paint callback that copies the bitmap (define canvas (instantiatecanvas%(frame) (paint-callback(lambda (canvas dc) (send dcdraw-bitmapface-bitmap 0 0))))) ;; Show the frame (send frameshow#t)
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 (lib "math.ss")) ; for;; Construct paths for a 630 x 630 logo (define left-lambda-path ;; left side of the lambda (let ([p (newpidc-path%)]) (send pmove-to153 44) (send pline-to161.5 60) (send pcurve-to202.5 49 230 42 245 61) (send pcurve-to280.06 105.41 287.5 141 296.5 186) (send pcurve-to301.12 209.08 299.11 223.38 293.96 244) (send pcurve-to281.34 294.54 259.18 331.61 233.5 375) (send pcurve-to198.21 434.63 164.68 505.6 125.5 564) (send pline-to135 572) p)) (define left-logo-path ;; left side of the lambda plus left part of circle (let ([p (newdc-path%)]) (send pappendleft-lambda-path) (send parc0 0 630 630 (* 235/360 2pi) (* 121/360 2pi) #f) p)) (define bottom-lambda-path (let ([p (newdc-path%)]) (send pmove-to135 572) (send pline-to188.5 564) (send pcurve-to208.5 517 230.91 465.21 251 420) (send pcurve-to267 384 278.5 348 296.5 312) (send pcurve-to301.01 302.98 318 258 329 274) (send pcurve-to338.89 288.39 351 314 358 332) (send pcurve-to377.28 381.58 395.57 429.61 414 477) (send pcurve-to428 513 436.5 540 449.5 573) (send pline-to465 580) (send pline-to529 545) p)) (define bottom-logo-path (let ([p (newdc-path%)]) (send pappendbottom-lambda-path) (send parc0 0 630 630 (* 314/360 2pi) (* 235/360 2pi) #f) p)) (define right-lambda-path (let ([p (newdc-path%)]) (send pmove-to153 44) (send pcurve-to192.21 30.69 233.21 14.23 275 20) (send pcurve-to328.6 27.4 350.23 103.08 364 151) (send pcurve-to378.75 202.32 400.5 244 418 294) (send pcurve-to446.56 375.6 494.5 456 530.5 537) (send pline-to529 545) p)) (define right-logo-path (let ([p (newdc-path%)]) (send pappendright-lambda-path) (send parc0 0 630 630 (* 314/360 2pi) (* 121/360 2pi) #t) p)) (define lambda-path ;; the lambda by itself (no circle) (let ([p (newdc-path%)]) (send pappendleft-lambda-path) (send pappendbottom-lambda-path) (let ([t (make-objectdc-path%)]) (send tappendright-lambda-path) (send treverse) (send pappendt)) (send pclose) p)) ;; This function draws the paths with suitable colors: (define (paint-plt dc) ;; Paint white lambda, no outline: (send dcset-pen"BLACK" 0 'transparent) (send dcset-brush"WHITE" 'solid) (send dcdraw-pathlambda-path) ;; Paint outline and colors... (send dcset-pen"BLACK" 0 'solid) ;; Draw red regions (send dcset-brush"RED" 'solid) (send dcdraw-pathleft-logo-path) (send dcdraw-pathbottom-logo-path) ;; Draw blue region (send dcset-brush"BLUE" 'solid) (send dcdraw-pathright-logo-path)) ;; Create a frame to display the logo on a light-purple background: (define f (newframe%[label "PLT Logo"])) (define c (newcanvas%[parent f] [paint-callback (lambda (c dc) (send dcset-background(make-objectcolor%220 200 255)) (send dcclear) (send dcset-smoothing'smoothed) (send dcset-origin5 5) (send dcset-scale0.5 0.5) (paint-plt dc))])) (send cmin-client-width(/ 650 2)) (send cmin-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.