💾 Archived View for thrig.me › blog › 2023 › 05 › 12 › triangle.go captured on 2024-06-16 at 13:32:49.
⬅️ Previous capture (2023-05-24)
-=-=-=-=-=-=-
// triangle - draws "mountains" in a somewhat random fashion package main import ( "fmt" "image" "image/color" "image/gif" "log" "math" "math/rand" "os" "time" ) var palette = []color.Color{ color.NRGBA{245, 222, 179, 255}, color.Black, } const ( bg_color = 0 fg_color = 1 ) func clamp(min, max, value int) int { if value < min { return min } else if value > max { return max } else { return value } } // TODO this probably could be improved func mark(c *image.Paletted, x, y int) { // KLUGE prevent overlaps for nx := -1; nx < 8; nx++ { for ny := -1; ny < 5; ny++ { if c.At(x+nx, y-ny) == palette[fg_color] { return } } } c.SetColorIndex(int(x), int(y), fg_color) c.SetColorIndex(int(x)+1, int(y)-1, fg_color) c.SetColorIndex(int(x)+2, int(y)-2, fg_color) c.SetColorIndex(int(x)+3, int(y)-3, fg_color) c.SetColorIndex(int(x)+4, int(y)-2, fg_color) c.SetColorIndex(int(x)+5, int(y)-1, fg_color) c.SetColorIndex(int(x)+6, int(y), fg_color) } func origin(xmax, ymax int) (x, y int) { x = int(rand.Int63n(int64(xmax))) y = int(rand.Int63n(int64(ymax))) return x, y } func main() { const ( baseangle = 15 // originally 60 degrees for a Sierpinski triangle border = 8 // avoid the edges by this amount maxlen = 256 // longest step to make minlen = 8 // down to this value xsize = 480 // image dimensions ysize = 640 nreps = 120 // how many things to draw ) out, err := os.Create("out.gif") if err != nil { log.Fatal(err) } defer out.Close() rand.Seed(time.Now().UTC().UnixNano()) rect := image.Rect(0, 0, xsize, ysize) canvas := image.NewPaletted(rect, palette) arclen := baseangle * math.Pi / 180 narcs := int64(math.Round(2 * math.Pi / arclen)) // TODO better way to fill a canvas? for x := 0; x < xsize; x++ { for y := 0; y < ysize; y++ { canvas.SetColorIndex(x, y, bg_color) } } startx, starty := origin(xsize, ysize) startx = clamp(border, xsize-border, startx) starty = clamp(border, ysize-border, starty) n := maxlen x := startx y := starty for i := 0; i < nreps; i++ { // another option would be to shuffle the available // angles, see if that moves the point out-of-bounds, // and if so to pick a different angle until those run // out, and then maybe to backtrack through a queue. // that's more complicated, but might be interesting? angle := float64(rand.Int63n(narcs)) * arclen //fmt.Printf("dbg angle %v [%v,%v]\n", angle, x, y) mark(canvas, int(x), int(y)) x += int(math.Cos(angle) * float64(n)) y += int(math.Sin(angle) * float64(n)) if x < border || x >= xsize-border || y < border || y >= ysize-border { // or the origin could be randomized ... x = startx y = starty } else { n /= 2 if n < minlen { n = maxlen } } } options := &gif.Options{ NumColors: len(palette), } gif.Encode(out, canvas, options) // KLUGE so I can add debug fmt.* calls without have to twiddle // the import line fmt.Println("ok") }