1#lang racket/base
2
3(require mrlib/image-core
4         "img-err.rkt"
5         racket/match
6         racket/contract
7         racket/class
8         (except-in racket/draw
9                    make-pen make-color)
10         ;(only-in racket/gui/base frame% canvas% slider% horizontal-panel% button%)
11         htdp/error
12         racket/math
13         racket/string
14         (for-syntax racket/base
15                     racket/list)
16         lang/posn
17         net/url)
18(module+ test (require rackunit))
19
20(define 2pi (* 2 pi))
21
22;; for testing
23; (require racket/gui/base)
24#;
25(define (show-image arg [extra-space 0])
26  (letrec ([g (to-img arg)]
27           [f (new frame% [label ""])]
28           [c (new canvas%
29                   [parent f]
30                   [min-width (+ extra-space (image-width g))]
31                   [min-height (+ extra-space (image-height g))]
32                   [paint-callback
33                    (λ (c dc)
34                      (send dc set-smoothing 'aligned)
35                      (let-values ([(w h) (send c get-client-size)])
36                        (let ([scale (send sl get-value)])
37                          (send dc set-scale scale scale)
38                          (render-image
39                           g
40                           dc
41                           (inexact->exact (floor (- (/ w 2 scale) (/ (get-right g) 2))))
42                           (inexact->exact (floor (- (/ h 2 scale) (/ (get-bottom g) 2))))))))])]
43           [min-scale 1]
44           [max-scale 10]
45           [sl (new slider%
46                    [label "Scale factor"]
47                    [parent f]
48                    [min-value min-scale]
49                    [max-value max-scale]
50                    [callback (λ ignore (send c refresh))])]
51           [bp (new horizontal-panel%
52                    [parent f]
53                    [alignment '(center center)]
54                    [stretchable-height #f])]
55           [scale-adjust
56            (λ (f)
57              (send sl set-value
58                    (max min-scale (min max-scale (f (send sl get-value)))))
59              (send c refresh))])
60    (send (new button%
61               [label "√"]
62               [callback (λ x (scale-adjust sub1))]
63               [parent bp]) min-width 100)
64    (send (new button%
65               [label "2"]
66               [callback (λ x (scale-adjust add1))]
67               [parent bp]) min-width 100)
68    (send f show #t)))
69
70;; the obfuscation in the width and height defaults
71;; is so that error checking happens in the right order
72(define/chk (save-image image
73                        filename
74                        [width (if (image? image) (image-width image) 0)]
75                        [height (if (image? image) (image-height image) 0)])
76  (check-dependencies 'save-image
77                      (not (zero? width))
78                      "the width must not be zero, got ~e"
79                      width)
80  (check-dependencies 'save-image
81                      (not (zero? height))
82                      "the width must not be zero, got ~e"
83                      height)
84  (let* ([bm (make-bitmap (inexact->exact (ceiling width))
85                          (inexact->exact (ceiling height)))]
86         [bdc (make-object bitmap-dc% bm)])
87    (send bdc set-smoothing 'aligned)
88    (send bdc erase)
89    (render-image image bdc 0 0)
90    (send bdc set-bitmap #f)
91    (send bm save-file filename 'png)))
92
93(define/chk (save-svg-image image
94                            filename
95                            [width (if (image? image) (image-width image) 0)]
96                            [height (if (image? image) (image-height image) 0)])
97  (call-with-output-file filename
98    (λ (port)
99      (define sdc (new svg-dc% [width width] [height height] [output port]))
100      (send sdc start-doc "")
101      (send sdc start-page)
102      (send sdc set-smoothing 'aligned)
103      (render-image image sdc 0 0)
104      (send sdc end-page)
105      (send sdc end-doc))
106    #:exists 'truncate))
107
108(define (get-right img) (bb-right (send img get-bb)))
109(define (get-bottom img) (bb-bottom (send img get-bb)))
110(define (get-baseline img) (bb-baseline (send img get-bb)))
111
112;
113;
114;
115;                              ;;
116;                              ;;
117;                              ;;
118;    ;;;;  ;;;  ;;;;;;   ;; ;  ;; ;;;;;  ;;;  ;
119;   ;;  ;;  ;;  ;;;; ;;  ;;;;  ;; ;   ;;  ;; ;;
120;  ;;;  ;;  ;;;; ;;;;;;  ;;    ;;   ;;;;  ;;;;
121;  ;;;  ;;  ;;;; ;;      ;;    ;; ;;  ;;   ;;;
122;   ;;  ;;   ;;; ;;;  ;  ;;    ;; ;;  ;;   ;;;
123;    ;;;;    ;;   ;;;;   ;;    ;; ;;;;;;;  ;;
124;                                          ;;
125;                                          ;
126;                                         ;;
127
128
129;; scale : number image -> image
130(define/chk (scale factor image)
131  (scale-internal factor factor image))
132
133(define/chk (scale/xy x-factor y-factor image)
134  (scale-internal x-factor y-factor image))
135
136(define (scale-internal x-factor y-factor image)
137  (let ([ph (send image get-pinhole)])
138    (make-image (make-scale x-factor y-factor (image-shape image))
139                (make-bb (* x-factor (get-right image))
140                         (* y-factor (get-bottom image))
141                         (* y-factor (get-baseline image)))
142                #f
143                (and ph
144                     (make-point (* x-factor (point-x ph))
145                                 (* y-factor (point-y ph)))))))
146
147;; overlay : image image image ... -> image
148;; places images on top of each other with their upper left corners aligned.
149;; last one goes on the bottom
150(define/chk (overlay image image2 . image3)
151  (overlay/internal 'middle 'middle image (cons image2 image3) #t))
152
153;; underlay : image image image ... -> image
154(define/chk (underlay image image2 . image3)
155  (let ([imgs (reverse (list* image image2 image3))])
156    (overlay/internal 'middle 'middle (car imgs) (cdr imgs) #f)))
157
158;; overlay/align : string string image image image ... -> image
159;; the first string has to be one of "center" "middle" "left" or "right" (or symbols)
160;; the second string has to be one of "center" "middle" "top" "bottom" or "baseline" (or symbols)
161;; behaves like overlay, but lines up the images in the various places.
162;; overlay without string arguments is the same as passing "left" and "top"
163;; for the two string arguments. Passing, eg, "center" "center" lines the
164;; images up at their centers.
165
166(define/chk (overlay/align x-place y-place image image2 . image3)
167  (when (or (eq? x-place 'pinhole) (eq? y-place 'pinhole))
168    (check-dependencies
169     'overlay/align
170     (and (send image get-pinhole)
171          (send image2 get-pinhole)
172          (andmap (λ (x) (send x get-pinhole))
173                  image3))
174     "when x-place or y-place is ~e or ~e, then all of the arguments must have pinholes"
175     'pinhole "pinhole"))
176  (overlay/internal x-place y-place image (cons image2 image3) #t))
177
178(define/chk (underlay/align x-place y-place image image2 . image3)
179  (when (or (eq? x-place 'pinhole) (eq? y-place 'pinhole))
180    (check-dependencies
181     'underlay/align
182     (and (send image get-pinhole)
183          (send image2 get-pinhole)
184          (andmap (λ (x) (send x get-pinhole))
185                  image3))
186     "when x-place or y-place is ~e or ~e, then all of the arguments must have pinholes"
187     'pinhole "pinhole"))
188  (let ([imgs (reverse (list* image image2 image3))])
189    (overlay/internal x-place y-place (car imgs) (cdr imgs) #f)))
190
191(define/chk (overlay/pinhole image1 image2 . image3)
192  (overlay/internal 'pinhole 'pinhole
193                    (maybe-center-pinhole image1)
194                    (map maybe-center-pinhole (cons image2 image3))
195                    #t))
196
197(define/chk (underlay/pinhole image1 image2 . image3)
198  (let ([imgs (map maybe-center-pinhole (reverse (list* image1 image2 image3)))])
199    (overlay/internal 'pinhole 'pinhole
200                      (car imgs)
201                      (cdr imgs)
202                      #f)))
203
204(define (maybe-center-pinhole img)
205  (if (send img get-pinhole)
206      img
207      (center-pinhole img)))
208
209(define (overlay/internal x-place y-place fst rst first-pinhole?)
210  (let loop ([fst fst]
211             [rst rst])
212    (cond
213      [(null? rst) fst]
214      [else
215       (let* ([fst-x-spot (find-x-spot x-place fst)]
216              [fst-y-spot (find-y-spot y-place fst)]
217              [snd-x-spot (find-x-spot x-place (car rst))]
218              [snd-y-spot (find-y-spot y-place (car rst))]
219              [dx (- fst-x-spot snd-x-spot)]
220              [dy (- fst-y-spot snd-y-spot)])
221         (loop (overlay/δ fst
222                          (if (< dx 0) (- dx) 0)
223                          (if (< dy 0) (- dy) 0)
224                          (car rst)
225                          (if (< dx 0) 0 dx)
226                          (if (< dy 0) 0 dy)
227                          first-pinhole?
228                          #f)
229               (cdr rst)))])))
230
231(define (find-x-spot x-place image)
232  (case x-place
233    [(left) 0]
234    [(middle) (/ (get-right image) 2)]
235    [(right) (get-right image)]
236    [(pinhole) (point-x (send image get-pinhole))]
237    [else (error 'find-x-spot "~s" x-place)]))
238
239(define (find-y-spot y-place image)
240  (case y-place
241    [(top) 0]
242    [(middle) (/ (get-bottom image) 2)]
243    [(bottom) (get-bottom image)]
244    [(baseline) (get-baseline image)]
245    [(pinhole) (point-y (send image get-pinhole))]
246    [else (error 'find-y-spot "~s" y-place)]))
247
248;; overlay/xy : image number number image -> image
249;; places images on top of each other with their upper-left corners offset by the two numbers
250
251(define/chk (overlay/xy image dx dy image2)
252  (overlay/δ image
253             (if (< dx 0) (- dx) 0)
254             (if (< dy 0) (- dy) 0)
255             image2
256             (if (< dx 0) 0 dx)
257             (if (< dy 0) 0 dy)
258             #t
259             #f))
260
261(define/chk (underlay/xy image dx dy image2)
262  (overlay/δ image2
263             (if (< dx 0) 0 dx)
264             (if (< dy 0) 0 dy)
265             image
266             (if (< dx 0) (- dx) 0)
267             (if (< dy 0) (- dy) 0)
268             #f
269             #f))
270
271(define (overlay/δ image1 dx1 dy1 image2 dx2 dy2 first-pinhole? beside-baseline?)
272    (make-image (make-overlay (make-translate dx1 dy1 (image-shape image1))
273                              (make-translate dx2 dy2 (image-shape image2)))
274                (make-bb (max (+ (get-right image1) dx1)
275                              (+ (get-right image2) dx2))
276                         (max (+ (get-bottom image1) dy1)
277                              (+ (get-bottom image2) dy2))
278                         (if beside-baseline?
279                             (let ([δ1 (- (get-bottom image1) (get-baseline image1))]
280                                   [δ2 (- (get-bottom image2) (get-baseline image2))]
281                                   [b1 (+ (get-baseline image1) dy1)]
282                                   [b2 (+ (get-baseline image2) dy2)])
283                               (cond
284                                 [(= δ1 δ2) (max b1 b2)]
285                                 [(< δ1 δ2) b2]
286                                 [else b1]))
287                             (max (+ (get-baseline image1) dy1)
288                                  (+ (get-baseline image2) dy2))))
289                #f
290                (if first-pinhole?
291                    (let ([ph (send image1 get-pinhole)])
292                      (and ph
293                           (make-point (+ (point-x ph) dx1)
294                                       (+ (point-y ph) dy1))))
295                    (let ([ph (send image2 get-pinhole)])
296                      (and ph
297                           (make-point (+ (point-x ph) dx2)
298                                       (+ (point-y ph) dy2)))))))
299
300;; beside : image image image ... -> image
301;; places images in a single horizontal row, top aligned
302(define/chk (beside image1 image2 . image3)
303  (beside/internal 'middle image1 (cons image2 image3)))
304
305;; beside/align : string image image image ... -> image
306;; places images in a horizontal row where the vertical alignment is
307;; covered by the string argument
308(define/chk (beside/align y-place image1 image2 . image3)
309  (when (eq? y-place 'pinhole)
310    (check-dependencies 'beside/align
311                        (and (send image1 get-pinhole)
312                             (send image2 get-pinhole)
313                             (andmap (λ (x) (send x get-pinhole))
314                                     image3))
315                        "when y-place is ~e or ~e, then all of the arguments must have pinholes"
316                        'pinhole "pinhole"))
317  (beside/internal y-place image1 (cons image2 image3)))
318
319(define (beside/internal y-place fst rst)
320  (let loop ([fst fst]
321             [rst rst])
322    (cond
323      [(null? rst) fst]
324      [else
325       (let* ([snd (car rst)]
326              [fst-y-spot (find-y-spot y-place fst)]
327              [snd-y-spot (find-y-spot y-place (car rst))]
328              [dy (- fst-y-spot snd-y-spot)])
329         (loop (overlay/δ fst
330                          0
331                          (if (< dy 0) (- dy) 0)
332                          (car rst)
333                          (get-right fst)
334                          (if (< dy 0) 0 dy)
335                          #t
336                          #t)
337               (cdr rst)))])))
338
339;; above : image image image ... -> image
340;; places images in a single vertical row, left aligned
341(define/chk (above image1 image2 . image3)
342  (above/internal 'middle image1 (cons image2 image3)))
343
344;; beside/align : string image image image ... -> image
345;; places images in a horizontal row where the vertical alignment is
346;; covered by the string argument
347(define/chk (above/align x-place image1 image2 . image3)
348  (when (eq? x-place 'pinhole)
349    (check-dependencies 'above/align
350                        (and (send image1 get-pinhole)
351                             (send image2 get-pinhole)
352                             (andmap (λ (x) (send x get-pinhole))
353                                     image3))
354                        "when x-place is ~e or ~e, then all of the arguments must have pinholes"
355                        'pinhole "pinhole"))
356  (above/internal x-place image1 (cons image2 image3)))
357
358(define (above/internal x-place fst rst)
359  (let loop ([fst fst]
360             [rst rst])
361    (cond
362      [(null? rst) fst]
363      [else
364       (let* ([snd (car rst)]
365              [fst-x-spot (find-x-spot x-place fst)]
366              [snd-x-spot (find-x-spot x-place (car rst))]
367              [dx (- fst-x-spot snd-x-spot)])
368         (loop (overlay/δ fst
369                          (if (< dx 0) (- dx) 0)
370                          0
371                          (car rst)
372                          (if (< dx 0) 0 dx)
373                          (get-bottom fst)
374                          #t
375                          #f)
376               (cdr rst)))])))
377
378(define/chk (overlay/offset image1 dx dy image2)
379  (overlay/offset/internal 'middle 'middle image1 dx dy image2))
380
381(define/chk (overlay/align/offset x-place y-place image1 dx dy image2)
382  (overlay/offset/internal x-place y-place image1 dx dy image2))
383
384(define/chk (underlay/offset image1 dx dy image2)
385  (overlay/offset/internal 'middle 'middle  image2 (- dx) (- dy) image1))
386
387(define/chk (underlay/align/offset x-place y-place image1 dx dy image2)
388  (overlay/offset/internal x-place y-place image2 (- dx) (- dy) image1))
389
390(define (overlay/offset/internal x-place y-place fst orig-dx orig-dy snd)
391  (let* ([fst-x-spot (find-x-spot x-place fst)]
392         [fst-y-spot (find-y-spot y-place fst)]
393         [snd-x-spot (find-x-spot x-place snd)]
394         [snd-y-spot (find-y-spot y-place snd)]
395         [dx (+ (- fst-x-spot snd-x-spot) orig-dx)]
396         [dy (+ (- fst-y-spot snd-y-spot) orig-dy)])
397    (overlay/δ fst
398               (if (< dx 0) (- dx) 0)
399               (if (< dy 0) (- dy) 0)
400               snd
401               (if (< dx 0) 0 dx)
402               (if (< dy 0) 0 dy)
403               #t
404               #f)))
405
406
407;
408;
409;
410;                                 ;;;;        ;;                        ;
411;                                 ;;;;        ;;                       ;;
412;    ;;;;;   ;;;;   ;;;;;;; ;;;;  ;;;;;;;        ;;;; ;;;  ;;;;;;;   ;;;;;   ;;;;   ;;; ;;;  ;;;;;
413;   ;;;;;;  ;;;;;;  ;;;;;;;;;;;;; ;;;;;;;;  ;;;; ;;;;;;;;; ;;;;;;;; ;;;;;;  ;;;;;;  ;;;;;;; ;;;;;;
414;  ;;;;;;; ;;;;;;;; ;;;; ;;; ;;;; ;;;;;;;;; ;;;; ;;;; ;;;;     ;;;;  ;;;;  ;;;;;;;; ;;;; ;; ;;;;
415;  ;;;;    ;;;; ;;; ;;;; ;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;;  ;;;;;;;  ;;;;  ;;;; ;;; ;;;;     ;;;;
416;  ;;;;;;; ;;;;;;;; ;;;; ;;; ;;;; ;;;;;;;;; ;;;; ;;;; ;;;; ;;  ;;;;  ;;;;; ;;;;;;;; ;;;;      ;;;;
417;   ;;;;;;  ;;;;;;  ;;;; ;;; ;;;; ;;;;;;;;  ;;;; ;;;; ;;;; ;;;;;;;;  ;;;;;  ;;;;;;  ;;;;    ;;;;;;
418;    ;;;;;   ;;;;   ;;;; ;;; ;;;; ;;;;;;;   ;;;; ;;;; ;;;;  ;; ;;;;   ;;;;   ;;;;   ;;;;    ;;;;;
419;
420;
421;
422
423
424;; crop : number number number number image -> image
425;; crops an image to be w x h from (x,y)
426(define/chk (crop x1 y1 width height image)
427  (crop/internal x1 y1 width height image))
428
429(define/chk (crop/align x-place y-place width height image)
430  (define x-spot (find-x-spot x-place image))
431  (define y-spot (find-y-spot y-place image))
432  (define crop-rec (rectangle width height "solid" "black"))
433  (define w-off (find-x-spot x-place crop-rec))
434  (define h-off (find-y-spot y-place crop-rec))
435  (crop/internal (- x-spot w-off)
436                 (- y-spot h-off)
437                 width height image))
438
439(define (crop/internal x1 y1 width height image)
440  (let ([points (rectangle-points width height)]
441        [ph (send image get-pinhole)])
442    (make-image (make-crop points
443                           (make-translate (- x1) (- y1) (image-shape image)))
444                (make-bb width
445                         height
446                         (min height (get-baseline image)))
447                #f
448                (and ph
449                     (make-point (- (point-x ph) x1)
450                                 (- (point-y ph) y1))))))
451
452;; place-image : image x y scene -> scene
453(define/chk (place-image image1 x1 y1 image2)
454  (place-image/internal image1 x1 y1 image2 'middle 'middle))
455(define/chk (place-image/align image1 x1 y1 x-place y-place image2)
456  (when (or (eq? x-place 'pinhole) (eq? y-place 'pinhole))
457    (check-dependencies
458     'place-image/align
459     (send image1 get-pinhole)
460     "when x-place or y-place is ~e or ~e, the the first image argument must have a pinhole"
461     'pinhole "pinhole"))
462  (place-image/internal image1 x1 y1 image2 x-place y-place))
463(define/chk (place-images images zero-or-more-posns image2)
464  (check-place-images-dependency 'place-images images zero-or-more-posns)
465  (for/fold ([image2 image2]) ([image1 (in-list (reverse images))]
466                               [posn (in-list (reverse zero-or-more-posns))])
467    (place-image/internal
468     image1 (posn-x posn) (posn-y posn) image2 'middle 'middle)))
469(define/chk (place-images/align images zero-or-more-posns x-place y-place image2)
470  (check-place-images-dependency 'place-images/align images zero-or-more-posns)
471  (for/fold ([image2 image2]) ([image1 (in-list (reverse images))]
472                               [posn (in-list (reverse zero-or-more-posns))])
473    (place-image/internal
474     image1 (posn-x posn) (posn-y posn) image2 x-place y-place)))
475
476(define/chk (put-image image1 x1 y1 image2)
477  (place-image/internal image1 x1 (- (image-height image2) y1) image2
478                        'middle 'middle))
479
480(define (check-place-images-dependency who images zero-or-more-posns)
481  (check-dependencies who
482                      (= (length images) (length zero-or-more-posns))
483                      "expected images and posns arguments to have the same length"))
484
485(define (place-image/internal image orig-dx orig-dy scene x-place y-place)
486  (let ([dx (- orig-dx (find-x-spot x-place image))]
487        [dy (- orig-dy (find-y-spot y-place image))])
488    (crop/internal
489     (if (< dx 0) (- dx) 0)
490     (if (< dy 0) (- dy) 0)
491     (get-right scene)
492     (get-bottom scene)
493     (overlay/δ image
494                (if (< dx 0) 0 dx)
495                (if (< dy 0) 0 dy)
496                scene
497                (if (< dx 0) (- dx) 0)
498                (if (< dy 0) (- dy) 0)
499                #f
500                #f))))
501
502(define/chk (scene+line image x1 y1 x2 y2 color)
503  (make-image (make-overlay
504               (make-crop (rectangle-points (get-right image) (get-bottom image))
505                          (make-line-segment (make-point x1 y1) (make-point x2 y2) color))
506               (image-shape image))
507              (image-bb image)
508              #f
509              (send image get-pinhole)))
510
511(define/chk (scene+polygon image posns-or-pulled-points mode color)
512  (check-mode/color-combination 'scene+polygon 4 mode color)
513  (make-image (make-overlay
514               (make-crop (rectangle-points (get-right image) (get-bottom image))
515                          (make-polygon (map (λ (p)
516                                               (cond
517                                                 [(posn? p)
518                                                  (make-point (posn-x p) (posn-y p))]
519                                                 [else p]))
520                                             posns-or-pulled-points)
521                                        mode color))
522               (image-shape image))
523              (image-bb image)
524              #f
525              (send image get-pinhole)))
526
527
528(define/chk (scene+curve image x1 y1 angle1 pull1 x2 y2 angle2 pull2 color)
529  (make-image (make-overlay
530               (make-crop (rectangle-points (get-right image) (get-bottom image))
531                          (make-curve-segment (make-point x1 y1) angle1 pull1
532                                              (make-point x2 y2) angle2 pull2
533                                              'outline color))
534               (image-shape image))
535              (image-bb image)
536              #f
537              (send image get-pinhole)))
538
539;; frame : image -> image
540;; draws a black frame around a image where the bounding box is
541;; (useful for debugging images)
542
543(define/chk (frame image) (real-color-frame "black" image))
544(define/chk (color-frame color image) (real-color-frame color image))
545
546(define (real-color-frame color image)
547  (make-image (make-overlay (image-shape
548                             (crop 0 0
549                                   (get-right image)
550                                   (get-bottom image)
551                                   (rectangle (get-right image)
552                                              (get-bottom image)
553                                              'outline
554                                              (pen color 2 'solid 'round 'round))))
555                            (image-shape image))
556              (make-bb (get-right image)
557                       (get-bottom image)
558                       (get-baseline image))
559              #f
560              (send image get-pinhole)))
561
562;; scale : I number -> I
563;; scales the I by the given factor
564
565;; rotate : number I -> I
566;; rotates the I around the top-left corner by the given angle (in degrees)
567(define/chk (rotate angle image)
568  (let* ([rotated-shape (rotate-normalized-shape
569                         angle
570                         (send image get-normalized-shape))]
571         [ltrb (normalized-shape-bb rotated-shape)]
572         [ph (send image get-pinhole)])
573    (make-image (make-translate (- (ltrb-left ltrb)) (- (ltrb-top ltrb)) rotated-shape)
574                (make-bb (- (ltrb-right ltrb) (ltrb-left ltrb))
575                         (- (ltrb-bottom ltrb) (ltrb-top ltrb))
576                         (- (ltrb-bottom ltrb) (ltrb-top ltrb)))
577                #f
578                (and ph
579                     (let ([rp (rotate-point ph angle)])
580                       (make-point (- (point-x rp) (ltrb-left ltrb))
581                                   (- (point-y rp) (ltrb-top ltrb))))))))
582
583(define/contract (rotate-normalized-shape angle shape)
584  (-> number? normalized-shape? normalized-shape?)
585  (cond
586    [(overlay? shape)
587     (let ([top-shape (rotate-normalized-shape angle (overlay-top shape))]
588           [bottom-shape (rotate-cn-or-simple-shape angle (overlay-bottom shape))])
589       (make-overlay top-shape bottom-shape))]
590    [else
591     (rotate-cn-or-simple-shape angle shape)]))
592
593(define/contract (rotate-cn-or-simple-shape angle shape)
594  (-> number? cn-or-simple-shape? cn-or-simple-shape?)
595  (cond
596    [(crop? shape)
597     (make-crop (rotate-points (crop-points shape) angle)
598                (rotate-normalized-shape angle (crop-shape shape)))]
599    [else
600     (rotate-simple angle shape)]))
601
602;; rotate-simple : angle simple-shape -> simple-shape
603(define/contract (rotate-simple θ simple-shape)
604  (-> number? simple-shape? simple-shape?)
605  (cond
606    [(line-segment? simple-shape)
607     (make-line-segment (rotate-point (line-segment-start simple-shape)
608                                      θ)
609                        (rotate-point (line-segment-end simple-shape)
610                                      θ)
611                        (line-segment-color simple-shape))]
612    [(curve-segment? simple-shape)
613     (make-curve-segment (rotate-point (curve-segment-start simple-shape)
614                                       θ)
615                         (bring-between (+ (curve-segment-s-angle simple-shape) θ) 360)
616                         (curve-segment-s-pull simple-shape)
617                         (rotate-point (curve-segment-end simple-shape)
618                                       θ)
619                         (bring-between (+ (curve-segment-e-angle simple-shape) θ) 360)
620                         (curve-segment-e-pull simple-shape)
621                         (curve-segment-mode simple-shape)
622                         (curve-segment-color simple-shape))]
623    [(polygon? simple-shape)
624     (make-polygon (rotate-points (polygon-points simple-shape) θ)
625                   (polygon-mode simple-shape)
626                   (polygon-color simple-shape))]
627    [else
628     (let* ([unrotated (translate-shape simple-shape)]
629            [rotated (rotate-atomic θ unrotated)])
630       (let-values ([(dx dy)
631                     (c->xy (* (degrees->complex θ)
632                               (xy->c (translate-dx simple-shape)
633                                      (translate-dy simple-shape))))])
634         (make-translate dx dy rotated)))]))
635
636(struct ltrb (left top right bottom) #:transparent)
637(define (union-ltrb ltrb1 ltrb2)
638  (ltrb (min (ltrb-left ltrb1) (ltrb-left ltrb2))
639        (min (ltrb-top ltrb1) (ltrb-top ltrb2))
640        (max (ltrb-right ltrb1) (ltrb-right ltrb2))
641        (max (ltrb-bottom ltrb1) (ltrb-bottom ltrb2))))
642
643;; only intersection if they already overlap.
644(define (intersect-ltrb ltrb1 ltrb2)
645  (ltrb (max (ltrb-left ltrb1) (ltrb-left ltrb2))
646        (max (ltrb-top ltrb1) (ltrb-top ltrb2))
647        (min (ltrb-right ltrb1) (ltrb-right ltrb2))
648        (min (ltrb-bottom ltrb1) (ltrb-bottom ltrb2))))
649
650(define/contract (normalized-shape-bb shape)
651  (-> normalized-shape? ltrb?)
652  (cond
653    [(overlay? shape)
654     (let ([top-ltrb (normalized-shape-bb (overlay-top shape))]
655           [bottom-ltrb (cn-or-simple-shape-bb (overlay-bottom shape))])
656       (union-ltrb top-ltrb bottom-ltrb))]
657    [else
658     (cn-or-simple-shape-bb shape)]))
659
660(define/contract (cn-or-simple-shape-bb shape)
661  (-> cn-or-simple-shape? ltrb?)
662  (cond
663    [(crop? shape)
664     (let ([ltrb (normalized-shape-bb (crop-shape shape))]
665           [crop-ltrb (points->ltrb (crop-points shape))])
666       (intersect-ltrb crop-ltrb ltrb))]
667    [else
668     (simple-bb shape)]))
669
670;; simple-bb : simple-shape -> ltrb
671;; returns the bounding box of 'shape'
672;; (only called for rotated shapes, so bottom=baseline)
673(define/contract (simple-bb simple-shape)
674  (-> simple-shape? ltrb?)
675  (cond
676    [(line-segment? simple-shape)
677     (let ([x1 (point-x (line-segment-start simple-shape))]
678           [y1 (point-y (line-segment-start simple-shape))]
679           [x2 (point-x (line-segment-end simple-shape))]
680           [y2 (point-y (line-segment-end simple-shape))])
681       (ltrb (min x1 x2)
682             (min y1 y2)
683             (+ (max x1 x2) 1)
684             (+ (max y1 y2) 1)))]
685    [(curve-segment? simple-shape)
686     (let ([x1 (point-x (curve-segment-start simple-shape))]
687           [y1 (point-y (curve-segment-start simple-shape))]
688           [x2 (point-x (curve-segment-end simple-shape))]
689           [y2 (point-y (curve-segment-end simple-shape))])
690       (ltrb (min x1 x2)
691             (min y1 y2)
692             (+ (max x1 x2) 1)
693             (+ (max y1 y2) 1)))]
694    [(polygon? simple-shape)
695     (points->ltrb (polygon-points simple-shape))]
696    [else
697     (let ([dx (translate-dx simple-shape)]
698           [dy (translate-dy simple-shape)])
699       (let-values ([(l t r b) (np-atomic-bb (translate-shape simple-shape))])
700         (ltrb (+ l dx)
701               (+ t dy)
702               (+ r dx)
703               (+ b dy))))]))
704
705(define (points->ltrb points)
706  (let-values ([(left top right bottom) (points->ltrb-values points)])
707    (ltrb left top right bottom)))
708
709(define/contract (np-atomic-bb atomic-shape)
710  (-> np-atomic-shape? (values number? number? number? number?))
711  (cond
712    [(ellipse? atomic-shape)
713     (define ew (ellipse-width atomic-shape))
714     (define eh (ellipse-height atomic-shape))
715     (define wedge (ellipse-wedge atomic-shape))
716     (define θ (degrees->radians (ellipse-angle atomic-shape)))
717     (cond
718       [wedge
719        ;; this code assumes that ew and eh are never zero,
720        ;; something that should be guaranteed by the way
721        ;; ellipses are constructed
722        (define (point-on-ellipse t)
723          (list (ellipse-t->x ew eh θ t)
724                (ellipse-t->y ew eh θ t)))
725        (define (in-range-point-on-ellipse t)
726          (and (is-between? 0 t wedge-θ)
727               (point-on-ellipse t)))
728        (define out-θ-x (ellipse-outermost-point-x ew eh θ))
729        (define out-θ-y (ellipse-outermost-point-y ew eh θ))
730        (define wedge-θ (degrees->radians wedge))
731        (define points-to-consider
732          (combine-points-to-consider
733           (list 0 0)                 ;; center point
734           (point-on-ellipse wedge-θ) ;; end of wedge
735           (point-on-ellipse (degrees->radians 0)) ;; start of wedge
736
737           ;; widest points on the ellipse (but only if they are in range)
738           (in-range-point-on-ellipse out-θ-x)
739           (in-range-point-on-ellipse out-θ-y)
740           (in-range-point-on-ellipse (+ pi out-θ-x))
741           (in-range-point-on-ellipse (+ pi out-θ-y))
742           ))
743        (define xs (map car points-to-consider))
744        (define ys (map cadr points-to-consider))
745        (define l (apply min xs))
746        (define r (apply max xs))
747        (define t (apply min ys))
748        (define b (apply max ys))
749        (values l t r b)]
750       [else
751        (define-values (w h)
752          (ellipse-rotated-size ew eh θ))
753
754        (values (- (/ w 2))
755                (- (/ h 2))
756                (/ w 2)
757                (/ h 2))])]
758    [(text? atomic-shape)
759     (let-values ([(w h a d) (send text-sizing-bm get-text-extent
760                                   (text-string atomic-shape)
761                                   (text->font atomic-shape))])
762       (rotated-rectangular-bounding-box w h (text-angle atomic-shape)))]
763    [(flip? atomic-shape)
764     (define bitmap (flip-shape atomic-shape))
765     (define bb (ibitmap-raw-bitmap bitmap))
766     (define-values (l t r b)
767       (rotated-rectangular-bounding-box (* (send bb get-width) (ibitmap-x-scale bitmap))
768                                         (* (send bb get-height) (ibitmap-y-scale bitmap))
769                                         (ibitmap-angle bitmap)))
770     (values l t r b)]
771    [else
772     (eprintf "using bad bounding box for ~s\n" atomic-shape)
773     (values 0 0 100 100)]))
774
775;; returns #true if θ2 is between θ1 and θ3
776;; θ1 =/= θ3 (as angles)
777(define (is-between? θ1 θ2 θ3)
778  (define (bring-in-range θ)
779    (cond
780      [(and (<= 0 θ) (< θ 2pi)) θ]
781      [(< θ 0)
782       (bring-in-range (+ θ 2pi))]
783      [else
784       (bring-in-range (- θ 2pi))]))
785  (define θ1r (bring-in-range θ1))
786  (define θ2r (bring-in-range θ2))
787  (define θ3r (bring-in-range θ3))
788  (cond
789    [(<= θ1r θ3r) (<= θ1r θ2r θ3r)]
790    [else (not (<= θ3r θ2r θ1r))]))
791(module+ test
792  (check-equal? (is-between? 0 pi (* 3/2 pi)) #t)
793  (check-equal? (is-between? 0 pi (* 1/2 pi)) #f)
794  (check-equal? (is-between? (* 1/6 pi) (* 3/6 pi) (* 5/6 pi)) #t)
795  (check-equal? (is-between? (* 1/6 pi) (* 7/6 pi) (* 5/6 pi)) #f)
796  (check-equal? (is-between? (* 5/6 pi) (* 3/6 pi) (* 1/6 pi)) #f)
797  (check-equal? (is-between? (* 5/6 pi) (* 7/6 pi) (* 1/6 pi)) #t)
798  (check-equal? (is-between? (* 1/2 pi) pi 2pi) #t)
799  (check-equal? (is-between? (* 1/2 pi) (* 1/4 pi) 2pi) #f))
800
801;; combine-points-to-consider : (or/c #f (cons/c number? number?)) -> (listof (cons/c number? number?))
802(define (combine-points-to-consider . xs)
803  (for/fold ([l '()]) ([x (in-list xs)])
804    (if x (cons x l) l)))
805
806(define (rotated-rectangular-bounding-box w h θ)
807  (let*-values ([(ax ay) (rotate-xy (- (/ w 2)) (- (/ h 2)) θ)]
808                [(bx by) (rotate-xy (- (/ w 2)) (/ h 2) θ)]
809                [(cx cy) (rotate-xy (/ w 2) (- (/ h 2)) θ)]
810                [(dx dy) (rotate-xy (/ w 2) (/ h 2) θ)])
811    (values (min ax bx cx dx)
812            (min ay by cy dy)
813            (max ax bx cx dx)
814            (max ay by cy dy))))
815
816(define (center-point np-atomic-shape)
817  (let-values ([(l t r b) (np-atomic-bb np-atomic-shape)])
818    (xy->c (/ (- r l) 2)
819           (/ (- b t) 2))))
820
821;; rotate-atomic : angle np-atomic-shape -> np-atomic-shape
822(define (rotate-atomic θ atomic-shape)
823  (-> number? np-atomic-shape? np-atomic-shape?)
824  (cond
825    [(ellipse? atomic-shape)
826     (cond
827       [(ellipse-wedge atomic-shape)
828        ;; we don't have the symmetry in the case below when we are a wedge
829        (define new-angle (bring-between (+ θ (ellipse-angle atomic-shape)) 360))
830        (make-ellipse (ellipse-width atomic-shape)
831                      (ellipse-height atomic-shape)
832                      new-angle
833                      (ellipse-mode atomic-shape)
834                      (ellipse-color atomic-shape)
835                      (ellipse-wedge atomic-shape))]
836       [else
837        (define new-angle (bring-between (+ θ (ellipse-angle atomic-shape)) 180))
838        (cond
839          [(= (ellipse-width atomic-shape)
840              (ellipse-height atomic-shape))
841           atomic-shape]
842          [(< new-angle 90)
843           (make-ellipse (ellipse-width atomic-shape)
844                         (ellipse-height atomic-shape)
845                         new-angle
846                         (ellipse-mode atomic-shape)
847                         (ellipse-color atomic-shape)
848                         (ellipse-wedge atomic-shape))]
849          [else
850           (make-ellipse (ellipse-height atomic-shape)
851                         (ellipse-width atomic-shape)
852                         (- new-angle 90)
853                         (ellipse-mode atomic-shape)
854                         (ellipse-color atomic-shape)
855                         (ellipse-wedge atomic-shape))])])]
856    [(text? atomic-shape)
857     (make-text (text-string atomic-shape)
858                (bring-between (+ θ (text-angle atomic-shape)) 360)
859                (text-y-scale atomic-shape)
860                (text-color atomic-shape)
861                (text-size  atomic-shape)
862                (text-face  atomic-shape)
863                (text-family atomic-shape)
864                (text-style atomic-shape)
865                (text-weight  atomic-shape)
866                (text-underline atomic-shape))]
867    [(flip? atomic-shape)
868     (let ([bitmap (flip-shape atomic-shape)]
869           [flipped? (flip-flipped? atomic-shape)])
870       (make-flip flipped?
871                  (make-ibitmap (ibitmap-raw-bitmap bitmap)
872                                (bring-between
873                                 (+ (ibitmap-angle bitmap) θ)
874                                 360)
875                                (ibitmap-x-scale bitmap)
876                                (ibitmap-y-scale bitmap)
877                                (make-hash))))]))
878
879;; rotate-point : point angle -> point
880(define (rotate-point p θ)
881  (let-values ([(x y) (rotate-xy (point-x p) (point-y p) θ)])
882    (make-point x y)))
883
884;; rotate-xy : x,y angle -> x,y
885(define (rotate-xy x y θ)
886  (c->xy (rotate-c (xy->c x y) θ)))
887
888;; bring-between : rational integer -> rational
889;; returns a number that is much like the modulo of 'x' and 'upper-bound',
890;; since modulo only works on integers
891(define (bring-between x upper-bound)
892  (let* ([x-floor (floor x)]
893         [fraction (- x x-floor)])
894    (+ (modulo x-floor upper-bound)
895       fraction)))
896(module+ test
897  (check-equal? (bring-between 30 180) 30)
898  (check-equal? (bring-between 210 180) 30)
899  (check-equal? (bring-between 30.5 180) 30.5)
900  (check-equal? (bring-between 210.5 180) 30.5)
901  (check-equal? (bring-between 180.5 180) .5)
902  (check-equal? (bring-between 179.9 180) 179.9))
903
904
905(define/chk (flip-horizontal image)
906  (rotate 90 (flip-vertical (rotate -90 image))))
907
908(define/chk (flip-vertical image)
909  (let* ([flipped-shape (flip-normalized-shape
910                         (send image get-normalized-shape))]
911         [ltrb (normalized-shape-bb flipped-shape)]
912         [ph (send image get-pinhole)])
913    (make-image (make-translate (- (ltrb-left ltrb)) (- (ltrb-top ltrb)) flipped-shape)
914                (make-bb (- (ltrb-right ltrb) (ltrb-left ltrb))
915                         (- (ltrb-bottom ltrb) (ltrb-top ltrb))
916                         (- (ltrb-bottom ltrb) (ltrb-top ltrb)))
917                #f
918                (and ph
919                     (make-point (+ (point-x ph) (- (ltrb-left ltrb)))
920                                 (+ (- (point-y ph)) (- (ltrb-top ltrb))))))))
921
922(define/contract (flip-normalized-shape shape)
923  (-> normalized-shape? normalized-shape?)
924  (cond
925    [(overlay? shape)
926     (let ([top-shape (flip-normalized-shape (overlay-top shape))]
927           [bottom-shape (flip-cn-or-simple-shape (overlay-bottom shape))])
928       (make-overlay top-shape bottom-shape))]
929    [else
930     (flip-cn-or-simple-shape shape)]))
931
932(define/contract (flip-cn-or-simple-shape shape)
933  (-> cn-or-simple-shape? cn-or-simple-shape?)
934  (cond
935    [(crop? shape)
936     (make-crop (flip-points (crop-points shape))
937                (flip-normalized-shape (crop-shape shape)))]
938    [else
939     (flip-simple shape)]))
940
941(define/contract (flip-simple simple-shape)
942  (-> simple-shape? simple-shape?)
943  (cond
944    [(line-segment? simple-shape)
945     (make-line-segment (flip-point (line-segment-start simple-shape))
946                        (flip-point (line-segment-end simple-shape))
947                        (line-segment-color simple-shape))]
948    [(curve-segment? simple-shape)
949     (make-curve-segment (flip-point (curve-segment-start simple-shape))
950                         (bring-between (- (curve-segment-s-angle simple-shape)) 360)
951                         (curve-segment-s-pull simple-shape)
952                         (flip-point (curve-segment-end simple-shape))
953                         (bring-between (- (curve-segment-e-angle simple-shape)) 360)
954                         (curve-segment-e-pull simple-shape)
955                         (curve-segment-mode simple-shape)
956                         (curve-segment-color simple-shape))]
957    [(polygon? simple-shape)
958     (make-polygon (flip-points (polygon-points simple-shape))
959                   (polygon-mode simple-shape)
960                   (polygon-color simple-shape))]
961    [else
962     (make-translate (translate-dx simple-shape)
963                     (- (translate-dy simple-shape))
964                     (flip-atomic (translate-shape simple-shape)))]))
965
966(define/contract (flip-atomic atomic-shape)
967  (-> np-atomic-shape? np-atomic-shape?)
968  (cond
969    [(ellipse? atomic-shape)
970     (define wedge (ellipse-wedge atomic-shape))
971     (cond
972       [(and (= (ellipse-width atomic-shape)
973                (ellipse-height atomic-shape))
974             (not wedge))
975        atomic-shape]
976       [wedge
977        (define angle (ellipse-angle atomic-shape))
978        (make-ellipse (ellipse-width atomic-shape)
979                      (ellipse-height atomic-shape)
980                      (bring-between (- 0 wedge angle) 360)
981                      (ellipse-mode atomic-shape)
982                      (ellipse-color atomic-shape)
983                      (ellipse-wedge atomic-shape))]
984       [else
985        (let ([new-angle (bring-between (- 180 (ellipse-angle atomic-shape)) 180)])
986          (cond
987            [(< new-angle 90)
988             (make-ellipse (ellipse-width atomic-shape)
989                           (ellipse-height atomic-shape)
990                           new-angle
991                           (ellipse-mode atomic-shape)
992                           (ellipse-color atomic-shape)
993                           (ellipse-wedge atomic-shape))]
994            [else
995             (make-ellipse (ellipse-height atomic-shape)
996                           (ellipse-width atomic-shape)
997                           (- new-angle 90)
998                           (ellipse-mode atomic-shape)
999                           (ellipse-color atomic-shape)
1000                           (ellipse-wedge atomic-shape))]))])]
1001    [(text? atomic-shape)
1002     (error 'flip "cannot flip shapes that contain text")]
1003    [(flip? atomic-shape)
1004     (define bitmap (flip-shape atomic-shape))
1005     (make-flip (not (flip-flipped? atomic-shape))
1006                (make-ibitmap (ibitmap-raw-bitmap bitmap)
1007                              (bring-between (- (ibitmap-angle bitmap)) 360)
1008                              (ibitmap-x-scale bitmap)
1009                              (ibitmap-y-scale bitmap)
1010                              (make-hash)))]))
1011
1012(define (flip-point point)
1013  (cond
1014    [(pulled-point? point)
1015     (make-pulled-point
1016      (pulled-point-lpull point)
1017      (bring-between (- (pulled-point-langle point)) 360)
1018      (pulled-point-x point)
1019      (- (pulled-point-y point))
1020      (pulled-point-rpull point)
1021      (bring-between (- (pulled-point-rangle point)) 360))]
1022    [else
1023     (make-point (point-x point) (- (point-y point)))]))
1024(define (flip-points points) (map flip-point points))
1025;
1026;
1027;
1028;  ;;;;                        ;;               ;;
1029;  ;;;;                        ;;               ;;
1030;  ;;;;;;;   ;;;;;;;   ;;;;;        ;;;;;          ;;;;;;; ;;;;  ;;;;;;;   ;;;;;;;   ;;;    ;;;;;
1031;  ;;;;;;;;  ;;;;;;;; ;;;;;; ;;;;  ;;;;;;     ;;;; ;;;;;;;;;;;;; ;;;;;;;; ;;;;;;;;  ;;;;;  ;;;;;;
1032;  ;;;;;;;;;     ;;;; ;;;;   ;;;; ;;;;;;;     ;;;; ;;;; ;;; ;;;;     ;;;; ;;; ;;;; ;;;; ;; ;;;;
1033;  ;;;; ;;;;  ;;;;;;;  ;;;;  ;;;; ;;;;        ;;;; ;;;; ;;; ;;;;  ;;;;;;; ;;;;;;;; ;;;;;;;  ;;;;
1034;  ;;;;;;;;; ;;  ;;;;   ;;;; ;;;; ;;;;;;;     ;;;; ;;;; ;;; ;;;; ;;  ;;;;  ;;;;;;; ;;;;;     ;;;;
1035;  ;;;;;;;;  ;;;;;;;; ;;;;;; ;;;;  ;;;;;;     ;;;; ;;;; ;;; ;;;; ;;;;;;;; ;   ;;;;  ;;;;;; ;;;;;;
1036;  ;;;;;;;    ;; ;;;; ;;;;;  ;;;;   ;;;;;     ;;;; ;;;; ;;; ;;;;  ;; ;;;; ;;;;;;;;   ;;;;  ;;;;;
1037;                                                                         ;;;;;;;;
1038;                                                                          ;;;;;;
1039;
1040
1041
1042(define/chk (polygon posns-or-pulled-points mode color)
1043  (check-mode/color-combination 'polygon 3 mode color)
1044  (make-a-polygon (for/list ([p (in-list posns-or-pulled-points)])
1045                    (if (posn? p)
1046                        (make-point (posn-x p) (posn-y p))
1047                        p))
1048                  mode
1049                  color))
1050
1051(define/chk (rectangle width height mode color)
1052  (check-mode/color-combination 'rectangle 4 mode color)
1053  (make-a-polygon (rectangle-points width height) mode color))
1054
1055(define/chk (square side-length mode color)
1056  (check-mode/color-combination 'square 3 mode color)
1057  (make-a-polygon (rectangle-points side-length side-length) mode color))
1058
1059(define/chk (empty-scene width height [color 'white])
1060  (crop 0 0 width height
1061        (overlay (rectangle width height 'outline (pen "black" 2 'solid 'round 'round))
1062                 (rectangle width height 'solid color))))
1063
1064(define/chk (rhombus side-length angle mode color)
1065  (check-mode/color-combination 'rhombus 3 mode color)
1066  (let* ([left-corner (make-polar side-length (+ (* pi 1/2) (/ (degrees->radians angle) 2)))]
1067         [right-corner (make-polar side-length (- (* pi 1/2) (/ (degrees->radians angle) 2)))]
1068         [bottom-corner (+ left-corner right-corner)])
1069    (make-a-polygon (list (make-point 0 0)
1070                          (make-point (real-part right-corner) (imag-part right-corner))
1071                          (make-point (real-part bottom-corner) (imag-part bottom-corner))
1072                          (make-point (real-part left-corner) (imag-part left-corner)))
1073                    mode
1074                    color)))
1075
1076(define (rectangle-points width height [dx 0] [dy 0])
1077  (list (make-point dx dy)
1078        (make-point (+ dx width) dy)
1079        (make-point (+ dx width) (+ height dy))
1080        (make-point dx (+ dy height))))
1081
1082
1083(define/chk (line x1 y1 color)
1084  (let-values ([(shape w h) (line-shape x1 y1 color)])
1085    (make-image shape
1086                (make-bb w h h)
1087                #f)))
1088
1089(define (line-shape x1 y1 color)
1090  (let ([dx (- (min x1 0))]
1091        [dy (- (min y1 0))]
1092        [w (+ (abs x1) 1)]
1093        [h (+ (abs y1) 1)])
1094    (values (make-translate
1095             dx dy
1096             (make-line-segment (make-point 0 0)
1097                                (make-point x1 y1)
1098                                color))
1099            w h)))
1100
1101(define/chk (add-line image x1 y1 x2 y2 color)
1102  (let* ([dx (abs (min 0 x1 x2))]
1103         [dy (abs (min 0 y1 y2))]
1104         [bottom (max (+ y1 dy)
1105                      (+ y2 dy)
1106                      (+ dy (get-bottom image)))]
1107         [right (max (+ x1 dx)
1108                     (+ x2 dx)
1109                     (+ dx (get-right image)))]
1110         [baseline (+ dy (get-baseline image))])
1111    (make-image (make-translate
1112                 dx dy
1113                 (make-overlay
1114                  (make-line-segment (make-point x1 y1) (make-point x2 y2) color)
1115                  (image-shape image)))
1116                (make-bb right bottom baseline)
1117                #f
1118                (send image get-pinhole))))
1119
1120(define/chk (add-polygon image posns-or-pulled-points mode color)
1121  (check-mode/color-combination 'add-polygon 3 mode color)
1122  (define left (apply min (map pp->x posns-or-pulled-points)))
1123  (define top (apply min (map pp->y posns-or-pulled-points)))
1124  (define poly (polygon posns-or-pulled-points mode color))
1125  (overlay/xy poly (- left) (- top) image))
1126
1127(define (pp->x p)
1128  (cond
1129    [(posn? p) (posn-x p)]
1130    [else (pulled-point-x p)]))
1131(define (pp->y p)
1132  (cond
1133    [(posn? p) (posn-y p)]
1134    [else (pulled-point-y p)]))
1135
1136(define/chk (add-solid-curve image x1 y1 angle1 pull1 x2 y2 angle2 pull2 color-only)
1137  (add-a-curve image x1 y1 angle1 pull1 x2 y2 angle2 pull2 'solid color-only))
1138
1139(define/chk (add-curve image x1 y1 angle1 pull1 x2 y2 angle2 pull2 color)
1140  (add-a-curve image x1 y1 angle1 pull1 x2 y2 angle2 pull2 'outline color))
1141
1142(define (add-a-curve image x1 y1 angle1 pull1 x2 y2 angle2 pull2 mode color)
1143  (define cs (make-curve-segment (make-point x1 y1) angle1 pull1
1144                                 (make-point x2 y2) angle2 pull2
1145                                 mode color))
1146  (define path (curve-segment->path cs))
1147  (define rdc (new record-dc%))
1148  (send rdc set-pen (mode-color->pen 'outline color))
1149  (send rdc set-brush "black" 'transparent)
1150  (send rdc set-smoothing 'smoothed)
1151  (define-values (path-l path-t path-w path-h) (send rdc get-path-bounding-box path 'stroke))
1152  (define dx (abs (min 0 path-l)))
1153  (define dy (abs (min 0 path-t)))
1154  (define bottom (max (+ dy path-t path-h) (+ dy (get-bottom image))))
1155  (define right (max (+ dx path-l path-w) (+ dx (get-right image))))
1156  (define baseline (+ dy (get-baseline image)))
1157  (make-image (make-translate
1158               dx dy
1159               (make-overlay
1160                cs
1161                (image-shape image)))
1162              (make-bb right bottom baseline)
1163              #f
1164              (send image get-pinhole)))
1165
1166;; this is just so that 'text' objects can be sized.
1167(define text-sizing-bm (make-object bitmap-dc% (make-object bitmap% 1 1)))
1168
1169(define/chk (text string font-size color)
1170  (mk-text/lines string font-size color #f 'swiss 'normal 'normal #f))
1171
1172(define/chk (text/font string font-size color face family style weight underline)
1173  (mk-text/lines string font-size color face family style weight underline))
1174
1175(define (mk-text/lines str font-size color face family style weight underline)
1176  (let ([lines (string-split str "\n")])
1177    (cond [(or (null? lines)
1178               (null? (cdr lines)))
1179           (mk-text str font-size color face family style weight underline)]
1180          [else
1181           (apply
1182            above/align
1183            "left"
1184            (map (λ(line)
1185                   (mk-text line font-size color face family style weight underline))
1186                 lines))])))
1187
1188(define (mk-text str font-size color face family style weight underline)
1189  (cond
1190    [(<= (string-length str) 1)
1191     (mk-single-text str font-size color face family style weight underline)]
1192    [else
1193     (let ([letters (string->list str)])
1194       (beside/internal
1195        'baseline
1196        (mk-single-text (string (car letters)) font-size color face family style weight underline)
1197        (map (λ (letter)
1198               (mk-single-text (string letter) font-size color face family style weight underline))
1199             (cdr letters))))]))
1200
1201(define (mk-single-text letter font-size color face family style weight underline)
1202  (let ([text (make-text letter 0 1 color font-size face family style weight underline)])
1203    (let-values ([(w h d a) (send text-sizing-bm get-text-extent letter (text->font text))])
1204      (make-image (make-translate (/ w 2) (/ h 2) text)
1205                  (make-bb w h (- h d))
1206                  #f))))
1207
1208(define/chk (isosceles-triangle side-length angle mode color)
1209  (check-mode/color-combination 'isosceles-triangle 4 mode color)
1210  (let ([left-corner (make-polar side-length (+ (* pi 1/2) (/ (degrees->radians angle) 2)))]
1211        [right-corner (make-polar side-length (- (* pi 1/2) (/ (degrees->radians angle) 2)))])
1212    (make-a-polygon (list (make-point 0 0)
1213                          (make-point (real-part right-corner) (imag-part right-corner))
1214                          (make-point (real-part left-corner) (imag-part left-corner)))
1215                    mode
1216                    color)))
1217
1218(define/chk (right-triangle side-length1 side-length2 mode color)
1219  (check-mode/color-combination 'right-triangle 4 mode color)
1220  (make-a-polygon (list (make-point 0 (- side-length2))
1221                        (make-point 0 0)
1222                        (make-point side-length1 0))
1223                  mode
1224                  color))
1225
1226(define/chk (triangle side-length mode color)
1227  (check-mode/color-combination 'triangle 3 mode color)
1228  (make-polygon/star side-length 3 mode color values))
1229
1230; excess : R+ R+ -> R
1231;  compute the Euclidean excess
1232;  Note: If the excess is 0, then C is 90 deg.
1233;        If the excess is negative, then C is obtuse.
1234;        If the excess is positive, then C is acuse.
1235(define (excess a b c)
1236  (+ (sqr a) (sqr b) (- (sqr c))))
1237
1238; polar->posn : R+ R -> (posn R R)
1239;  return a position with x and y coordinates
1240(define (polar->posn radius angle)
1241  (make-posn (* radius (cos angle))
1242             (* radius (sin angle))))
1243
1244; cos-rel : R R R -> R+
1245;   return c^2 = a^2 + b^2 - 2ab cos(C)
1246(define (cos-rel a b C)
1247  (+ (sqr a) (sqr b) (* -2 a b (cos C))))
1248
1249; sin-rel : R R R -> R
1250;  return the side b
1251(define (sin-rel A a B)
1252  (/ (* a (sin B)) (sin A)))
1253
1254; last-angle : R R -> R
1255;   return pi-(A+B)
1256(define (last-angle A B)
1257  (- pi A B))
1258
1259(define (radians degree)
1260  (* (/ degree 180.0) pi))
1261
1262
1263(define (triangle/sss side-a side-b side-c mode color)
1264  (define (triangle-vertices/sss a b c)
1265    (let ([A (acos (/ (excess b c a) (* 2 b c)))])
1266      (list (make-posn 0 0)
1267            (make-posn c 0)
1268            (polar->posn b A))))
1269  (check-dependencies 'triangle/sss
1270                      (and (>= (+ side-a side-b) side-c)
1271                           (>= (+ side-a side-c) side-b)
1272                           (>= (+ side-b side-c) side-a))
1273                      "the given side lengths will not form a triangle ~a, ~a, and, ~a."
1274                      side-a side-b side-c)
1275  (polygon (triangle-vertices/sss side-a side-b side-c) mode color))
1276
1277(define/chk (triangle/ass angle-a side-b side-c mode color)
1278  (define (triangle-vertices/ass A b c)
1279    (list (make-posn 0 0) (make-posn c 0) (polar->posn b A)))
1280  (polygon (triangle-vertices/ass (radians angle-a) side-b side-c) mode color))
1281
1282(define/chk (triangle/sas side-a angle-b side-c mode color)
1283  (define (triangle-vertices/sas a B c)
1284    (let ([b^2 (cos-rel a c B)])
1285      (check-dependencies 'triangle/sas
1286                          "the given side, angle, and, side will not form a triangle ~a, ~a, and, ~a."
1287                          side-a angle-b side-c)
1288      (let* ([b (sqrt b^2)]
1289             [A (acos (/ (excess b c a) (* 2 b c)))])
1290        (list (make-posn 0 0) (make-posn c 0) (polar->posn b A)))))
1291  (polygon (triangle-vertices/sas side-a (radians angle-b) side-c) mode color))
1292(define/chk (triangle/ssa side-a side-b angle-c mode color)
1293  (define (triangle-vertices/ssa a b C)
1294    (let ([c^2 (cos-rel a b C)])
1295      (check-dependencies 'triangle/ssa
1296                          (positive? c^2)
1297                          "the given side, side, and, angle will not form a triangle ~a, ~a, and, ~a."
1298                          side-a side-b angle-c)
1299      (let*([c (sqrt c^2)]
1300            [A (acos (/ (excess b c a) (* 2 b c)))])
1301        (list (make-posn 0 0)
1302              (make-posn c 0)
1303              (polar->posn b A)))))
1304  (polygon (triangle-vertices/ssa side-a side-b (radians angle-c)) mode color))
1305
1306(define/chk (triangle/aas angle-a angle-b side-c mode color)
1307  (define (triangle-vertices/aas A B c)
1308    (let* ([C (last-angle A B)]
1309           [b (sin-rel C c B)])
1310      (list (make-posn 0 0) (make-posn c 0) (polar->posn b A))))
1311  (polygon (triangle-vertices/aas (radians angle-a) (radians angle-b) side-c) mode color))
1312
1313(define/chk (triangle/asa angle-a side-b angle-c mode color)
1314  (define (triangle-vertices/asa A b C)
1315    (let* ([B (last-angle A C)]
1316           [c (sin-rel B b C)])
1317      (list (make-posn 0 0) (make-posn c 0) (polar->posn b A))))
1318  (polygon (triangle-vertices/asa (radians angle-a) side-b (radians angle-c)) mode color))
1319
1320(define/chk (triangle/saa side-a angle-b angle-c mode color)
1321  (define (triangle-vertices/saa a B C)
1322    (let* ([A (last-angle B C)]
1323           [b (sin-rel A a B)]
1324           [c (sin-rel A a C)])
1325      (list (make-posn 0 0)
1326            (make-posn c 0)
1327            (polar->posn b A))))
1328  (polygon (triangle-vertices/saa side-a (radians angle-b) (radians angle-c)) mode color))
1329
1330(define/chk (regular-polygon side-length side-count mode color)
1331  (check-mode/color-combination 'regular-polygon 4 mode color)
1332  (make-polygon/star side-length side-count mode color values))
1333
1334(define/chk (pulled-regular-polygon side-length side-count pull angle mode color)
1335  (check-mode/color-combination 'regular-polygon 4 mode color)
1336  (make-a-polygon (regular-polygon-points side-length side-count pull angle)
1337                  mode color))
1338
1339(define/chk (star-polygon side-length side-count step-count mode color)
1340  (check-mode/color-combination 'star-polygon 5 mode color)
1341  (check-arg 'star-polygon
1342             (step-count . < . side-count)
1343             (format "number that is smaller than the side-count (~a)" side-count)
1344             3
1345             step-count)
1346  (check-arg 'star-polygon
1347             (= 1 (gcd side-count step-count))
1348             (format "number that is relatively prime to the side-count (~a)" side-count)
1349             3
1350             step-count)
1351  (make-polygon/star side-length side-count mode color (λ (l) (swizzle l step-count))))
1352
1353(define/chk (star side-length mode color)
1354  (check-mode/color-combination 'star 3 mode color)
1355  (make-polygon/star side-length 5 mode color (λ (l) (swizzle l 2))))
1356
1357(define (make-polygon/star side-length side-count mode color adjust)
1358  (make-a-polygon (adjust (regular-polygon-points side-length side-count 0 0))
1359                  mode color))
1360
1361(define/chk (radial-star point-count radius1 radius2 mode color)
1362  (make-a-polygon (star-points radius1 radius2 point-count) mode color))
1363
1364(define (star-points in-small-rad in-large-rad points)
1365  (let* ([small-rad (- in-small-rad 1)]
1366         [large-rad (- in-large-rad 1)]
1367         [roff (floor (/ large-rad 2))])
1368    (let loop ([i points])
1369      (cond
1370        [(zero? i) '()]
1371        [else
1372         (let* ([this-p (- i 1)]
1373                [theta1 (* 2 pi (/ this-p points))]
1374                [theta2 (* 2 pi (/ (- this-p 1/2) points))])
1375           (let-values ([(x1 y1) (find-xy small-rad theta1)]
1376                        [(x2 y2) (find-xy large-rad theta2)])
1377             (let ([p1 (make-point (+ large-rad x1)
1378                                   (+ large-rad y1))]
1379                   [p2 (make-point (+ large-rad x2)
1380                                   (+ large-rad y2))])
1381               (list* p1 p2 (loop (- i 1))))))]))))
1382
1383(define (find-xy radius theta)
1384  (values (* radius (cos theta))
1385          (* radius (sin theta))))
1386
1387(define (make-a-polygon points mode color)
1388  (let* ([poly (make-polygon points mode color)]
1389         [ltrb (simple-bb poly)]
1390         [l (ltrb-left ltrb)]
1391         [t (ltrb-top ltrb)]
1392         [r (ltrb-right ltrb)]
1393         [b (ltrb-bottom ltrb)])
1394    (make-image (make-translate (- l) (- t) poly)
1395                (make-bb (- r l) (- b t) (- b t))
1396                #f)))
1397(define (gcd a b)
1398  (cond
1399    [(zero? b) a]
1400    [else (gcd b (modulo a b))]))
1401
1402
1403
1404;; swizzle : (listof X)[odd-length] -> (listof X)
1405;; returns a list with the same elements,
1406;; but reordered according to the step. Eg, if the step
1407;; is 2, we get the even elements and then the odd ones.
1408(define (swizzle l step)
1409  (let ([v (list->vector l)])
1410    (let loop ([i 0])
1411      (cond
1412        [(= i (vector-length v)) '()]
1413        [else
1414         (cons (vector-ref v (modulo (* i step) (vector-length v)))
1415               (loop (+ i 1)))]))))
1416
1417;; regular-polygon-points : number number -> (listof point)
1418(define (regular-polygon-points side-length side-count pull angle)
1419  (let loop ([p (make-rectangular 0 0)]
1420             [i 0])
1421    (cond
1422      [(= i side-count) '()]
1423      [else (cons (build-pulled-point pull angle (real-part p) (imag-part p) pull (- angle))
1424                  (loop (+ p (make-polar side-length
1425                                         (* -1 2pi (/ i side-count))))
1426                        (+ i 1)))])))
1427
1428(define/chk (ellipse width height mode color)
1429  (check-mode/color-combination 'ellipse 4 mode color)
1430  (cond
1431    [(or (zero? width) (zero? height))
1432     (make-a-polygon (rectangle-points width height) mode color)]
1433    [else
1434     (make-image (make-translate (/ width 2) (/ height 2)
1435                                 (make-ellipse width height
1436                                               0
1437                                               mode
1438                                               color
1439                                               #f))
1440                 (make-bb width height height)
1441                 #f)]))
1442
1443(define/chk (wedge non-zero-radius angle-between-0-and-360 mode color)
1444  (check-mode/color-combination 'ellipse 4 mode color)
1445  (define width (* 2 non-zero-radius))
1446  (define the-wedge (make-ellipse width width 0 mode color angle-between-0-and-360))
1447  (define-values (l t r b) (np-atomic-bb the-wedge))
1448  (define w (- r l))
1449  (define h (- b t))
1450  (make-image (make-translate (- l) (- t) the-wedge)
1451              (make-bb w h h)
1452              #f))
1453
1454(define/chk (circle radius mode color)
1455  (check-mode/color-combination 'circle 3 mode color)
1456  (cond
1457    [(zero? radius)
1458     (make-a-polygon (rectangle-points 0 0) mode color)]
1459    [else
1460     (define w/h (* 2 radius))
1461     (make-image (make-translate radius radius (make-ellipse w/h w/h 0 mode color #f))
1462                 (make-bb w/h w/h w/h)
1463                 #f)]))
1464
1465(define empty-image (rectangle 0 0 'solid 'black))
1466
1467(define/chk (image-width image) (bb-select/round/exact bb-right image))
1468(define/chk (image-height image) (bb-select/round/exact bb-bottom image))
1469(define/chk (image-baseline image) (bb-select/round/exact bb-baseline image))
1470(define (bb-select/round/exact select image) (inexact->exact (round (select (send image get-bb)))))
1471
1472(define-syntax (bitmap stx)
1473  (syntax-case stx ()
1474    [(_ arg)
1475     (let* ([arg (syntax->datum #'arg)]
1476            [path/lst
1477             (cond
1478               [(and (pair? arg)
1479                     (eq? (car arg) 'planet))
1480                (raise-syntax-error 'bitmap "planet paths not yet supported" stx)]
1481               [(symbol? arg)
1482                (define pieces (regexp-split #rx"/" (symbol->string arg)))
1483                (cond
1484                  [(or (null? pieces) (null? (cdr pieces)))
1485                   (raise-syntax-error 'bitmap "expected a path with a / in it" stx)]
1486                  [else
1487                   (define fn (last pieces))
1488                   (define colls (reverse (cdr (reverse pieces))))
1489                   (define candidate
1490                     (apply collection-file-path fn colls
1491                            #:fail
1492                            (λ (msg) (raise-syntax-error 'bitmap msg stx))))
1493                   (unless (file-exists? candidate)
1494                     (raise-syntax-error 'bitmap
1495                                         (format "could not find ~s, expected it to be in ~a"
1496                                                 arg candidate)
1497                                         stx))
1498                   (cons fn colls)])]
1499               [(string? arg)
1500                (path->complete-path
1501                 arg
1502                 (or (current-load-relative-directory)
1503                     (current-directory)))]
1504               [else (raise-syntax-error
1505                      'bitmap
1506                      (string-append
1507                       "expected the argument to specify a local path (via a string)"
1508                       " or a module path (e.g. `icons/b-run.png')")
1509                      stx)])])
1510       #`(bitmap/proc '#,path/lst))]))
1511
1512(define (bitmap/proc arg)
1513  (define pth (if (path? arg)
1514                  arg
1515                  (apply collection-file-path arg
1516                         #:fail
1517                         (λ (msg) (error 'bitmap msg)))))
1518  (when (and (path? pth)
1519             (not (file-exists? pth)))
1520    (error 'bitmap "could not find the file ~a" (path->string pth)))
1521  ;; the rotate does a coercion to a 2htdp/image image
1522  (rotate 0 (make-object image-snip% (make-object bitmap% pth 'unknown/alpha))))
1523
1524(define/chk (bitmap/url string)
1525  ;; the rotate does a coercion to a 2htdp/image image
1526  (rotate
1527   0
1528   (call/input-url (string->url string)
1529                   (lambda (url) (get-pure-port url #:redirections 20))
1530                   (λ (port)
1531                     (make-object bitmap% port 'unknown/alpha #f #t)))))
1532
1533(define/chk (bitmap/file filename)
1534  (unless (file-exists? filename)
1535    (error 'bitmap/file
1536           "could not find the file ~a"
1537           filename))
1538  (rotate
1539   0
1540   (read-bitmap filename)))
1541
1542(define/chk (image->color-list image)
1543  (define w (image-width image))
1544  (define h (image-height image))
1545  (cond
1546    [(or (= w 0) (= h 0)) '()]
1547    [else
1548     (define bm (make-bitmap w h))
1549     (define bdc (make-object bitmap-dc% bm))
1550     (define c (make-object color%))
1551     (define bytes (make-bytes (* w h 4)))
1552     (send bdc erase)
1553     (render-image image bdc 0 0)
1554     (send bdc get-argb-pixels 0 0 w h bytes)
1555     (for/list ([i (in-range 0 (* w h 4) 4)])
1556       (color (bytes-ref bytes (+ i 1))
1557              (bytes-ref bytes (+ i 2))
1558              (bytes-ref bytes (+ i 3))
1559              (bytes-ref bytes i)))]))
1560
1561(define/chk (color-list->bitmap color-list width height)
1562  (check-dependencies
1563   'color-list->bitmap
1564   (= (* width height) (length color-list))
1565   (string-append
1566    "the length of the color list to match the product of the width and the height,"
1567    " but the list has ~a elements and the width and height are ~a and ~a respectively")
1568   (length color-list) width height)
1569  (cond
1570    [(or (zero? width) (zero? height))
1571     (rectangle width height "solid" "black")]
1572    [else
1573     (define bmp (make-bitmap width height))
1574     (define bytes (make-bytes (* width height 4) 0))
1575     (define o (make-object color%))
1576     (for ([c (in-list color-list)]
1577           [i (in-naturals)])
1578       (define j (* i 4))
1579       (cond
1580         [(color? c)
1581          (bytes-set! bytes j (color-alpha c))
1582          (bytes-set! bytes (+ j 1) (color-red c))
1583          (bytes-set! bytes (+ j 2) (color-green c))
1584          (bytes-set! bytes (+ j 3) (color-blue c))]
1585         [else
1586          (define str (if (string? c) c (symbol->string c)))
1587          (define clr (or (string->color-object/f str)
1588                          (send the-color-database find-color "black")))
1589          (bytes-set! bytes j 255)  ;; this should probably (send clr alpha) when that's possible
1590          (bytes-set! bytes (+ j 1) (send clr red))
1591          (bytes-set! bytes (+ j 2) (send clr green))
1592          (bytes-set! bytes (+ j 3) (send clr blue))]))
1593     (send bmp set-argb-pixels 0 0 width height bytes)
1594     (bitmap->image bmp)]))
1595
1596(define build-color/make-color
1597  (let ([orig-make-color make-color])
1598    (define/chk make-color
1599      (case-lambda
1600        [(int0-255-1 int0-255-2 int0-255-3)
1601         (orig-make-color int0-255-1 int0-255-2 int0-255-3)]
1602        [(int0-255-1 int0-255-2 int0-255-3 int0-255-4)
1603         (orig-make-color int0-255-1 int0-255-2 int0-255-3 int0-255-4)]))
1604    make-color))
1605
1606(define/chk (pinhole-x image) (let ([ph (send image get-pinhole)]) (and ph (point-x ph))))
1607(define/chk (pinhole-y image) (let ([ph (send image get-pinhole)]) (and ph (point-y ph))))
1608(define/chk (put-pinhole x1 y1 image)
1609  (make-image (image-shape image) (image-bb image) (image-normalized? image) (make-point x1 y1)))
1610(define/chk (center-pinhole image)
1611  (let ([bb (send image get-bb)])
1612    (make-image (image-shape image)
1613                (image-bb image)
1614                (image-normalized? image)
1615                (make-point (/ (bb-right bb) 2)
1616                            (/ (bb-baseline bb) 2)))))
1617(define/chk (clear-pinhole image)
1618  (make-image (image-shape image) (image-bb image) (image-normalized? image) #f))
1619
1620(define build-color/color
1621  (let ([orig-make-color make-color])
1622    (define/chk color
1623      (case-lambda
1624        [(int0-255-1 int0-255-2 int0-255-3)
1625         (orig-make-color int0-255-1 int0-255-2 int0-255-3)]
1626        [(int0-255-1 int0-255-2 int0-255-3 int0-255-4)
1627         (orig-make-color int0-255-1 int0-255-2 int0-255-3 int0-255-4)]))
1628    color))
1629
1630(define build-pulled-point/pulled-point
1631  (let ()
1632    (define/chk (pulled-point pull1 angle1 x y pull2 angle2)
1633      (build-pulled-point pull1 angle1 x y pull2 angle2))
1634    pulled-point))
1635
1636(define build-pulled-point/make-pulled-point
1637  (let ()
1638    (define/chk (make-pulled-point pull1 angle1 x y pull2 angle2)
1639      (build-pulled-point pull1 angle1 x y pull2 angle2))
1640    make-pulled-point))
1641
1642(define build-pen/make-pen
1643  (let ([orig-make-pen make-pen])
1644    (define/chk (make-pen color int-0-255 pen-style pen-cap pen-join)
1645      (orig-make-pen color int-0-255 pen-style pen-cap pen-join))
1646    make-pen))
1647
1648(define build-pen/pen
1649  (let ([orig-make-pen make-pen])
1650    (define/chk (pen color int-0-255 pen-style pen-cap pen-join)
1651      (orig-make-pen color int-0-255 pen-style pen-cap pen-join))
1652    pen))
1653
1654(define/chk freeze
1655  (case-lambda
1656    [(image) (freeze/internal 0 0 (image-width image) (image-height image) image)]
1657    [(width height image) (freeze/internal 0 0 width height image)]
1658    [(x y width height image) (freeze/internal x y width height image)]))
1659
1660(define (freeze/internal x y w h image)
1661  (cond
1662    [(or (zero? w) (zero? h))
1663     ;; ensures that we never return an image with an embedded `text` in
1664     ;; it which, in turn, guarantees that we can flip the result
1665     (rectangle w h 'solid 'black)]
1666    [else
1667     (define bm (make-bitmap w h))
1668     (define bdc (make-object bitmap-dc% bm))
1669     (render-image image bdc (- x) (- y))
1670     (send bdc set-bitmap #f)
1671     (to-img bm)]))
1672
1673(provide overlay
1674         overlay/align
1675         overlay/offset
1676         overlay/align/offset
1677         overlay/xy
1678
1679         underlay
1680         underlay/align
1681         underlay/align/offset
1682         underlay/offset
1683         underlay/xy
1684
1685         beside
1686         beside/align
1687         above
1688         above/align
1689
1690         rotate
1691         crop
1692         crop/align
1693         flip-vertical
1694         flip-horizontal
1695         frame
1696         color-frame
1697
1698         place-image
1699         place-image/align
1700         place-images
1701         place-images/align
1702         put-image
1703
1704         save-image
1705         save-svg-image
1706         bring-between
1707
1708
1709         scale
1710         scale/xy
1711
1712         image-width
1713         image-height
1714         image-baseline
1715
1716         circle
1717         ellipse
1718         wedge
1719         rectangle
1720         empty-scene
1721         square
1722         rhombus
1723         empty-image
1724
1725         polygon
1726         regular-polygon
1727         pulled-regular-polygon
1728         triangle
1729         triangle/sss
1730         triangle/ssa
1731         triangle/sas
1732         triangle/ass
1733         triangle/aas
1734         triangle/asa
1735         triangle/saa
1736
1737         isosceles-triangle
1738         right-triangle
1739         star
1740         star-polygon
1741         radial-star
1742
1743         line
1744         add-line
1745         add-polygon
1746         add-curve
1747         add-solid-curve
1748         scene+line
1749         scene+polygon
1750         scene+curve
1751         text
1752         text/font
1753         image->color-list
1754         color-list->bitmap
1755
1756         bitmap
1757         bitmap/url
1758         bitmap/file
1759
1760         swizzle
1761
1762         rotate-xy
1763
1764         put-pinhole
1765         pinhole-x
1766         pinhole-y
1767         clear-pinhole
1768         center-pinhole
1769         overlay/pinhole
1770         underlay/pinhole
1771
1772         build-color/make-color
1773         build-color/color
1774         build-pen/make-pen
1775         build-pen/pen
1776         build-pulled-point/make-pulled-point
1777         build-pulled-point/pulled-point
1778
1779         freeze
1780
1781         render-image)
1782
1783(provide/contract
1784 [np-atomic-bb (-> np-atomic-shape? (values real? real? real? real?))]
1785 [center-point (-> np-atomic-shape? number?)])
1786