Meta  Pict
1 Introduction
2 Guide
2.1 Coordinates
2.1.1 Points
2.1.2 Displacements
3 Reference
3.1 Points and Vectors (pt and vec)
3.1.1 Points and Vectors
pt
vec
3.1.1.1 Predefined Points and Vectors
origo
north
south
west
east
up
down
left
right
3.1.1.2 Point Operations
pt+
pt-
pt*
dist
pt=
pt~
med
pt@
pt@d
3.1.1.3 Vector Operations
vec+
vec-
vec*
vec->pt
pos
vec=
vec~
dot
len2
len
norm
dir/  rad
dir
vec@
@
rot90
rot-90
3.2 Colors
make-color*
color
color->list
color+
color*
color-med
color-med*
change-red
change-blue
change-green
change-alpha
3.3 Pict
3.3.1 Pict Adjusters
3.3.1.1 Pen Adjusters
color
pencolor
penwidth
penscale
penstyle
pencap
penjoin
pen
dashed
dotted
3.3.1.2 Brush Adjusters
brush
brushcolor
brushstyle
brushstipple
brushgradient
save-pict
margin
3.3.1.3 Pict Combiners
above
beside
above*
beside*
3.4 Bezier Curves
point-of-bez
bez~
bez-reverse
split-bez
bez-subpath
bez-intersection-point
bez-intersection-times
bez-intersection-point-and-times
draw-bez
draw-bezs
bez->dc-path
bezs->dc-path
bez/  dirs+  tensions
control-points
3.5 Curves
curve:
curve-length
point-of
start-point
end-point
curve-reverse
curve-append
intersection-times
intersection-point
intersection-point-and-times
intersection-points
subcurve
cut-before
cut-after
post-control
pre-control
direction-of
cyclic?
intercurve
curve
..
--
cycle
join
controls-and
tension-and
full-join
tension
curl
3.6 Transformations
trans
identity
rotated90
rotated180
rotated270
flipx
flipy
slanted
scaled
xscaled
yscaled
shifted
zscaled
rotated
rotatedd
rotated-about
rotatedd-about
reflected
inverse
trans~
compose-trans
trans->vector
trans->transformation
3.7 Device Settings
curve-pict-width
curve-pict-height
set-curve-pict-size
set-curve-pict-size
curve-pict-window
trans-logical-to-device
current-curve-transformation
px
xpx
ypx
3.8 Drawing and Filling
draw
draw*
fill
eofill
filldraw
for/  draw
for*/  draw
3.9 Labels
label
placement
lft
ulft
llft
rt
urt
lrt
bot
top
cnt
label-lft
label-ulft
label-llft
label-rt
label-urt
label-lrt
label-top
label-bot
label-cnt
dot-label
dot-label-lft
dot-label-ulft
dot-label-llft
dot-label-rt
dot-label-urt
dot-label-lrt
dot-label-top
dot-label-bot
dot-label-cnt
label/  offset
label-bbox
fill-label
3.10 Representation
pt
vec
bez
window
4 Examples
4.1 Rotating Triangle
4.2 Rooty Helix
4.3 Glider - Hacker Emblem
4.4 Puzzle:   The Missing Square
4.5 The Olympic Rings
4.6 Cuboid
4.7 RGB Triangle
7.9

MetaPict

Jens Axel Søgaard <jensaxel@soegaard.net>

 (require metapict) package: metapict

1 Introduction

The metapict library provides functions and data structures useful for generating picts. The library includes support for points, vectors, Bezier curves, and, general curves.

The algorithm used to calculate a nice curve from points and tangents is the same as the one used in MetaPost.

With this library I to hope narrow the gap between Scribble and LaTeX + MetaPost/Tikz. If you find any features in MetaPost or Tikz that you miss, don’t hesitate to mail me.

2 Guide

2.1 Coordinates

2.1.1 Points

In order to make a computer draw a shape, a way to specify the key points of the shape is needed.

Note: This is different from racket/pict which reverses the direction of the y-axis.

MetaPict uses standard (x,y)-coordinates for this purpose. The location of a point is always relative to the reference point (0,0). The x-coordinate of a point is the number of units to the right of the reference point. The y-coordinate of a point is the number of units upward from the reference point.

Consider these points:

The coordinates of these points are:

p1=(0,100)

  

p2=(100,100)

  

p3=(200,100)

p4=(0,0)

  

p5=(100,0)

  

p6=(200,0)

Notice that the point p4=(0,0) is the reference point. The point p3=(200,100) is located 200 units to the right of p4 and 100 units upwards.

In order to write a MetaPict program to draw a shape, a good strategy is to draw the shape on paper. Determine the coordinates for the key points, and then write the MetaPict program that draws lines or curves between the points.

Let us write such a program, that connects point p1 and p6.
> (with-window (window -10 210 -5 105)
    (draw (curve (pt 0 100) .. (pt 200 0))))

The .. between the two points connects the two points with a line.

If we are to use the points repeatedly, it is better give them names.

def is shorthand for define

> (def p1 (pt   0 100))
> (def p2 (pt 100 100))
> (def p3 (pt 200 100))
> (def p4 (pt   0   0))
> (def p5 (pt 100   0))
> (def p6 (pt 200   0))
> (with-window (window -10 210 -5 105)
    (draw (curve p1 .. p6)))

Let us connect the point p2 with p5 and p3 with p4.
> (with-window (window -10 210 -5 105)
    (draw (curve p1 .. p6)
          (curve p2 .. p5)
          (curve p3 .. p4)))

If you zoom, you will see that the lines have a thickness and that the ends are rounded. Imagine that you have a pen with a circular nib. The drawings produced by MetaPict will try to mimick the result you get by drawing with such a pen. In the chapter on pens you will learn to the control the thickness of the pen and the shape of the ends of lines.

2.1.2 Displacements

In the example above the point p2=(100,100) was described as being 100 to the right and 100 upwards relative to the reference point (0,0).

An alternative way of describing the location of p2 would be to say that is located 100 to the right of p1 (and 0 upwards).

Such a displacement can be described with a vector. Since Racket uses the name "vector", we will represent displacement vectors with a vec structure. To displace a point p with a vector v, use pt+.

> (def v (vec 100 0))
> (def p1 (pt 0 100))
> (def p2 (pt+ p1 v))
> (def p3 (pt+ p2 v))
> (def p4 (pt 0 0))
> (def p5 (pt+ p4 v))
> (def p6 (pt+ p5 v))
> (with-window (window -10 210 -5 105)
    (draw (curve p1 .. p6)
          (curve p2 .. p5)
          (curve p3 .. p4)))

Note: defv is short for define-values.

The point p3 is p1 displaced by v twice. Use vec* to compute 2v. At the same time, let’s use defv to define multiple points at a time.

> (def  v (vec 100 0))
> (def 2v (vec* 2 v))
> (defv (p1 p2 p3) (values (pt 0 100) (pt+ p1 v) (pt+ p1 2v)))
> (defv (p4 p5 p6) (values (pt 0   0) (pt+ p4 v) (pt+ p4 2v)))
> (with-window (window -10 210 -5 105)
    (draw (curve p1 .. p6) (curve p2 .. p5) (curve p3 .. p4)))

The displacements left, right, up, and, down. are predefined. As are the vector operations vec+,vec-, and, vec*. The displacement that moves a point a to point b is given by (pt- b a).

> (list left right up down)

(list (vec -1 0) (vec 1 0) (vec 0 1) (vec 0 -1))

> (vec+ left up)

(vec -1 1)

> (vec- left up)

(vec -1 -1)

> (vec* 3 right)

(vec 3 0)

> (pt- (pt 2 4) (pt 7 8))

(vec -5 -4)

It is common to need points that lie between two point A and B. The mediation operation is called med. The call (med 0.25 A B) will compute the point M on the line from A to B whose distance from A is 25% of the length of AB.

> (def A (pt 0 0))
> (def B (pt 3 2))
> (with-window (window -1 4 -1 3)
    (draw (dot-label "A" A              (top))
          (dot-label "C" (med 0.25 A B) (top))
          (dot-label "D" (med 0.5  A B) (bot))
          (dot-label "E" (med 0.75 A B) (bot))
          (dot-label "B" B              (bot))))

Note: (med x A B) is equivalent to (pt+ A (vec* x (pt- B A))).

Let’s use the knowledge from this section to write a small program to generate the character A. The shape depends on the parameters w (width), h (height) and the placement of the bar α.

> (define (A w h α)
    (set-curve-pict-size w h)
    (def p1 (pt    0    0))
    (def p2 (pt (/ w 2) h))
    (def p3 (pt    w    0))
    (def p4 (med α p1 p2))
    (def p5 (med α p3 p2))
    (with-window (window 0 w 0 h)
      (draw (curve p1 .. p2)
            (curve p2 .. p3)
            (curve p4 .. p5))))
