The code on this page runs with Kojo version 150212-1 and later.
The information on this page is also available in an interactive story within Kojo - accessible via the Stories -> Playing with Pictures menu item.
The Basics
Transformations
Effects
Using functions
Working with Lists of Pictures
Animating Pictures
The Basics
To work with pictures within Kojo, you start by creating a seed pattern. This can be anything that you can get the turtle to make (Staging shapes will come later). Here's a simple example:
def p(size: Int) = PictureT { t =>
import t._
// set animation delay to slow down picture making for debugging/effects
// The default animation delay for picture drawing is 0
// setAnimationDelay(100)
repeat (4) {
forward(size)
right()
}
right(45)
forward(math.sqrt(2 * size * size))
}
def p1 = p(40)
Now you have a couple of functions to create seed patterns for you.
You draw your pattern on the screen like this:
clear()
invisible()
draw(p1)
Let's make a richer picture.
clear()
invisible()
val pic = HPics(
p1,
p1
)
draw(pic)
This gives you two instances of your pattern, tiled in the horizontal direction.
Note - your seed patterns (like the ones made by p and p1 above) should normally be drawn to the top and right of the turtle's starting position. If you go below or to the left of this position, you will overlap other patterns in your tiled picture. If you deliberately want this overlap, you are free to draw in any direction.
If you want more patterns in a row, just add them in:
clear()
invisible()
val pic = HPics(
p1,
p1,
p1,
p1
)
draw(pic)
Now you have four of your pattern blocks in a row.
What if you want the third block in the row to have two more blocks on top of it?
That's easy, just use VPics:
clear()
invisible()
val pic = HPics(
p1,
p1,
VPics(
p1,
p1,
p1
),
p1
)
draw(pic)
You can keep putting VPics inside HPics, and HPics inside VPics, for as long as you want!
HPics and VPics are picture containers. A container contains some pictures, and has a strategy for laying out these pictures.
You can also lay out your pictures in terms of rows and columns:
clear()
invisible()
val pic = col(row(p1, 5), 3)
draw(pic)
Transformations
You can modify the appearance of the pattern blocks in your picture with transformations. You do this by applying a series of transformations to a block. Here's an example:
clear()
invisible()
val pic = HPics(
p1,
fillColor(blue) * rot(20) * scale(1.5) * trans(0, 10) -> p1,
VPics(
p1,
p1,
p1
),
p1
)
draw(pic)
You can also use function call notation with your transformations:
FillColor(blue) (Rot (20) (scale(1.5) (trans(0, 10) (p1)))),
The available transformations are:
- rot
- scale
- trans
- offset
- axes
- flipX and flipY
- fillColor
- penColor
- hue
- sat
- brit
- opac
- penWidth
Code completion within Kojo will help you with the inputs/args to the transformations.
Note - With function call notation, the transformation names begin with capital letters. The examples above should make this clear.
Another container - GPics
You have already seen the HPics and VPics containers. You generally use these containers to make a picture by tiling a seed pattern. There's another container called GPics which you can use to build arbitrary pictures out of a bunch of other pictures. GPics does not try to tile the pictures within it. It just draws them one on top of the other. It's your job to translate, rotate etc the pictures within a GPics to get the figure that you want.
Here's an example of GPics usage:
val height = 6.0
val width = 5.0
def rect = Picture {
repeat (2) {
forward(height)
right()
forward(width)
right()
}
}
def tri = Picture {
right()
repeat (3) {
forward(width)
left(120)
}
}
def circ = Picture {
repeat (360) {
forward(0.1)
right(1)
}
}
val smoke = rot(60) -> HPics(
circ,
circ,
circ
).withGap(0.5)
clearWithUL(Cm)
invisible()
val pic = fillColor(brown) * trans(0, -5) -> GPics(
rect,
trans(0, height) -> tri,
fillColor(black) * trans(width/4, 0) * scale(0.5) -> rect,
fillColor(gray) * trans(width/2, height + width* math.cos(30.toRadians)) * scale(0.1) -> smoke
)
draw(pic)
Effects
Effects build upon transformations to provide richer, uhm, effects. Generally, transformations modify a picture, while effects make multiple copies of a picture and put them together in interesting ways.
The following effects are available:
- spin
- reflect
Again, code completion within Kojo will help you to use these effects.
Here's an example:
val pic = reflect(200) * spin(5) -> HPics(
p1,
scale(0.9) -> p1,
scale(0.8) * rot(30) -> HPics(
fillColor(green) -> p1,
fillColor(yellow) -> p1,
scale(0.7) * rot(30) -> HPics(
p1,
p1,
fillColor(blue) * rot(45) -> p1
)
)
)
clear()
draw(pic)
You can also accomplish something similar using functions, as described at the end of the next section.
Using Functions
Parts of this section are inspired by: http://docs.racket-lang.org/quick
You just saw how you can lay out your pictures declaratively using HPics and VPics. You can also tap into the power of functions to help with this:
def two(fn: => Picture) = HPics(fn, fn)
def four(fn: => Picture) = VPics(two(fn), two(fn))
def checker(p1: => Picture, p2: => Picture) = VPics(
HPics(p1, p2),
HPics(p2, p1)
)
def pc(color: Color) = FillColor(color)(p1)
clear()
invisible()
val pic = four(four(checker(pc(blue), pc(black))))
draw(pic)
Or
def two(fn: => Picture) = HPics(fn, fn)
def four(fn: => Picture) = VPics(two(fn), two(fn))
def checker(p1: => Picture, p2: => Picture) = VPics(
HPics(p1, p2),
HPics(p2, p1)
)
def checkerBoard(size: Int) = four(checker(pc(size, blue), pc(size, black)))
def series(fn: Int => Picture) = HPics(fn(20), fn(40), fn(60), fn(80))
def pc(size: Int, color: Color) = FillColor(color)(p(size))
clear()
invisible()
val pic = series(checkerBoard)
draw(pic)
You saw in an earlier section how you can lay out your pictures in terms of rows and columns. But what if you wanna modify one of the pictures in a row?
No problem. Functions to the rescue again:
// This is a slightly advanced example
// Note the use of the call-by-name Picture input/argument below
def rowm(p: => Picture, n: Int)(deco: (Picture, Int) => Picture): HPics = {
val lb = collection.mutable.ListBuffer[Picture]()
for (i <- 1 to n) {
lb += deco(p, i) // need to use p.copy if p is not call-by-name
}
HPics(lb.toList)
}
clear()
invisible()
val pic2 = rowm(p1, 5) { (p, i) =>
if (i == 2) FillColor(blue)(p) else p
}
draw(pic2)
And here's an effect similar to the one that you saw in the last section, built using a function (as opposed to being manually laid out using HPics):
def growTurnBy(f: Double, a: Double, n: Int, p: => Picture): Picture = {
if (n==1) {
rot(a) * scale(f) -> HPics(p)
}
else {
rot(a) * scale(f) -> HPics(p, growTurnBy(f, a, n-1, p))
}
}
clear()
draw(reflect(200) * spin(5) -> growTurnBy(0.8, -14, 12, p1))
Working with Lists of Pictures
You can do interesting things with Lists of Pictures:
clear()
invisible()
val pics = List(p(40), p(50), p(60), p(70))
val pic = VPics(
HPics(pics),
HPics(pics.map {p => rot(10) * fillColor(green) -> p.copy})
)
draw(pic)
To be able to use the list methods like filter, find, etc, you need to do some extra work - to get access to fields of interest within your pictures:
case class Pattern(size: Int)(p: Painter) extends Pic(p) {
override def copy: Pattern = Pattern(size)(p)
}
def p(size: Int) = Pattern(size) { t =>
import t._
setAnimationDelay(0)
repeat (4) {
forward(size)
right()
}
right(45)
forward(math.sqrt(2 * size * size))
}
clear()
invisible()
val pics = List(p(40), p(50), p(60), p(70))
val pic = VPics(
HPics(pics),
HPics(pics map {p => rot(10) * fillColor(green) -> p.copy}),
HPics(pics map {_ copy} filter {_.size > 50}),
fillColor(blue) -> HPics(pics map {_ copy} find {_.size == 50} get)
)
draw(pic)
Animating Pictures
Once you have a picture that you have drawn on the screen (via the show command), you can easily animate it:
val pic = reflect(200) * spin(5) -> HPics(
p1,
scale(0.9) -> p1,
scale(0.8) * rot(30) -> HPics(
fillColor(green) -> p1,
fillColor(yellow) -> p1,
scale(0.7) * rot(30) -> HPics(
p1,
p1,
fillColor(blue) * rot(45) -> p1
)
)
)
clear()
draw(pic)
animate {
pic.rotate(1)
pic.translate(-1, -0.5)
pic.scale(0.999)
}