The (kawa pictures)
library lets you create geometric shapes
and images, and combine them in interesting ways.
The tutorial gives an introduction.
The easiest way to use and learn the pictures
library
is with a suitable REPL. You can use the old Swing-based console
or any DomTerm-based terminal emulator.
You can create a suitable window either by starting kawa
with the -w
flag, or by running the kawa
command
inside an existing DomTerm-based terminal emulator.
The screenshot below is of the latter,
using the qtdomterm
terminal emulator.
After (import (kawa swing))
you can use show-picture
to display a picture in a Swing window.
A picture is an object that can be displayed on a screen, web-page, or printed page, and combined with other pictures.
A picture has a method printing itself in a graphical context. It also has various properties.
An important property of a picture is its bounding box. This is a rectangle (with edges parallel to the axes) that surrounds the contents of the picture. Usually the entire visible part of the picture is inside the bounding box, but in some cases part of the picture may stick outside the bounding box. For example when a circle is drawn (stroked) with a “pen”, the bounding box is that of the infinitely-thin mathematical circle, so “ink” from the pen that is outside the circle may be outside the bounding box.
A picture has an origin point corresponding to the (0 0) cordinates.
The origin is commonly but not always inside the bounding box.
Certain operations (for example hbox
) combine pictures by putting
them “next to” each other, where “next to” is defined in
terms of the bounding box and origin point.
The library works with a two-dimensional grid, where each position has an x cordinate and y coordinate. Normally, x values increase as you move right on the screen/page, while y values increase as you move down. This convention matches that used by Java 2D, SVG, and many other graphics libraries. However, note that this is different from the traditional mathematical convention of y values increasing as you go up.
By default, one unit is one “pixel”. (More precisely,
it is the px
unit in the CSS specification.)
A point is a pair consisting of an x and a y coordinate.
Construct a point value with the specified
x
andy
values. Bothx
andy
are expressions that evaluate to real numbers:&P[(+ old-right 10) baseline]
A dimension value is a pair of a width and a height. It is used for the size of pictures in the two dimensions.
In a context that expects a point, a dimension is treated as an offset relative to some other point.
Construct a dimension value with the specified
width
andheight
values, which are both expressions that evaluate to real numbers.
A shape is a collection of lines and curves. Examples include lines, circles, and polygons. A shape can be stroked, which you can think of as being drawn by a very fancy calligraphic pen that follows the lines and curves of the shape.
A closed shape is a shape that is continuous and ends up where it started. This includes circles and polygons. A closed shape can be filled, which means the entire “interior” is painted with some color or texture.
A shape is represented by the Java java.awt.Shape
interface.
The picture
library only provides relatively simple shapes,
but you can use any methods that create a java.awt.Shape
object.
Shape is effectively a sub-type of picture,
though they’re represented using using disjoint classes:
If you use a shape where a picture is required,
the shape is automatically converted to a picture,
as if using the draw
procedure.
In the simple case two points are specified, and the result is a line that goes from point
p1
top2
. Ifn
points are specied, the result is a polyline: a path consisting ofn-1
line segments, connecting adjacent arguments. (If only a single point is specified, the result is degenerate single-point shape.)All of the points except the first can optionally be specified using a dimension, which is treated an an offset from the previous point:
(line &P[30 40] &D[10 5] &D[10 -10])is the same as:
(line &P[30 40] &P[40 45] &P[50 35])
Procedure: polygon
p1
[p2
...
]
Constructs a closed shape from line segments. This is the same as calling
line
with the same arguments, with the addition of a final line segment from the last point back to the first point.
A rectangle is closed polygon of 4 line segments that are alternatively parallel to the x-axis and the y-axis. I.e. if you rotate a rectangle it is no longer a rectangle by this definition, though of course it still has a rectangular shape. If
p2
is not specified, constructs a rectangle whose upper-left corner is&P[0 0]
and whose lower-right corner isp1
. Ifp2
is specified, constructs a rectangle whose upper-left corner isp1
and whose lower-right corner isp2
. Ifp2
is a dimension it is considered a relative offset fromp1
, just like forpolygon
.
Procedure: circle
radius
[center
]
Creates a circle with the specified
radius
. If thecenter
is not specified, it defaults to&P[0 0]
.
A paint is a color pattern used to fill part of the canvas. A paint can be a color, a texture (a replicated pattern), or a gradient (a color that gradually fades to some other color).
A color is defined by red, green, and blue values. It may also have an alpha component, which specifies transparency.
Converts
value
to a color - or more general a paint. Specificlly, the return type isjava.awt.Paint
. Thevalue
can be any one of:
A
java.awt.Paint
, commonly ajava.awt.Color
.A 24-bit integer value is converted to a color. Assume the integer is equal to a hexadecimal literal of the form
#xRRGGBB
. ThenRR
(bits 16-23) is the intensity of the red component;GG
(bits 8-15) is the intensity of the green component; andRR
(bits 0-7) is the intensity of the red component.One of the standard HTML/CSS/SVG color names, as a string or symbol. See the table in
gnu/kawa/models/StandardColor.java
source file. Case is ignored, and you can optionally use hyphens to separate words. For example'hot-pink
,'hotpink
, and'hotPink
are all the same sRGB color#xFF69B4
.
Procedure: with-paint
paint
picture
Create a new picture that is the “same” as
picture
but usepaint
as the default paint. Forpaint
you can use any valid argument to->paint
. The default paint (which is the color black if none is specified) is used for bothfill
(paint interior) anddraw
(stroke outline).#|kawa:1|#(! circ1 (circle 20 &P[20 20]))
#|kawa:2|#(hbox (fill circ1) (draw circ1))
#|kawa:3|#(with-paint 'hot-pink (hbox (fill circ1) (draw circ1)))
Above we use
with-paint
to create a cloned picture, which is the same as the originalhbox
, except for the default paint, in this case the colorhot-pink
.#|kawa:4|#(! circ2 (hbox (fill circ1) (with-paint 'hot-pink (fill circ1))))
#|kawa:5|#circ2
#|kawa:6|#(with-paint 'lime-green circ2)
Here
circ2
is anhbox
of two filled circles, one that has unspecified paint, and one that ishot-pink
. Printingcirc2
directly uses black for the circle with unspecified color, but if we wrapcirc2
in anotherwith-paint
that provides a default that is used for the first circle.
Fill the “inside” of
shape
. If nopaint
is specified, uses the current default paint. Otherwise,(fill
is the samepaint
shape
)(with-paint
.paint
(fillshape
))
Procedure: draw
option
*
shape
+
Returns a picture that draws the outline of the
shape
. This is called stroking. Anoption
may be one of:
A
Paint
orColor
object, which is used to draw the shape.A standard color name, such as
'red
or'medium-slate-blue
. This is mapped to aColor
.A join-specifier, which is a symbol specifying how each curve of the shape is connected to the next one. The options are
'miter-join
,'round-join
, and'bevel-join
. The default if none is specified is'miter-join
.A end-cap-specifier, which is a symbol specifying how each end of the shape is terminated. The options are
'square-cap
,'round-cap
, or'butt-cap
. The default is'butt-cap
. (This follows SVG and HTML Canvas. The default in plain Java AWT is a square cap.)A real number specifies the thickness of the stroke.
A
java.awt.Stroke
object. This combines join-specifier, end-cap-specifier, thickness, and more in a single object. TheBasicStroke
class can specify dashes, though that is not yet supported for SVG output; only AWT or image output.Let us illustrate with a sample line
lin
and a helper macroshow-draw
, which adds a border around a shape, then draws the given shape with some options, and finally re-draws the shape in plain form.#|kawa:10|#(define lin (line &P[0 0] &P[300 40] &P[200 100] &P[50 70]))
#|kawa:11|#(define-syntax show-draw
#|....:12|#(syntax-rules ()
#|....:13|#((_ options ... shape)
#|....:14|#(border 12 'bisque (zbox (draw options ... shape) shape)))))
#|....:15|#(show-draw 8 'lime lin)
#|....:16|#(show-draw 8 'lime 'round-cap 'round-join lin)
#|....:17|#(show-draw 8 'lime 'square-cap 'bevel-join lin)
Notice how the different cap and join styles change the drawing. Also note how the stroke (color lime) extends beyond its bounding box, into the surrounding border (color bisque).
A 2D affine transform is a linear mapping from coordinates to coordinates. It generalizes translation, scaling, flipping, shearing, and their composition. An affine transform maps straight parallel lines into other straight parallel lines, so it is only a subset of possible mappings - but a very useful subset.
Procedure: affine-transform
xx
xy
yx
yy
x0
y0
Procedure: affine-transform
px
py
p0
Creates a new affine transform. The result of applying
(affine-transform
to the pointxx
xy
yx
yy
x0
y0
)&P[
is the transformed pointx
y
]&P[(+ (*x
xx
) (*y
yx
)x0
) (+ (*x
xy
) (*y
yy
)y0
)]If using point arguments,
(affine-transform &P[
is equivalent to:xx
xy
] &P[yx
yy
] &P[x0
y0
])(affine-transform
.xx
xy
yx
yy
x0
y0
)
Procedure: with-transform
transform
picture
Procedure: with-transform
transform
shape
Creates a transformed picture.
If the argument is a
shape
, then the result is also a shape.
Procedure: with-transform
transform
point
Apply a transform to a single point, yielding a new point.
Procedure: with-transform
transform1
transform2
Combine two transforms, yielding the composed transform.
Procedure: rotate
angle
picture
The one-argument variant creates a new affine transform that rotates a picture about the origin by the specified
angle
. A positiveangle
yields a clockwise rotation. Theangle
can be either a quantity (with a unit of eitherrad
radians,deg
(degrees), orgrad
(gradians)), or it can be a unit-less real number (which is treated as degrees).The two-argument variant applies the resulting transform to the specified picture. It is equivalent to:
(with-transform (rotateangle
)picture
)
Procedure: scale
factor
picture
Scales the
picture
by the givenfactor
. Thefactor
can be a real number. Thefactor
can also be a point or a dimension, in which case the two cordinates are scaled by a different amount.The two-argument variant applies the resulting transform to the specified picture. It is equivalent to:
(with-transform (scalefactor
)picture
)
Procedure: translate
offset
picture
The single-argument variant creates a transform that adds the
offset
to each point. Theoffset
can be either a point or a dimension (which are treated quivalently).The two-argument variant applies the resulting transform to the specified picture. It is equivalent to:
(with-transform (translateoffset
)picture
)
Procedure: hbox
[spacing
] picture
...
Procedure: vbox
[spacing
] picture
...
Make a combined picture from multiple sub-pictures drawn either “next to” or “on top of” each other.
The case of
zbox
is simplest: The sub-pictures are drawn in argument order at the same position (origin). The “z
” refers to the idea that the pictures are stacked on top of each other along the “Z-axis” (the one perpendicular to the screen).The
hbox
andvbox
instead place the sub-pictures next to each other, in a row or column. Ifspacing
is specified, if must be a real number. That much extra spacing is added between each sub-picture.More precisely:
hbox
shifts each sub-picture except the first so its left-origin control-point (see discussion atre-center
) has the same position as the right-origin control point of the previous pictureplus
the amount ofspacing
. Similarly,vbox
shifts each sub-picture except the first so its top-origin control point has the same position as the bottom-origin point of the previous picture, plusspacing
.The bounding box of the result is the smallest rectangle that includes the bounding boxes of the (shifted) sub-pictures. The origin of the result is that of the first picture.
Procedure: border
[size
[paint
]] picture
Return a picture that combines
picture
with a rectangular border (frame) aroundpicture
’s bounding box. Thesize
specifies the thickness of the border: it can be real number, in which it is the thickness on all four sides; it can be a Dimension, in which case the width is the left and right thickness, while the height is the top and bottom thickness; or it can be a Rectangle, in which case it is the new bounding box. Ifpaint
is specified it is used for the border; otherwise the default paint is used. The border is painted before (below) thepicture
painted. The bounding box of the result is that of the border, while the origin point is that of the originalpicture
.#|kawa:2|#(with-paint 'blue (border &D[8 5] (fill 'pink (circle 30))))
Procedure: padding
width
[background
] picture
This is similar to
border
, but it just adds extra space aroundpicture
, without painting it. Thesize
is specified the same way. Ifbackground
is specified, it becomes the background paint for the entire padded rectangle (bothpicture
and the extra padding).#|kawa:3|#(define circ1 (fill 'teal (circle 25)))
#|kawa:4|#(zbox (line &P[-30 20] &P[150 20])
#|kawa:5|#(hbox circ1 (padding 6 'yellow circ1) (padding 6 circ1)))
This shows a circle drawn three ways: as-is; with padding and a background color; with padding and a transparent background. A line is drawn before (below) the circles to contrast the yellow vs transparent backgrounds.
Procedure: re-center
[xpos
] [ypos
] picture
Translate the
picture
such that the point specified byxpos
andypos
is the new origin point, adjusting the bounding box to match. If thepicture
is a shape, so is the result.The
xpos
can have four possible values, all of which are symbols:'left
(move the origin to the left edge of the bounding box);'right
(move the origin to the right edge of the bounding box);'center
(or'centre
) (move the origin to halfway between the left and right edges); or'origin
(don’t change the location along the x-axis). Theypos
can likewise have four possible values:'top
(move the origin to the top edge of the bounding box);'bottom
(move the origin to the bottom edge of the bounding box);'center
(or'centre
) (move the origin to halfway between the top and bottom edges); or'origin
(don’t change the location along the y-axis).A single
'center
argument is the same as specifying'center
for both axis; this is the default. A single'origin
argument is the same as specifying'origin
for both axis; this is the same as justpicture
.The 16 control points are shown below, relative to a picture’s bounding box and the X- and Y-axes. The abbreviations have the obvious values, for example
LC
means'left 'center
.LT OT CT RT ┌────┬──────────┐ │ │ │ │ │ │ LC│ OC C │RC LO├────O──CO──────┤RO │ │ │ └────┴──────────┘ LB OB CB RBThe result of (for example)
(re-center 'left 'center
isP
)P
translated so the origin is at control pointLC
.#|kawa:1|#(define D (fill 'light-steel-blue (polygon &P[-20 0] &P[0 -20] &P[60 0] &P[0 40])))
#|kawa:2|#(zbox D (draw 'red (circle 5)))
Above we defined
D
as a vaguely diamond-shaped quadrilateral. A small red circle is added to show the origin point. Below we display 5 versions ofD
in a line (anhbox
), starting with the originalD
and 4 calls tore-center
.#|kawa:3|#(zbox (hbox D (re-center 'top D) (re-center 'bottom D)
#|....:4|#(re-center 'center D) (re-center 'origin D))
#|....:5|#(line &P[0 0] &P[300 0]))
The line at
y=0
shows the effects ofre-center
.
An image is a picture represented as a rectangular grid of color values.
An image file is some encoding (usually compressed) of an image,
and mostly commonly has the extensions png
, gif
,
or jpg
/jpeg
.
A “native image” is an instance of java.awt.image.BufferedImage
,
while a “picture image” is an instance of gnu.kawa.models.DrawImage
.
(Both classes implement the java.awt.image.RenderedImage
interface.)
A BufferedImage
is automatically converted to a DrawImage
when needed.
Creates a picture image, using either an existing native image
bimage
, or an image file specified bypath
.Writing
(image src:
is roughly the same aspath
)(image (read-image
except that the former has thepath
))path
associated with the resulting picture image. This can make a difference when the image is used or displayed.If the argument is a
picture
, it is converted to an image as if by->image
.
Read an image file from the specified
path
, and returns a native image object (aBufferedImage
).#|kawa:10|#(define img1 (image-read "http://pics.bothner.com/2013/Cats/06t.jpg"))
#|kawa:11|#img1
#|kawa:12|#(scale 0.6 (rotate 30 img1))
Note that while img1
above is a (native) image,
the scaled rotated image is not an image object.
It is a picture - a more complex value that contains an image.
Procedure: image-write
picture
path
The
picture
is converted to an image (as if by using->image
) and then it is written to the specifiedpath
. The format used depends on (the lower-cased string value of) the path: A JPG file if the name ends with".jpg"
or".jpeg"
; a GIF file if the name ends with".gif"
; a PNG file if the name ends with".png"
. (Otherwise, the defalt is PNG, but that might change.)
Return the width or height of the given
image
, in pixels.
Convert
picture
to an image (aRenderedImage
). If thepicture
is an image, return as-is. Otherwise, create an empty image (aBufferedImage
whose size is thepicture
’s bounding box), and “paint” thepicture
into it.#|kawa:1|#(define c (fill (circle 10)))
#|kawa:2|#(scale 3 (hbox c (->image c)))
Here we take a circle
c
, and convert it to an image. Note how when the image is scaled, the pixel artifacts are very noticable. Also note how the origin of the image is the top-level corner, while the origin of the original circle is its center.
Procedure: with-composite
[[compose-op
] picture
] ...
Limited support - SVG and DomTerm output has not been implemented.
Procedure: picture-write-svg
picture
path
[headers
]
Writes the
picture
to the file specified bypath
, in SVG (Structered Vector Graphics) format. Ifheaders
is true (which is the default) first write out the XML and DOCTYPE declarations that should be in a well-formed standaline SVG file. Otherwise, just write of the<svg>
element. (Modern browers should be able to display a file consisting of just the<svg>
element, as long as it has extension.svg
,.xml
, or.html
; the latter may add some extra padding.)
Procedure: picture->svg-node
picture
Returns a SVG representation of
picture
, in the form of an<svg>
element, similar to those discussed in Creating XML nodes. If you convert the<svg>
element to a string, you get formatted XML; if youwrite
the<svg>
element you get an XML literal of form"#<svg>...</svg>"
. If youdisplay
the<svg>
element in a DomTerm terminal you get the picture (as a picture). This works because when you display an element in DomTerm it gets inserted into the display.
These procedures require (import (kawa swing))
in addition to
(import (kawa pictures))
.
The convenience function show-picture
is useful
for displaying a picture in a new (Swing) window.
Procedure: show-picture
picture
If this is the first call to
show-pictures
, displayspicture
in a new top-level window (using the Swing toolkit). Sequenent calls toshow-picture
will reuse the window.#|kawa:1|#(import (kawa swing) (kawa pictures))
#|kawa:2|#(show-picture
#|kawa:3|#some-picture
)(set-frame-size! &D[200 200])
; Adjust window size #|kawa:4|#(show-picture
some-other-picture
)
Procedure: picture->jpanel
picture
Return a
JPanel
that displayspicture
. You can change the displayed picture by:(set!panel
:picturesome-other-picture
)
Procedure: set-frame-size!
size
[frame
]
If
frame
is specified, set its size. Otherwise, remember the size for futureshow-picture
calls; if there is already ashow-picture
window, adjust its size.