> (list (A 10 20 0.3)
        (A 10 20 0.4)
        (A 10 20 0.5)
        (A 10 20 0.6))

'(   )

3 Reference

3.1 Points and Vectors (pt and vec)

 (require metapict/pt-vec) package: metapict

3.1.1 Points and Vectors

Points and vectors are represented as pt and vec structures respectively. Think of points as positions and of vectors as displacements.

struct

(struct pt (x y)
    #:extra-constructor-name make-pt)
  x : real?
  y : real?
The pt structure represents a point with coordinates (x,y) in the current coordinate system.

> (def A (pt 3 4))
> A

(pt 3 4)

> (pt-x A)

3

> (penwidth 4 (draw (pt 0 0) (pt 1/2 1/2) (pt 1 0)))

struct

(struct vec (x y)
    #:extra-constructor-name make-vec)
  x : real?
  y : real?
The vec structure represents a vector with coordinates (x,y) in the current coordinate system.
> (def v (vec 3 4))
> v

(vec 3 4)

> (vec-x v)

3

3.1.1.1 Predefined Points and Vectors

The most common points and vectors have predefined names.

value

origo : (pt 0 0)

Origo (0,0) is the reference point of the coordinate system.

value

north : (vec  0  1)

value

south : (vec  0 -1)

value

west : (vec -1  0)

value

east : (vec  1  0)

The compass directions as vecs.

value

up : (vec  0  1)

value

down : (vec  0 -1)

value

left : (vec -1  0)

value

right : (vec  1  0)

Alternative directions names. Note that the direction names make sense only if, the current coordinate system has a positive orientation.

> (penwidth 4 (draw (color "red"     (draw      origo))
                    (color "green"   (draw (pt+ origo north)))
                    (color "blue"    (draw (pt+ origo south)))
                    (color "magenta" (draw (pt+ origo left)))
                    (color "purple"  (draw (pt+ origo right)))))

3.1.1.2 Point Operations

procedure

(pt+ A v)  pt?

  A : pt?
  v : vec?
(pt+ A B)  pt?
  A : pt?
  B : pt?
(pt+ A B-or-v ...)  pt?
  A : pt
  B-or-v : (or pt? vec?)
Let the coordinates of A, B and v be A=(a1,a2), B=(b1,b2), and, v=(v1,v2).

The form (pt+ A v) returns the displacement of the point A with the vector v. That is, (a1+v1,a2+v2) is returned.

The form (pt+ A B) adds the coordinates of A and B pairwise. The point A is thus displaced with the vector OB. That is, (a1+b1,a2+b2) is returned.

The form (pt+) returns origo, (pt 0 0).

The form (pt+ A) returns the point A.

The form (pt+ A B-or-v ...) returns the result of (pt+ (pt+ A B-or-v) ...).
> (pt+ (pt 1 2) (vec 3 7))

(pt 4 9)

> (pt+ (pt 1 2) (pt 3 7))

(pt 4 9)

> (pt+)

(pt 0 0)

> (pt+ (pt 1 2))

(pt 1 2)

> (pt+ (pt 0.3 0.4) (vec 3 0) (vec 4 0))

(pt 7.3 0.4)

procedure

(pt- A B)  pt?

  A : pt?
  B : pt?
(pt- A v)  pt?
  A : pt?
  v : vec?
(pt- A)  pt?
  A : pt?
The form (pt- B A) returns the vector AB. That is, if A=(a1,a2) and B=(b1,b2), then (b1-a1,b2-a2) is returned.

The form (pt- A v) returns the displacement of the point A with the opposite of vector v. If A=(a1,a2) and v=(v1,v2) then the vector (a1-v1,a2-v2) is returned.

The form (pt- A) returns the reflection of the point A with respect to origo. If A=(a1,a2), then the vector (-a1,-a2) is returned.
> (pt- (pt 1 2) (vec 3 7))

(pt -2 -5)

> (pt- (pt 1 2))

(vec -1 -2)

procedure

(pt* s A)  pt?

  s : real?
  A : pt?
Scale the coordinates of A with s. If the coordinates of A are (x,y) then the point (sx,sy) is returned.
> (pt* 3 (pt 1 2))

(pt 3 6)

procedure

(dist A B)  real?

  A : pt?
  B : pt?
Return the distance between the points A and B.

The distance from (x,y) to (a,b) is sqrt((x-a)2 + (y-b)2).
> (dist (pt 4 0) (pt 4 3))

3

procedure

(pt= A B)  boolean?

  A : pt?
  B : pt?
Returns #t if the coordinates of the point A and B are equal with respect to =. Otherwise #f is returned.
> (pt= (pt 1 2) (pt 1 2))

#t

> (pt= (pt 1 2) (pt 1 42))

#f

procedure

(pt~ A B ε)  boolean?

  A : pt?
  B : pt?
  ε : 1e-15
Returns #t if the distance from the point A to the point is less than or equal to ε. The default value of ε is 1e-15.
> (pt~ (pt 1 2) (pt 1 2.09))

#f

> (pt~ (pt 1 2) (pt 1 2.09) 0.1)

#t

procedure

(med r A B)  pt?

  r : real?
  A : pt?
  B : pt?
The mediate function med computes points between points A and B (if 0<=r<=1). The mediation operation is also known as linear interpolation.

The form (med 1/3 A B) returns the point that lies one-third of the way from A to B.

In general (med r A B) returns the point (1-r)A + rB.
> (def A (pt 0 0))
> (def B (pt 2 1))
> (list (med 0 A B) (med 1/3 A B) (med 1/2 A B) (med 2/3 A B) (med 1 A B))

(list (pt 0 0) (pt 2/3 1/3) (pt 1 1/2) (pt 4/3 2/3) (pt 2 1))

> (set-curve-pict-size 100 50)
> (with-window (window -0.2 2.2 -0.1 1.1)
    (penwidth 4 (draw* (for/list ([r '(0 1/3 1/2 2/3 1)]
                                  [c '("red" "orange" "yellow" "green" "blue")])
                         (color c (draw (med r A B)))))))

procedure

(pt@ r θ)  pt?

  r : real?
  θ : real?
Returns the point with polar coordinations (r,θ). That is, the point is on the circle with center (0,0) and radius r. The angle from the x-axis to the line through origo and the point is θ. The angle θ is given in radians (0 rad = 0 degrees, π rad = 180 degrees).
> (require racket/math)
> (set-curve-pict-size 50 50)
> (with-window (window -1.1 1.1 -1.1 1.1)
    (penwidth 4 (draw* (for/list ([θ (in-range 0 (* 2 pi) (/ (* 2 pi) 12))])
                         (pt@ 1 θ)))))

procedure

(pt@d r θ)  pt?

  r : real?
  θ : real?
Same as pt@ but the angle is in degrees.
> (pt@d 1 45)

(pt 0.7071067811865476 0.7071067811865475)

> (pt@  1 (/ pi 4))

(pt 0.7071067811865476 0.7071067811865475)

3.1.1.3 Vector Operations

In this section the coordinates of vecs v and w will be referred to as v=(v1,v2) and w=(w1,w2).

procedure

(vec+ v w)  vec?

  v : vec?
  w : vec?
Returns the vector sum of v and w, that is the vector (v1+w1,v2+w2) is returned.

In terms of displacements the vector sum v+w can be thought of as the result of the displament v followed by the displacement w.

> (def v   (vec 2 0))
> (def w   (vec 0 3))
> (def v+w (vec+ v w))
> v+w

(vec 2 3)

> (define (arrow v [offset (vec 0 0)])
    (def A (pt+ origo offset))
    (draw-arrow (curve A -- (pt+ A v))))
> (ahlength (px 12))
> (with-window (window -0.2 3.2 -0.2 3.2)
    (penwidth 2
      (draw (color "red"   (arrow v))
            (color "green" (arrow w v))
            (color "blue"  (arrow v+w)))))

procedure

(vec- v w)  vec?

  v : vec?
  w : vec?
Returns the vector difference of v and w, that is the vector (v1-w1,v2-w2) is returned.

procedure

(vec* s v)  vec?

  s : real?
  v : vec?
Scale the coordinates of v with s. If the coordinates of v are (x,y) then the vector (sx,sy) is returned.
> (vec* 3 (vec 1 2))

(vec 3 6)

procedure

(vec->pt v)  pt?

  v : vec?
Converts the vector (x,y) into a point with the same coordinates. If a point A has the same coordinates as a vector v, then the vector is said to a position vector for the point and OA=v.
> (vec->pt (vec 1 2))

(pt 1 2)

procedure

(pos p)  vec?

  p : pt?
Converts the point p into a vector with the same coordinates. Such a vector is also called a position vector, hence the name.
> (pos (pt 1 2))

(vec 1 2)

procedure

(vec= v w)  boolean?

  v : vec?
  w : vec?
Returns #t if the coordinates of the vectors v and w are equal with respect to =. Otherwise #f is returned.
> (vec= (vec 1 2) (vec 1 2))

#t

> (vec= (vec 1 2) (vec 1 42))

#f

procedure

(vec~ v w ε)  boolean?

  v : vec?
  w : vec?
  ε : 1e-15
Returns #t if the length of v-w is less than or equal to ε. The default value of ε is 1e-15.
> (vec~ (vec 1 2) (vec 1 2.09))

#f

> (vec~ (vec 1 2) (vec 1 2.09) 0.1)

#t

procedure

(dot v w)  real?

  v : vec?
  w : vec?
Returns the dot product of the vectors v and w. The dot product is the number v1 w1 + v2 w2.

The dot product of two vectors are the same as the product of the lengths of the two vectors times the cosine of the angle between the vectors. Thus the dot product of two orthogonal vectors are zero, and the dot product of two vectors sharing directions are the product of their lengths.
> (dot (vec 1 0) (vec 0 1))

0

> (dot (vec 0 2) (vec 0 3))

6

procedure

(len2 v)  real?

  v : vec?
Returns the square of the length of the vector v.
> (len2 (vec 1 1))

2

> (len2 (vec 3 4))

25

procedure

(len v)  real?

  v : vec?
(norm v)  real?
  v : vec?
Returns the length of the vector v.
> (len (vec 1 1))

1.4142135623730951

> (len (vec 3 4))

5

procedure

(dir/rad α)  vec?

  α : real?
Returns the unit vector whose angle with the first axis is α radians.
> (dir/rad 0)

(vec 1 0)

> (dir/rad (/ pi 2))

(vec 6.123233995736766e-17 1.0)

procedure

(dir α)  vec?

  α : real?
Returns the unit vector whose angle with the first axis is α degrees.
> (dir 0)

(vec 1 0)

> (dir 90)

(vec 6.123233995736766e-17 1.0)

procedure

(vec@ r α)  vec?

  r : real?
  α : real?
Returns the vector of length r whose angle with the first axis is α radians. In other words construct a vector form polar coordinates.
> (vec@ 2 0)

(vec 2 0)

> (vec@ 2 pi)

(vec -2.0 2.4492935982947064e-16)

procedure

(@ A-or-v)  
real? real?
  A-or-v : (or pt? vec?)
Returns the polar coordinates of the point or vector.
> (@ (pt  3 4))

5

0.9272952180016123

> (@ (vec 3 4))

5

0.9272952180016123

procedure

(rot90 A-or-v)  (or pt? vec?)

  A-or-v : (or pt? vec?)
(rot-90 A-or-v)  (or pt? vec?)
  A-or-v : (or pt? vec?)
Rotates the point or vector 90 or -90 degrees around origo.
> (rot90  (pt  1 0))

(pt 0 1)

> (rot90  (vec 1 0))

(vec 0 1)

> (rot-90 (pt  1 0))

(pt 0 -1)

> (rot-90 (vec 1 0))

(vec 0 -1)

3.2 Colors

 (require metapict/color) package: metapict

procedure

(make-color* name)  (is-a?/c color%)

  name : string?
(make-color* r g b α)  (is-a?/c color%)
  r : real?
  g : real?
  b : real?
  α : 1.0
The function make-color* is a fault tolerant version of make-color that also accepts color names.

Given a color name as a string, make-color* returns a color% object.

Given real numbers to use as the color components, make-color* works like make-color, but accepts both non-integer numbers, and numbers outside the range 0–255. For a real number x the value used is (min 255 (max 0 (exact-floor x))).

The optional argument α is the transparency. The default value is 1. Given a transparency outside the interval 0–1 whichever value of 0 and 1 is closest to α is used.

> (def red-ish  (make-color* 300 -12 42.3))
> (def purple   (make-color* "purple"))
> (color->list red-ish)

'(255 0 42 1.0)

> (color->list purple)

'(160 32 240 1.0)

> (with-window (window 0 1 0 1)
    (beside (color red-ish  (fill unitsquare))
            (color purple   (fill unitsquare))))

procedure

(color c p)  pict?

  c : (is-a?/c color%)
  p : pict?
(color f c p)  pict?
  f : real?
  c : (is-a?/c color%)
  p : pict?
In an expression context (color c p) is equivalent to (colorize p c) and (color f c p) is equivalent to (colorize p (color* f c)).

As a match pattern (color r g b a) matches both color% objects and color names (represented as strings). The variables r, g, and, b will be bound to the red, green, and, blue components of the color. The variable a will be bound to the transparency.

> (with-window (window 0 1 0 1)
    (apply beside (for/list ([f (in-range 0 11/10 1/10)])
                    (color f "red" (fill unitsquare)))))

> (require racket/match)
> (match "magenta"
    [(color r g b a) (list r g b a)])

'(255 0 255 1.0)

procedure

(color->list c)  (listof real?)

  c : color
Returns a list of the color components and the transparency of the color c. The color can be a color% object or a color name (string).

> (color->list "magenta")

'(255 0 255 1.0)

procedure

(color+ c1 c2)  (is-a?/c color%)

  c1 : color
  c2 : color
Returns a color% object, whose color components are the components of c1 and c2 added componentwise. The transparency is (min 1.0 (+ α1 α2)) where α1 and α2 the transparencies of the two colors.

> (color->list (color+ "red" "blue"))

'(255 0 255 1.0)

procedure

(color* k c)  (is-a?/c color%)

  k : real?
  c : color
Returns a color% object, whose color components are the components of c multiplied componentwise with k. The transparency is the same as in c.

> (color->list (color* 0.5 "blue"))

'(0 0 127 1.0)

procedure

(color-med t c1 c2)  (is-a?/c color%)

  t : real?
  c1 : color
  c2 : color
Interpolates linearly between the colors c1 and c2. For t=0 the color c1 is returned, and when t=1 the color c2 is returned.

> (with-window (window 0 1 0 1)
    (apply beside (for/list ([t (in-range 0 11/10 1/10)])
                    (color (color-med t "red" "yellow")
                           (fill unitsquare)))))

procedure

(color-med* t cs)  (is-a?/c color%)

  t : real?
  cs : (listof color)
Interpolates linearly between the colors in the list cs. For "t=0" corresponds to the first color in the list, and "t=1" corresponds to the last color.

> (with-window (window 0 1 0 1)
    (apply beside (for/list ([t (in-range 0 11/10 1/10)])
                    (color (color-med* t '("red" "yellow" "blue" "green"))
                           (fill unitsquare)))))

procedure

(change-red c r)  (is-a?/c color%)

  c : color
  r : real?

procedure

(change-blue c r)  (is-a?/c color%)

  c : color
  r : real?

procedure

(change-green c r)  (is-a?/c color%)

  c : color
  r : real?

procedure

(change-alpha c r)  (is-a?/c color%)

  c : color
  r : real?
Returns a color% object like c where one component has been changed to r.

> (color->list (change-red "blue" 42))

'(42 0 255 1.0)

3.3 Pict

 (require metapict/pict) package: metapict

3.3.1 Pict Adjusters

All images in MetaPict are represented as picts. A pict is a structure that holds information on how to draw a picture. A pict can be rendered to produce an image in various formats such as png, pdf, and, svg.

The standard library pict defines several functions to construct and manipulate picts. MetaPict provides and offers some extra operations. Since they are not MetaPict specific, they are also useful outside of the world of MetaPict.

A few of the pict operations are provided under new names. The basic concept in MetaPict is the curve. Therefore it makes sense for, say, circle to return a curve. In the pict library the name circle returns a pict, so to avoid a name conflict it is exported as circle-pict.

An attempt have been made to make the pict the last argument of all operations. This explains the existance of a few functions whose functionality overlap with the pict library.

The operations in this section operate on picts, so use draw to convert curves into picts.

3.3.1.1 Pen Adjusters

procedure

(color c p)  pict?

  c : color?
  p : pict?
Draws the pict p with a solid pen and brush of the color c. It is equivalent to (pencolor c (brushcolor c p)).
> (color "red" (filldraw unitcircle))

procedure

(pencolor c p)  pict?

  c : color?
  p : pict?
Draws the pict p with a solid pen color c. The brush is not affected by pencolor.
> (penwidth 4
    (beside (pencolor "red" (brushcolor "orange" (filldraw unitcircle)))
            (pencolor "red" (filldraw unitcircle))))

procedure

(penwidth w p)  pict?

  w : real?
  p : pict?
Draws the pict p with a pen of width w, a real number between 0 and 255. Same as linewidth.

TODO: What unit?!?!
> (apply beside
         (for/list ([w 5])
           (penwidth w (draw unitcircle))))

procedure

(penscale s p)  pict?

  s : real?
  p : pict?
Adjusts the current pen width to a width s times wider than the current, then draws the pict p.
> (beside (penwidth 3 (penscale 2 (draw unitcircle)))
          (penscale 3 (penwidth 2 (draw unitcircle)))
                      (penwidth 6 (draw unitcircle)))

procedure

(penstyle s p)  pict?

  s : style?
  p : pict?
Adjusts the current pen style, and then draws the pict p. The available styles are: 'transparent 'solid 'hilite 'dot 'long-dash 'short-dash 'dot-dash. Note: The pen% documentation mentions a few xor- styles, these are no longer supported by Racket.
> (define (styled-circle style)
    (draw (color "red" (filldraw unitsquare))
          (penstyle style (draw unitcircle))
          (label-bot (~a style) (pt 0 0))))
> (def styles1 '(solid transparent hilite))
> (def styles2 '(dot short-dash long-dash dot-dash))
> (above (beside* (map styled-circle styles1))
         (beside* (map styled-circle styles2)))

procedure

(pencap c p)  pict?

  c : cap?
  p : pict?
Adjusts the current pen cap, and then draws the pict p. The available caps are: 'round, 'projecting, and, 'butt. The cap determines how the end of curves are drawn. The default pen is 'round.
> (define (squiggle cap)
    (def l (curve (pt -1/2 0) -- (pt 0 0) .. (pt 1/2 1/2)))
    (penwidth 20
      (draw (pencap cap   (color "red"   (draw l)))
            (pencap 'butt (color "black" (draw l)))
            (label-bot (~a cap) (pt 0 -1/2)))))
> (def caps '(round projecting butt))
> (beside* (map squiggle caps))

procedure

(penjoin j p)  pict?

  j : join?
  p : pict?
Adjusts the current pen join, and then draws the pict p. The available joins are: 'round, 'bevel, and, 'miter. The join determines how the transition from one curve section to the next is drawn. The default join is 'round.

Note: If you want to draw a rectangle with a crisp 90 degree outer angle, then use the 'miter join.
> (define (squiggle join)
    (def l (curve (pt -1/2 0) -- (pt 0 0) .. (pt 1/2 1/2)))
    (draw (penwidth 40 (penjoin join (draw l)))
          (penwidth 2 (color "red" (draw (circle (pt 1/4 -1/3) 1/3))))
          (label-bot (~a join) (pt 0 -1/2))))
> (def joins '(round bevel miter))
> (beside* (map squiggle joins))

procedure

(pen a-pen p)  pict?

  a-pen : pen%
  p : pict?
Use the pen a-pen as the current pen, then draw the pict p.

> (def teacher-pen
    (new pen% [color "red"]  [width 1]     [style 'solid]
              [cap   'round] [join 'round] [stipple #f]))
> (pen teacher-pen (draw unitcircle))

procedure

(dashed p)  pict

  p : pict?
Use the pen style 'long-dash to draw the pict p
> (dashed (draw unitcircle))

procedure

(dotted p)  pict

  p : pict?
Use the pen style 'dot to draw the pict p
> (dotted (draw unitcircle))

3.3.1.2 Brush Adjusters

procedure

(brush b p)  pict

  b : brush%
  p : pict?
Use the brush b to draw the pict p.
> (def hatch (new brush% [color "black"] [style 'crossdiag-hatch]))
> (brush hatch (filldraw unitcircle))

procedure

(brushcolor c p)  pict

  c : color?
  p : pict?
Adjust the brush to use the color b, then draw the pict p.
> (brushcolor "red" (fill unitcircle))

procedure

(brushstyle s p)  pict

  s : style?
  p : pict?
Adjust the brush to use the style s, then draw the pict p. The example below shows the available styles. The brush style hilite is black with a 30% alpha.
> (define (styled-circle style)
    (draw (color "red" (filldraw (scaled 0.7 unitsquare)))
          (brushcolor "black" (brushstyle style (fill (scaled 0.7 unitcircle))))
          (brushcolor "white" (draw (label-bot (~a style) (pt 0 -0.7))))))
> (def styles1 '(solid transparent hilite))
> (def styles2 '(bdiagonal-hatch fdiagonal-hatch crossdiag-hatch))
> (def styles3 '(horizontal-hatch vertical-hatch cross-hatch))
> (above (beside* (map styled-circle styles1))
         (beside* (map styled-circle styles2))
         (beside* (map styled-circle styles3)))

procedure

(brushstipple s p)  pict

  s : style?
  p : pict?
Adjust the brush to use the stipple s, then draw the pict p.

> (set-curve-pict-size 256 256)
> (define stipple (bitmap "texture.jpeg"))
> (with-window (window -1 1 -1 1)
    (beside stipple (blank 64 64)
            (brushstipple (pict->bitmap stipple)
                          (fill (circle 1)))))

procedure

(brushgradient TODO:TO-BE-DETERMINED)  pict

  TODO:TO-BE-DETERMINED : gradient?
Use a gradient as brush, then draw the pict p.

procedure

(save-pict filename p type)  (void)

  filename : path?
  p : pict?
  type : 'png
Save the pict p as filename. Default is png, other formats include svg, pdf, xbm, xpm and bmp. JPEG is not included.

procedure

(margin r p)  pict?

  r : real?
  p : pict?
Equivalent to (inset p r).

3.3.1.3 Pict Combiners

procedure

(above p ...)  pict?

  p : pict?
Draw the picts p ... above each other. If the picts are of different widths, center them.

Same as vc-append

procedure

(beside p ...)  pict?

  p : pict?
Draw the picts p ... beside each other. If the picts are of different heights, center them.

Same as hc-append

procedure

(above* ps)  pict?

  ps : list?
Draw the picts in the list ps above each other. The first element is on top. If the picts are of different widths, center them.

Same as (apply vc-append ps)

procedure

(beside* ps)  pict?

  ps : list?
Draw the picts in the list ps beside each other. The first element is on the left If the picts are of different heights, center them.

Same as (apply hc-append ps)

3.4 Bezier Curves

 (require metapict/bez) package: metapict

A Bezier curve from point A to point B with control points A+ and B- is represented as an instance of a bez structure: (bez A A+ B- B).

Graphically such a curve begins at point A and ends in point B. The curve leaves point A directed towards the control point A+. The direction in which the curve enters the end point B is from B-.

The points A and B are referred to as start and end point of the Bezier curve. The points A+ and B- are refererred to as control points. The point A+ is the post control of A and the point B- is the pre control of B.

Most users will not have reason to work with bez structures directly. The curve constructor is intended to cover all use cases.

Each point on the Bezier curve corresponds to a real number t between 0 and 1. The correspondence is called a parameterization of the curve. The number t is called a parameter. Thus for each value of the parameter t between 0 and 1, you get a point on the curve. The parameter value t=0 corresponds to the start point A and the parameter value t=1 corresponds to the end point.

Let’s see an example of a Bezier curve and its construction.

procedure

(point-of-bez b t)  pt?

  b : bez?
  t : real?
Return the point on the Bezier curve b that corresponds to the parameter value t. De Casteljau’s algorithm is used to compute the point.
> (def b (bez (pt 0 0) (pt 0 1) (pt 3 2) (pt 5 0)))
> (for/list ([t '(0 1/2 1)])
    (point-of-bez b t))

(list (pt 0 0) (pt 7/4 9/8) (pt 5 0))

procedure

(bez~ b1 b2 [ε])  boolean?

  b1 : bez?
  b2 : bez?
  ε : real? = 0.0001
Returns #t if the defining points of the two Bezier curves are within pairwise distance ε of each other. The default value of ε=0.0001 was chosen to mimick the precision of MetaPost.
> (bez~ (bez (pt 0     0) (pt 0 1) (pt 3 2) (pt 5 0))
        (bez (pt #i5e-05 0) (pt 0 1) (pt 3 2) (pt 5 0)))

#t

procedure

(bez-reverse b)  bez

  b : bez?
Returns a bez representing a Bezier curve whose graph is the same as the graph of the Bezier curve b, but has the reverse orientation.
> (def b     (bez (pt 0 0) (pt 0 1) (pt 3 2) (pt 5 0)))
> (def (γ t) (point-of-bez              b  t))
> (def (φ t) (point-of-bez (bez-reverse b) t))
> (def ts    (in-range 0 5/4 1/4))
> (cons 'γ (for/list ([t ts]) (γ t)))

(list 'γ (pt 0 0) (pt 1/2 45/64) (pt 7/4 9/8) (pt 27/8 63/64) (pt 5 0))

> (cons 'φ (for/list ([t ts]) (φ t)))

(list 'φ (pt 5 0) (pt 27/8 63/64) (pt 7/4 9/8) (pt 1/2 45/64) (pt 0 0))

procedure

(split-bez b t)  
bez? bez?
  b : bez?
  t : real?
Given a Bezier curve b from p0 to p3 with control points p1 and p2, split the Bezier curve at time t in two parts b1 (from p0 to b(t)) and b2 (from b(t) to p3), such that (point-of-bez b1 1) = (point-of-bez b2 0) and the graphs of b1 and b2 gives the graph of b.
> (def b        (bez (pt 0 0) (pt 0 1) (pt 3 2) (pt 5 0)))
> (defv (b1 b2) (split-bez b 1/3))
> (with-window (window -1 6 -1 6)
    (penwidth 4
      (draw (color "red"  (draw b1))
            (color "blue" (draw b2)))))

procedure

(bez-subpath b t0 t1)  bez?

  b : bez?
  t0 : real?
  t1 : real?
Given a Bezier curve b return a new Bezier curve c, such that c(0)=b(t0) and c(1)=b(t1) and such that the graph of c is a subset of the graph of b.
> (def b (bez (pt 0 0) (pt 0 1) (pt 3 2) (pt 5 0)))
> (with-window (window -1 6 -1 6)
    (for/draw ([t (in-range 0 5/4 1/4)]
               [c '("red" "blue" "green" "magenta")])
    (penwidth 4
      (beside (draw b)
              (color c (draw (bez-subpath b t (+ t 1/4))))))))

Note: The example shows that the parameterization is not an arc-length (aka unit-speed) parameterization.

procedure

(bez-intersection-point b1 b2)  (or pt? #f)

  b1 : bez?
  b2 : bez?
If the graphs of the Bezier curves intersect, then their first intersection point is returned. If there are no intersections, then #f is returned.
> (def b1 (bez (pt 0.0 0.0) (pt 1.0 1.0) (pt 2.0 2.0) (pt 3.0 3.0)))
> (def b2 (bez (pt 0.0 3.0) (pt 1.0 2.0) (pt 2.0 1.0) (pt 3.0 0.0)))
> (defv (p) (bez-intersection-point b1 b2))
> p

(pt 1.4999999999999996 1.4999999999999996)

> (def b3 (bez (pt 0 4) (pt 1 4) (pt 2 4) (pt 3 4)))
> (bez-intersection-point b1 b3)

#f

> (with-window (window 0 5 0 5)
    (draw b1 b2 (color "red" (penwidth 8 (draw p))) b3))

procedure

(bez-intersection-times b1 b2)  
real? real?
  b1 : bez?
  b2 : bez?
If the graphs of the Bezier curves intersect numbers t1 and t2 such that b1(t1)=b2(t2) are returned. If there are more than one intersection, the parameter values for the first intersection is returned. If no such numbers exist the result is (values #f #f).
> (def b1 (bez (pt 0 0) (pt 1 1) (pt 2 2) (pt 3 3)))
> (def b2 (bez (pt 0 3) (pt 1 2) (pt 2 1) (pt 3 0)))
> (defv (t1 t2) (bez-intersection-times b1 b2))
> (defv (p1 p2) (values (point-of-bez b1 t1) (point-of-bez b2 t2)))
> (list p1 p2)

(list

 (pt 1.4999999999999998 1.4999999999999998)

 (pt 1.4999999999999998 1.5000000000000002))

> (def b3 (bez (pt 0 4) (pt 1 4) (pt 2 4) (pt 3 4)))
> (bez-intersection-times b1 b3)

#f

> (with-window (window 0 5 0 5)
    (draw b1 b2 (color "red" (penwidth 8 (draw p1))) b3))

procedure

(bez-intersection-point-and-times b1 b2)

  (or (list pt? real? real?) #f)
  b1 : bez?
  b2 : bez?
If the graphs of the Bezier curves intersect, returns a list of the intersection point and two numbers t1 and t2 such that b1(t1)=b2(t2). If there are more than one intersection, the parameter values for the first intersection is returned. If no such numbers exist the result is (values #f #f).
> (def b1 (bez (pt 0.0 0.0) (pt 1.0 1.0) (pt 2.0 2.0) (pt 3.0 3.0)))
> (def b2 (bez (pt 0.0 3.0) (pt 1.0 2.0) (pt 2.0 1.0) (pt 3.0 0.0)))
> (bez-intersection-point-and-times b1 b2)

(list

 (pt 1.4999999999999996 1.4999999999999996)

 0.4999999999999999

 0.4999999999999999)

> (defm (list p t1 t2) (bez-intersection-point-and-times b1 b2))
> (def b3 (bez (pt 0 4) (pt 1 4) (pt 2 4) (pt 3 4)))
> (bez-intersection-times b1 b3)

#f

> (with-window (window 0 5 0 5)
    (draw b1 b2 (color "red" (penwidth 8 (draw p))) b3))

procedure

(draw-bez dc    
  b    
  [#:transformation t    
  #:pen-transformation pent])  (void)
  dc : (is-a dc<%>)
  b : bez?
  t : trans? = #f
  pent : trans? = #f
Draws the Bezier curve b on a drawing context dc with optional transformation t and pen-transformation pent.

procedure

(draw-bezs dc    
  bs    
  [#:transformation t    
  #:pen-transformation pent])  (void)
  dc : (is-a dc<%>)
  bs : (listof bez?)
  t : trans? = #f
  pent : trans? = #f
Draws the Bezier curves bs on the drawing context dc with optional transformation t and pen-transformation pent.

procedure

(bez->dc-path b [t])  (is-a? dc<%>)

  b : bez?
  t : trans? = #f
Convert the Bezier curve b into a dc-path%. If the optional transformation t is present, it is applied to b before the conversion.

procedure

(bezs->dc-path bs [t])  (is-a? dc<%>)

  bs : (listof bez?)
  t : trans? = #f
Convert the "consecutive" Bezier curves bs into a dc-path%. If the optional transformation t is present, it is applied to the bs before the conversion.

procedure

(bez/dirs+tensions p0 p3 w0 w3 [τ0 τ3])  bez?

  p0 : pt?
  p3 : pt?
  w0 : vec?
  w3 : vec?
  τ0 : real? = 1
  τ3 : real? = 1
Returns a bez structure representing a Bezier curve from p0 to p3 that leaves p0 in the direction of w0 and arrives in p3 from the the direction of w0 with tensions t0 and t3 respectively.
> (defv (p0 p3 w0 w3 τ0 τ3) (values (pt 0 0) (pt 5 0) (vec 0 1) (vec 0 -1) 1 1))
> (def b (bez/dirs+tensions p0 p3 w0 w3 τ0 τ3))
> b

(bez

 (pt 0 0)

 (pt 2.041077998578922e-16 3.333333333333333)

 (pt 5.000000000000001 -3.333333333333335)

 (pt 5 0))

> (with-window (window -5 11 -5 11) (draw b))

procedure

(control-points p0 p3 θ φ τ0 τ3)  bez?

  p0 : pt?
  p3 : pt
  θ : real?
  φ : real?
  τ0 : real?
  τ3 : real?
Returns a bez structure representing a Bezier curve from p0 to p3 that leaves p0 with an angle of θ and arrives in p3 with an angle of φ with tensions t0 and t3 respectively.
> (defv (p0 p3 θ φ τ0 τ3) (values (pt 0 0) (pt 5 0) pi/2 -pi/2 1 1))
> (defv (p1 p2) (control-points p0 p3 θ φ τ0 τ3))
> (def b (bez p0 p1 p2 p3))
> b

(bez

 (pt 0 0)

 (pt 2.041077998578922e-16 3.333333333333333)

 (pt 5.0 -3.333333333333333)

 (pt 5 0))

> (with-window (window -5 11 -5 11) (draw b))

3.5 Curves

 (require metapict/curve) package: metapict

General curves are drawn by gluing together a series of Bezier curves. Conceptually a curve consists of multiple Bezier curves glued together. Such a curve can be either open or closed (a loop).

The representation of a curve consists simply of a list of Bezier curves and a flag indicating whether the curve is closed. For two consecutive Bezier curves in the list, the end point of the first and the start point of the second must be equal.

The actual representation uses a curve: structure.

struct

(struct curve: (closed? bezs)
    #:extra-constructor-name make-curve:)
  closed? : boolean?
  bezs : (listof bez?)
The reflection name of curve: is 'curve, so the names is printed without the colon suffix.

Most users will not have reason to work with curve: structures directly. The curve constructor is intended to cover all use cases. The constructor can be used to construct both curved as well as straight lines.

The syntax of curve will be detailed later, but let’s look at some examples. First when multiple points separated by .. are given, a smooth curve through the points will be constructed: (curve p0 .. p1 .. p2 .. p3 .. p4).

As a concrete example, let’s look at the points (0,0) (60,40) (40,90) (10,70) (30,50).

> (def p0 (pt 0   0))
> (def p1 (pt 60 40))
> (def p2 (pt 40 90))
> (def p3 (pt 10 70))
> (def p4 (pt 30 50))
> (define (label-points)
    (for/draw ([i '(0 1 2 3 4)]
               [p (list p0 p1 p2 p3 p4)]
               [d (list (lft) (rt) (top) (lft) (top))])
      (dot-label (~a i) p d)))
> (set-curve-pict-size 120 120)
> (with-window (window -20 100 -20 100)
    (draw (curve p0 .. p1 .. p2 .. p3 .. p4)
          (label-points)))

In order to produce a closed curve, end the curve specification with .. cycle.
> (with-window (window -20 100 -20 100)
    (draw (curve p0 .. p1 .. p2 .. p3 .. p4 .. cycle)
          (label-points)))

Use -- instead of .. if you want to connect two points with a straight line.
> (with-window (window -20 100 -20 100)
    (draw (curve p0 .. p1 .. p2 -- p3 .. p4)
          (label-points)))

There is more to the curve constructor (it is possible to specify directions in which a curve enters and leaves a points), but let’s return to operations defined on curves.

The number of Bezier curves used to represent the curve is called the length. The function curve-length returns this length n.

Each point on a curve corresponds to a real number t between 0 and n. The correspondence is called a parameterization of the curve. The number t is called a parameter. Thus for each value of the parameter t between 0 and n, you get a point on the curve. The parameter value t=0 corresponds to the start point A and the parameter value t=n corresponds to the end point.

procedure

(curve-length c)  integer?

  c : curve?
Returns the number of Bezier curves used to represent the curve c. This number n is also the end of the interval [0,n] used to parameterize the curve.
> (curve-length (curve p0 .. p1 .. p2 .. p3 .. p4))

4

Given a curve c one can compute points on the curve corresponding to a parameter value between 0 and the curve length with the function point-of.

procedure

(point-of c t)  pt?

  c : curve?
  t : real?
Given a curve c and a number t the function point-of computes the point corresponding to the parameter value t. Here (point-of c 0) and (point-of c (curve-length c)) will return the start and and end point of the curve respectively.
> (let ()
    (define c (curve p0 .. p1 .. p2 .. p3 .. p4))
    (define (label-parameter-values)
      (for/draw ([i '(0 1 2 3 4)]
                 [d (list (lft) (rt) (top) (lft) (top))])
        (define p (point-of c i))
        (dot-label (~a "t=" i) p d)))
    (set-curve-pict-size 120 120)
    (with-window (window -20 100 -20 100)
      (draw c
            (label-parameter-values))))

Since the start and end point of a curve are used often, the following short hands are available:

procedure

(start-point c)  pt?

  c : curve?
Returns the start point of a curve.

procedure

(end-point c)  pt?

  c : curve?
Returns the end point of a curve.

> (let ()
    (def c (curve (pt 0 0) .. (pt 1 3) .. (pt 2 5)))
    (list (start-point c) (end-point c)))

(list (pt 0 0) (pt 2.0 5.0))

Given a curve c parameterized from 0 to n from a start point to an end point, one can use curve-reverse to create a curve where the parameterization is reversed.

procedure

(curve-reverse c)  curve?

  c : curve?
Returns a curve throught the same points as the curve c. In the parameterization 0 corresponds to the end point of c and (curve-length c) corresponds to start point og c.
> (let ()
    (def c (curve (pt 0 0) .. (pt 1 3) .. (pt 2 5)))
    (def r (curve-reverse c))
    (list (start-point c) (end-point c))
    (list (start-point r) (end-point r)))

(list (pt 2 5) (pt 0.0 0.0))

Another way to produce a new curve is to join two existing curves.

procedure

(curve-append c1 c2)  curve?

  c1 : curve?
  c2 : curve?
Given two curves c1 and c2 where the end point of c1 is the start point of c2, the function curve-append will produce a curve that combines c1 and c2.
> (let ()
    (def c1 (curve (pt 0 0) .. (pt 1 1)))
    (def c2 (curve (pt 1 1) .. (pt 2 0)))
    (def c  (curve-append c1 c2))
    (with-window (window 0 2 0 2)
       (draw (linewidth 6 (color "red"   (draw c1)))
             (linewidth 6 (color "blue"  (draw c2)))
             (linewidth 2 (color "white" (draw c))))))

We will now turn to operations involving two curves. The first problem we will look at is intersections between two curves.

procedure

(intersection-times c1 c2)  (or #f number?)

  c1 : curve?
  c2 : curve?
If the two curves c1 and c2 have an intersection point, the function will return times (parameter values) t and u such that c1(t)=c2(u). If no intersection point is found #f is returned.
> (let ()
    (def c1 (curve (pt 0 0) .. (pt 2 3)))
    (def c2 (curve (pt 0 2) .. (pt 4 1)))
    (defv (t u) (intersection-times c1 c2))
    (list (list 't t 'u u)
          (list (point-of c1 t) (point-of c2 u))))

(list

 '(t 0.5714285714285715 u 0.2857142857142857)

 (list

  (pt 1.1428571428571432 1.7142857142857146)

  (pt 1.1428571428571428 1.7142857142857144)))

procedure

(intersection-point c1 c2)  (or #f pt?)

  c1 : curve?
  c2 : curve?
Return an intersection point of the curves c1 and c2. If no intersection point exists, the false value #f is returned.
> (let ()
    (def c1 (curve (pt 0 0) .. (pt 2 4)))
    (def c2 (curve (pt 0 4) .. (pt 4 0)))
    (def P  (intersection-point c1 c2))
    (with-window (window 0 4 0 4)
      (draw c1 c2 P (dot-label-rt "P" P))))

If you need both the intersection point and the two times, then fear not:

procedure

(intersection-point-and-times c1 c2)

  (or #f (list pt? number? number?))
  c1 : curve?
  c2 : curve?
Return a list (list P t u) where P is an intersection point of the two curves c1 and c2 – and the numbers t and u are times such that P=c1(t)=c2(t).

Finally to get all intersection points of two curves, use intersection-points.

procedure

(intersection-points c1 c2)  (list-of pt?)

  c1 : curve?
  c2 : curve?
Returns a list of all intersection points of the two curves c1 and c2. If no intersection point exists, the empty list is returned.
> (let ()
    (def c1 (curve (pt 0 0) .. (pt 3 1) .. (pt 2 3) .. (pt 2 1) .. (pt 2.5 1)))
    (def c2 (curve (pt 0 1) .. (pt 1 3) .. (pt 3 0) .. (pt 4 1)))
    (def Ps  (intersection-points c1 c2))
    (with-window (window -1 4 -1 4)
      (draw c1
            (linewidth 2 (color "blue" (draw c2)))
            (color "red" (for/draw ([P Ps])
                           (dot-label "" P (cnt)))))))

If a curve is too long, the function subcurve can be used to make a shorter one.

procedure

(subcurve c t0 t1)  curve?

  c : curve?
  t0 : number?
  t1 : number?
Produces a curve from c(t0) to c(t1). If t0<t1 the direction is reversed.
> (let ()
    (def c (curve (pt 0 0) up .. (pt 1 3) .. (pt 2 0) .. (pt 3 3) .. (pt 3 2)))
    (def s (subcurve c 1 3))
    (with-window (window -1 4 -1 4)
      (draw (linewidth 6 (color "red"   (draw c)))
            (linewidth 2 (color "white" (draw s))))))

Instead of subcurve one can use cut-before and cut-after to make curves shorter.

procedure

(cut-before c1 c2)  curve?

  c1 : curve?
  c2 : curve?
Cut the part of c1 that lie before the "first" intersection point of the two curves.
> (let ()
    (def c1 (curve (pt 0 0) .. (pt 2 2)))
    (def c2 (curve (pt 0 2) .. (pt 2 0)))
    (def c  (cut-before c1 c2))
    (with-window (window 0 2 0 2)
      (draw (linewidth 6 (color "red"   (draw c1)))
            (linewidth 6 (color "blue"  (draw c2)))
            (linewidth 2 (color "white" (draw c))))))

procedure

(cut-after c1 c2)  curve?

  c1 : curve?
  c2 : curve?
Cut the part of c1 that lie after the "first" intersection point of the two curves.
> (let ()
    (def c1 (curve (pt 0 0) .. (pt 2 2)))
    (def c2 (curve (pt 0 2) .. (pt 2 0)))
    (def c  (cut-after c1 c2))
    (with-window (window 0 2 0 2)
      (draw (linewidth 6 (color "red"   (draw c1)))
            (linewidth 6 (color "blue"  (draw c2)))
            (linewidth 2 (color "white" (draw c))))))

procedure

(post-control c t)  pt?

  c : curve?
  t : number?
The curve c consists of a number of Bezier curves. Return the first control point after the paramter value t. See the MetaFontBook page 134.

procedure

(pre-control c t)  pt?

  c : curve?
  t : number?
The curve c consists of a number of Bezier curves. Return the first control point before the paramter value t. See the MetaFontBook page 134.

procedure

(direction-of c t)  vec?

  c : curve?
  t : number?
Return the direction of which the curve c is moving at time t.
> (let ()
    (def c (curve (pt 1 0) .. (pt 1 3) .. (pt 4 1)))
    (def p (point-of     c 1))
    (def d (direction-of c 1))
    (with-window (window 0 6 0 6)
      (draw c
            (draw-arrow (curve p .. (pt+ p d)))
            (dot-label-top "P" p))))

procedure

(cyclic? c)  boolean?

  c : curve?
Return the true value #t if the curve c is a closed curve.
> (let ()
    (def c1 (curve (pt 1 0) .. (pt 1 3)))
    (def c2 (curve (pt 1 0) .. (pt 1 3) .. cycle))
    (list (cyclic? c1) (cyclic? c2)))

'(#f #t)

procedure

(intercurve α c1 c2)  curve?

  α : number?
  c1 : curve?
  c2 : curve?
Given two curves c1 and c2 of the same length, the call (intercurve α c1 c2) will return a curve "between" the two curves. For α=0 the first curve is return and for α=1 the second curve is returned.

In other words we can interpolate curves.

> (let ()
    (def heart ; a heart shaped curve
      (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
             .. (pt 100 0) (curl 0) .. up (pt 5 125) .. right (pt 60 178) .. (pt 100 162)))
  
    (def fig-heart ; a heart pict
      (with-window (window -100 300 -100 300) (draw heart)))
  
    (def heart-line ; used to interpolate from line to heart below
      (curve (pt 100 0) -- (pt 300 0) -- (pt 200 0) -- (pt 100 0) -- (pt 0 0)
             -- (pt -100 0) -- (pt 100 0)))
  
    (def fig-line-to-heart ; shows interpolation between two curves of same length
      (with-window (window -100 300 -100 300)
        (draw* (for/list ([i (in-range 8)])
                 (def α (/ i 8))
                 (color α "red"
                        (draw (intercurve α heart-line heart)))))))
  
    (beside fig-heart fig-line-to-heart))

procedure

(curve <path-specification-fragment ...)  curve:?

  <path-specification-fragment : <path-specification-fragment
The overall purpose of curve is to find a "nice" (possibly closed) curve through a given set of points. If the user wants, he can specify how the curve enters and leave a point.

Concretely curve converts a path specification into a list of bezier curves. In the most basic form, a call to curve has one of these forms (but see path operations for more options):

(curve p0 j0 p1 j1 ... pn)

(curve p0 j0 p1 j1 ... pn jn)

(curve p0 j0 p1 j1 ... pn jn cycle)

Here p0, p1, ... are points and j0, j0, ... are path joins. A path join describes how the two points on either side of the path join are to be connected. The most common path joins are .., and --.

> (defv (A B C D) (values (pt -0.3 -0.3) (pt 0.3 -0.3) (pt 0.3 0.3) (pt -0.3 0.3)))
> (let ()
    (beside*
     (list (draw (curve A ..  B ..  C ..  D))
           (draw (curve A --  B --  C --  D))
           (draw (curve A ..  B ..  C ..  D .. cycle))
           (draw (curve A --  B --  C --  D .. cycle)))))

The function curve is a smart constructor, so it accepts a wider range of inputs and rewrites the given path specification into a basic one as the step of computing the list of Bezier curves. In particular you can optionally use direction specifiers before and/or after a point, to control the direction the curve will enter and leave a point.

... j- ds- p ds+ j+ ...

Here the - and + indicates "before" and "after" the point.

Finally curve supports path operations. A path operation f can be placed after a point p:

... p f more ...

In order to process a path specification, curve will make a first pass to remove path operations. When ... p f more ... is encountered, the path operation (a function) f will be called with (list p f more ...) as input. The output is a new path specification where f has been removed.

A few path operations such as /-, -/, --++, -arc, -rectangle are builtin. A user can define his own path operations.

Given two points and a path join (and some extra pieces of information), curve must compute the two control points between the two points.

The standard path joins are .. and --.

value

.. : tension-and? = (tension-and 1 1)

value

-- : full-join? = (full-join (curl 1) .. (curl 1))

Use .. to get "medium bendiness" and -- to get a straight line.

value

cycle : 'cycle

The value used to indicate a closed curve is 'cycle.

struct

(struct join ())

struct

(struct controls-and join (c- c+))

  c- : pt?
  c+ : pt?

struct

(struct tension-and join (τ- τ+))

  τ- : real?
  τ+ : real?

struct

(struct full-join join (ds- j ds+))

  ds- : direction-specifier?
  j : join?
  ds+ : direction-specifier?

procedure

(tension τ)  tension-and?

  τ : real?
These path joins represent the most basic joins. They are not meant to be used directly (although controls-and are occasionally useful), but are used internally.

The "tension" controls how "stiff/bendable" the curve is.

The tension numbers τ- and τ+ are real numbers. If a tension τ is positive, that tension is used. If a tension τ is negative, it is interpreted as "at least abs(τ)". The default tension is 1. A tension of ∞ gives a (almost) linear curve.

Note: (tension τ) will return (tension-and τ τ), this represent the same tension before and after a point.

> (defv (A B C) (values (pt -0.3 -0.3) (pt -0.3 0.0) (pt 0.3 0.3)))
> (let ()
    (define points (penwidth 4 (color "red" (draw A B C))))
    (beside*
     (list (draw (curve A ..  B (tension  3/4)  C) points)
           (draw (curve A ..  B (tension  1)    C) points)
           (draw (curve A ..  B (tension  2)    C) points)
           (draw (curve A ..  B (tension  3)    C) points)
           (draw (curve A ..  B (tension 10)    C) points))))

The path join controls-and explicitly give the Bezier control points between the two points. This can be used, if a piece of a curve has been computed elsewhere.

> (defv (A B C D) (values (pt -0.3 -0.3) (pt -0.3 0.0) (pt 0.3 0.3) (pt 0.6 0.3)))
> (let ()
    (define points (penwidth 4 (color "red" (draw A B C D))))
    (beside (draw (curve A (controls-and B C) D) points)
            (draw (bez A B C D) points)))

The last basic path join is (full-join ds- j ds+) which allows one to specify an direction ds- for the curve to enter the point and an direction ds+ to leave the points as well as the tension.

A direction specifier can either be empty (represented by #f), an explicit direction (an vec?) or an curl amount.

struct

(struct curl (amount))

  amount : real?
Warning: Currently curl doesn’t behave exactly like MetaPost (due to a bug in Metapict).

Note that curve will a join with direction specifiers before and after a join into a full-fjoin.

As an example

... p0 ds0+ j0 ds1- p1 ...

will be rewritten as (full-join ds0+ j0 ds1-).

> (defv (A B C) (values (pt -0.3 -0.3) (pt -0.3 0.0) (pt 0.3 0.3)))
> (let ()
    (define points (penwidth 4 (color "red" (draw A B C))))
    (define (fj v1 v2) (full-join v1 .. v2))
    (beside*
     (list (draw (curve A ..  B (fj (vec  0 1) (vec 1 0)) C) points)
           (draw (curve A ..  B (fj (vec  0 1) (vec 0 1)) C) points)
           (draw (curve A ..  B (fj (vec -1 0) (vec 0 1)) C) points)
           (draw (curve A ..  B (fj (vec -1 0) (vec 1 0)) C) points)
           (draw (curve A ..  B (fj (vec  1 1) (vec 1 1)) C) points))))

> (defv (A B C) (values (pt -0.3 -0.3) (pt -0.3 0.0) (pt 0.3 0.3)))
> (let ()
    (define points (penwidth 4 (color "red" (draw A B C))))
    (define (fj v1 v2) (full-join v1 .. v2))
    (beside*
     (list (draw (curve A ..  B (fj #f (vec 1 0)) C) points)
           (draw (curve A ..  B (fj #f (vec 0 1)) C) points)
           (draw (curve A ..  B (fj #f (vec 0 1)) C) points)
           (draw (curve A ..  B (fj #f (vec 1 0)) C) points)
           (draw (curve A ..  B (fj #f (vec 1 1)) C) points))))

3.6 Transformations

 (require metapict/trans) package: metapict

Given a curve c, one can use a transformation to calculate a new curve c with a different shape or placement. Transformations such as scaling, reflection, rotation and translation appear again and again in graphics programs. These transformations are all affine transformations. An important property of affine transformations is that the composition of two affine transformations is a new affine transformation. If a series of transformations are to be applied to a large set of points or curves, it is more efficient to compute the composed transformation first, and then apply that to the large set.

In MetaPict a transformation can be applied to instances of curve, pt, vec and bez. Furthermore a transformation can be applied to another transformation.

> (def c (curve (pt -1/2 1/2) -- (pt -1/2 0) -- (pt 1/2 0)))
> (penwidth 4
            (draw (color "black" (penwidth 8 (draw c)))
                  (color "red"   (draw ((shifted 1/2 1/2) c)))
                  (color "blue"  (draw ((rotated (/ π 2)) c)))
                  (color "green" (draw (((shifted 0 -0.1) flipy) c)))))

Mathematically an affine transformation transforms a point (x,y) into the point

x = ax + cy + e
y = bx + dy + f

where a, b, c, d, e and f are real numbers.

struct

(struct trans (a b c d e f)
    #:extra-constructor-name make-trans)
  a : real?
  b : real?
  c : real?
  d : real?
  e : real?
  f : real?
The structure trans represents the affine transformation:

x = ax + cy + e
y = bx + dy + f

The trans struct is applicable and can be applied to instances of curve, pt, vec and bez. Also a trans can be applied to another trans.

The most used transformations have specialized constructors (see below), so trans is seldom used directly.

> (def t (shifted 10 20))
> (t (pt 1 2))

(pt 11 22)

> (t (vec 1 2))

(vec 11 22)

> (t (curve (pt 1 2) -- (pt 3 4)))

(curve

 #f

 (list

  (bez

   (pt 11 22)

   (pt 11.666666666666666 22.666666666666668)

   (pt 12.333333333333334 23.333333333333332)

   (pt 13 24))))

> t

(trans 1 0 0 1 10 20)

> (t t)

(trans 1 0 0 1 20 40)

> ((t t) t)

(trans 1 0 0 1 30 60)

value

identity : trans? = (trans 1 0 0 1 0 0)

The identity transformation.

value

rotated90 : trans? = (trans 0 1 -1 0 0 0)

Rotate 90 degrees counterclockwise.

value

rotated180 : trans? = (trans -1 0 0 -1 0 0)

Rotate 180 degrees counterclockwise.

value

rotated270 : trans? = (trans 0 -1  1  0 0 0)

Rotate 270 degrees counterclockwise.

> (def c (curve (pt 0 0) -- (pt 1 0)))
> (penwidth 4
    (draw (color "black" (draw (identity   c)))
          (color "red"   (draw (rotated90  c)))
          (color "blue"  (draw (rotated180 c)))
          (color "green" (draw (rotated270 c)))))

value

flipx : trans? = (trans 1  0  0 -1 0 0)

Reflect about the x-axis.

value

flipy : trans? = (trans -1  0  0 1 0 0)

Reflect about the y-axis.

> (def c (curve (pt 1/4 1/4) -- (pt 1 1)))
> (penwidth 4
    (draw (color "black" (draw (identity c)))
          (color "red"   (draw (flipx    c)))
          (color "blue"  (draw (flipy    c)))))

procedure

(slanted a)  trans?

  a : real?
The transformation (trans 1 0 a 1 0 0).

(x,y) slanted a = (x+ay,y)

.

> (def c (curve (pt 0 0) -- (pt 0 1)))
> (penwidth 4
    (draw (color "black" (draw (identity    c)))
          (color "red"   (draw (slanted 1/3 c)))
          (color "blue"  (draw (slanted 1/2 c)))
          (color "green" (draw (slanted 1   c)))))

procedure

(scaled a)  trans?

  a : real?
The transformation (trans a 0 0 a 0 0).

(x,y) scaled a = (ax,ay)

> (def c
    (scaled 1/100
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw             c))
          (color "red"   (draw (scaled 3/4 c)))
          (color "blue"  (draw (scaled 1/2 c)))
          (color "green" (draw (scaled 1/4 c)))))

procedure

(xscaled a)  trans?

  a : real?
The transformation (trans a 0 0 1 0 0).

(x,y) xscaled a = (ax,y)

> (def c
    (scaled 1/100
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw              c))
          (color "red"   (draw (xscaled 1/2 c)))))

procedure

(yscaled a)  trans?

  a : real?
The transformation (trans 1 0 0 a 0 0).

(x,y) yscaled a = (x,ay)

> (def c
    (scaled 1/100
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw              c))
          (color "red"   (draw (yscaled 1/2 c)))))

procedure

(shifted a b)  trans?

  a : real?
  b : real?
The transformation (trans 1 0 0 1 a b).

(x,y) yscaled a = (x+a,y+b)

> (def c
    (scaled 1/200
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw                  c))
          (color "red"   (draw (shifted 1/2 0   c)))
          (color "blue"  (draw (shifted 0   1/2 c)))))

procedure

(zscaled a b)  trans?

  a : real?
  b : real?
The transformation (trans a b -b a 0 0).

(x,y) zscaled a b = (ax-ay,bx+ay)

Note: (1,0) zscaled (a,b) = (a,b) , thus (1,0) is rotated and scaled into (a,b). Think of zscaled as "complex multiplaction".

In the example the red curve is multiplied with 0+1i, which corresponds to a rotation of 90 degrees.

In the example the blue curve is multiplied with 1+1i, which corresponds to a rotation of 45 degrees. Also the since the magnitude of 1+1i is sqrt(2) the figure is scaled with the factor sqrt(2).

> (def c
    (scaled 1/200
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw              c))
          (color "red"   (draw (zscaled 0 1 c)))
          (color "blue"  (draw (zscaled 1 1 c)))))

procedure

(rotated θ)  trans?

  θ : real?
The transformation (rotated theta) is a rotation of θ radian around (0,0).

> (def c
    (scaled 1/200
      (shifted -100 0
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw             c))
          (color "red"   (draw (rotated 3.141592653589793 c)))))

procedure

(rotatedd θ)  trans?

  θ : real?
The transformation (rotatedd theta) is a rotation of θ degrees around (0,0).

> (def c
    (scaled 1/200
      (shifted -100 0
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw               c))
          (color "red"   (draw (rotatedd 180 c)))))

procedure

(rotated-about θ p)  trans?

  θ : real?
  p : pt?
The transformation (rotated-about θ) is a rotation of θ radian around the point p.

> (def c
    (scaled 1/200
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw               c))
          (color "red"   (draw (rotated-about 3.14159 (pt 0.25 0) c)
                                                      (pt 0.25 0)))))

procedure

(rotatedd-about θ p)  trans?

  θ : real?
  p : pt?
The transformation (rotatedd-about θ p) is a rotation of θ degrees around the point p.

> (def c
    (scaled 1/200
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (penwidth 4
    (draw (color "black" (draw               c))
          (color "red"   (draw (rotatedd-about 180 (pt 0.25 0) c)
                                                   (pt 0.25 0)))
          (color "blue"  (draw (rotatedd-about  90 (pt 0.25 0) c)
                                                   (pt 0.25 0)))))

procedure

(reflected p q)  trans?

  p : pt?
  q : pt?
The transformation (reflected p q) reflects in the line l through the points p and q.

> (def c
    (scaled 1/200
      (shifted -100 0
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (draw (curve (pt -1 -1) -- (pt 1 1))
        (penwidth 4
          (draw
           (color "red"   (draw               c))
           (color "blue"  (draw (reflected (pt -1 -1) (pt 1 1) c))))))

procedure

(inverse t)  trans?

  t : trans?
If the transformation t is invertible, then (inverse t) is the inverse transformation of t.

> (def c
    (scaled 1/200
      (shifted -100 -100
          (curve (pt 100 162) .. (pt 140 178) right .. (pt 195 125) down
                 .. (pt 100 0) (curl 0) .. up (pt 5 125)
                 .. right (pt 60 178) .. (pt 100 162)))))
> (def t (shifted 1/2 0))
> (def s (inverse t))
> (penwidth 4
    (draw (color "black" (draw c))
          (color "red"   (draw (t c)))
          (color "blue"  (draw (s c)))))

procedure

(trans~ t s [ε])  boolean?

  t :