💾 Archived View for thrig.me › art › orbits › orbit.go captured on 2024-08-31 at 14:18:01.

View Raw

More Information

⬅️ Previous capture (2023-05-24)

-=-=-=-=-=-=-

// orbits of color

package main

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"log"
	"math"
	"math/rand"
	"os"
	"time"
)

type Orbit struct {
	a float64
	b float64

	afuzz float64
	bfuzz float64

	xmid int
	ymid int

	iters int
}

var palette = []color.Color{
	color.White,
	color.Black,
	color.NRGBA{255, 114, 114, 255},
	color.NRGBA{255, 203, 114, 255},
	color.NRGBA{255, 255, 114, 255},
	color.NRGBA{57, 128, 57, 255},
	color.NRGBA{114, 114, 255, 255},
	color.NRGBA{99, 58, 130, 255},
	color.NRGBA{238, 237, 238, 255},

	color.NRGBA{78, 78, 78, 255},
	color.NRGBA{99, 99, 99, 255},
	color.NRGBA{130, 130, 130, 255},
	color.NRGBA{156, 156, 156, 255},
	color.NRGBA{208, 208, 208, 255},
	color.NRGBA{237, 237, 237, 255},
	color.NRGBA{239, 239, 239, 255},
}

const (
	fg_color = 0
	bg_color = 1
	cidx1    = 2
	cidx2    = 3
	cidx3    = 4
	cidx4    = 5
	cidx5    = 6
	cidx6    = 7
	cidx7    = 8
)

func irand(max int) int {
	return int(rand.Int63n(int64(max)))
}

func one_in(n int) bool {
	x := rand.Int63n(int64(n))
	if x > 0 {
		return false
	} else {
		return true
	}
}

func make_orbit() Orbit {
	orb := Orbit{
		a: 200 + rand.Float64()*800,

		afuzz: rand.Float64() * 2.0,
		bfuzz: rand.Float64() * 3.0,

		xmid: irand(1128 * 2),
		ymid: irand(752 * 2),

		iters: 120 + irand(640),
	}
	// not too eccentric an orbit? usually.
	if one_in(20) {
		orb.b = 200 + rand.Float64()*800
	} else {
		orb.b = orb.a + rand.Float64()*100.0 + rand.Float64()*100.0 - 100.0
	}
	// iters probably needs to scale with the size of the orbit,
	// otherwise small get overdrawn
	size := orb.a * orb.b
	// NOTE needs a floor to prevent feeding irand a 0 value
	orb.iters = irand(500 + int(size/1000))
	return orb
}

// for 0 <= time <= 2*pi to sketch the whole thing
func xyell(time, a, b float64) (x, y float64) {
	x = a * math.Cos(time)
	y = b * math.Sin(time)
	return x, y
}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	with_gif("out.gif")
	fmt.Fprintf(os.Stderr, "ok\n") // KLUGE for "fmt" import
}

func with_gif(file string) {
	out, err := os.Create(file)
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	xsize := 1128
	ysize := 752

	rect := image.Rect(0, 0, xsize, ysize)
	canvas := image.NewPaletted(rect, palette)
	draw.Draw(canvas, canvas.Bounds(), &image.Uniform{palette[bg_color]}, image.ZP, draw.Src)

	// NOTE the shapes may not actually be on-screen, so one
	// optimization would be to calclate for only those that do
	// present something within the canvas
	done := 0
	for {
		if done == 17 {
			break
		}
		orb := make_orbit()
		for i := 0; i < orb.iters; i++ {
			t := rand.Float64() * math.Pi * 2
			//xf, yf := xyell(t, orb.a+rand.Float64()*orb.afuzz, orb.b+rand.Float64()*orb.bfuzz)
			xf, yf := xyell(t, orb.a, orb.b)
			x := int(xf)
			y := int(yf)
			canvas.SetColorIndex(x+orb.xmid, y+orb.ymid, uint8(irand(7)+2))
			if one_in(30) {
				orb.xmid++
			}
		}
		done++
	}

	// some background noise
	for i := 0; i < 200; i++ {
		canvas.SetColorIndex(irand(xsize), irand(ysize), uint8(irand(7)+9))
	}

	options := &gif.Options{
		NumColors: len(palette),
	}
	gif.Encode(out, canvas, options)
}