125 lines
4.2 KiB
Go
125 lines
4.2 KiB
Go
// Creates an animation of several renditions of "Munching Square".
|
|
|
|
package main
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"log"
|
|
"math"
|
|
|
|
"smariot.com/animate"
|
|
)
|
|
|
|
func main() {
|
|
const (
|
|
gridSize = 32
|
|
pixelSize = 12
|
|
pixelRadius = pixelSize / 2.
|
|
subImageSize = gridSize * pixelSize
|
|
frameRate = 10
|
|
filename = "demo.webp"
|
|
)
|
|
|
|
// create a single animation large enough to contain four sub-animations.
|
|
anim, err := animate.New(image.Rect(0, 0, subImageSize*2, subImageSize*2), frameRate, filename)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// create the sub images
|
|
// (note that b and d are swapped because I think it looks better visually, but as for logical progression, the letter order makes more sense)
|
|
a := anim.SubImage(image.Rect(0, 0, subImageSize, subImageSize)).(*image.NRGBA)
|
|
b := anim.SubImage(image.Rect(0, 0, subImageSize, subImageSize).Add(image.Pt(subImageSize, subImageSize))).(*image.NRGBA)
|
|
c := anim.SubImage(image.Rect(0, 0, subImageSize, subImageSize).Add(image.Pt(0, subImageSize))).(*image.NRGBA)
|
|
d := anim.SubImage(image.Rect(0, 0, subImageSize, subImageSize).Add(image.Pt(subImageSize, 0))).(*image.NRGBA)
|
|
|
|
// I don't want to deal with the sub images all having weird rectangles that don't start at (0, 0), so copy the rectangle from sub-image a for
|
|
// all of them.
|
|
b.Rect, c.Rect, d.Rect = a.Rect, a.Rect, a.Rect
|
|
|
|
// a pixel sized circle mask, used for the PDP-1 version.
|
|
// scaled by √2 so that the circles touch diagonally.
|
|
// need to use floor in the inset because the result is negative,
|
|
// and integers normally round toward zero.
|
|
dot := image.NewAlpha(image.Rect(0, 0, pixelSize, pixelSize).Inset(int(math.Floor(pixelRadius - pixelRadius*math.Sqrt2))))
|
|
|
|
for y := dot.Rect.Min.Y; y < dot.Rect.Max.Y; y++ {
|
|
for x := dot.Rect.Min.X; x < dot.Rect.Max.X; x++ {
|
|
dx := float64(x) + 0.5 - pixelRadius
|
|
dy := float64(y) + 0.5 - pixelRadius
|
|
a := uint8(max(0, min(1, pixelRadius*math.Sqrt2+0.5-math.Sqrt(dx*dx+dy*dy)))*255 + 0.5)
|
|
dot.SetAlpha(x, y, color.Alpha{A: a})
|
|
}
|
|
}
|
|
|
|
pixel := image.Rect(0, 0, pixelSize, pixelSize)
|
|
black := image.NewUniform(color.Black)
|
|
white := image.NewUniform(color.White)
|
|
|
|
for t := range gridSize {
|
|
// clear the frame.
|
|
draw.Draw(anim, anim.Rect, black, image.Point{}, draw.Src)
|
|
|
|
// the classing PDP-1 version.
|
|
for x := range gridSize {
|
|
y := x ^ t
|
|
draw.DrawMask(a, dot.Rect.Add(image.Pt(x*pixelSize, y*pixelSize)), white, image.Point{}, dot, dot.Rect.Min, draw.Over)
|
|
}
|
|
|
|
// an alternate version
|
|
for y := range gridSize {
|
|
for x := range gridSize {
|
|
if (x^y+t)%gridSize <= gridSize/2 {
|
|
draw.Draw(b, pixel.Add(image.Pt(x*pixelSize, y*pixelSize)), white, image.Point{}, draw.Src)
|
|
}
|
|
}
|
|
}
|
|
|
|
// an alternate alternate version - with color!
|
|
for y := range gridSize {
|
|
for x := range gridSize {
|
|
r := uint8((x ^ y + t) % gridSize * 255 / gridSize)
|
|
g := uint8((x ^ y + t + gridSize/3) % gridSize * 255 / gridSize)
|
|
b := uint8((x ^ y + t + gridSize*2/3) % gridSize * 255 / gridSize)
|
|
|
|
draw.Draw(c, pixel.Add(image.Pt(x*pixelSize, y*pixelSize)), image.NewUniform(color.NRGBA{r, g, b, 255}), image.Point{}, draw.Src)
|
|
}
|
|
}
|
|
|
|
for y := range subImageSize {
|
|
for x := range subImageSize {
|
|
// distance, as 0 in the center and 1 at the corners.
|
|
dx := float64(x) + 0.5 - subImageSize/2.
|
|
dy := float64(y) + 0.5 - subImageSize/2.
|
|
dist := math.Sqrt(dx*dx+dy*dy) / (math.Sqrt2 * subImageSize / 2.)
|
|
|
|
// distort time using a sine wave, based on distance and angle.
|
|
t := (t + int((math.Sin(math.Pi*2.*dist+math.Atan2(dy, dx))*.5+.5)*gridSize)) % gridSize
|
|
|
|
// re-quantize space, but also remember the original pixel position for drawing the pixel.
|
|
px, py := x, y
|
|
x, y := x/pixelSize, y/pixelSize
|
|
|
|
// for extra contrast with the RGB version, let's use CMY.
|
|
cyan := uint8((x ^ y + t) % gridSize * 255 / gridSize)
|
|
magenta := uint8((x ^ y + t + gridSize/3) % gridSize * 255 / gridSize)
|
|
yellow := uint8((x ^ y + t + gridSize*2/3) % gridSize * 255 / gridSize)
|
|
|
|
d.Set(px, py, color.NRGBA{255 - cyan, 255 - magenta, 255 - yellow, 255})
|
|
}
|
|
}
|
|
|
|
// emit the frame.
|
|
if err := anim.EmitFrame(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// and finally finish the animation by closing it.
|
|
if err := anim.Close(); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|