glitch-garden/main.go
Ed bc94c774dd 🎪 Ed's Glitch Garden — generative pixel/glitch art
6 pieces, pure math × chaos, zero dependencies:
- Pixel Sunrise Corruption
- Memory Leak Quilt
- Signal From Nowhere
- Databend Sunset
- Ghost in the Machine
- Bitfield Tapestry

Go program outputs 512x512 PNGs. HTML version for browser viewing.
2026-02-26 05:20:41 +00:00

247 lines
6 KiB
Go

package main
import (
"fmt"
"image"
"image/color"
"image/png"
"math"
"math/rand"
"os"
"path/filepath"
)
const size = 512 // bigger than 64 now that we're not constrained by canvas perf
type Piece struct {
Name string
Draw func(img *image.RGBA, w, h int)
}
func main() {
outDir := "output"
if len(os.Args) > 1 {
outDir = os.Args[1]
}
os.MkdirAll(outDir, 0755)
pieces := []Piece{
{"01-pixel-sunrise-corruption", drawPixelSunrise},
{"02-memory-leak-quilt", drawMemoryLeakQuilt},
{"03-signal-from-nowhere", drawSignalFromNowhere},
{"04-databend-sunset", drawDatabendSunset},
{"05-ghost-in-the-machine", drawGhostInTheMachine},
{"06-bitfield-tapestry", drawBitfieldTapestry},
}
for _, p := range pieces {
img := image.NewRGBA(image.Rect(0, 0, size, size))
p.Draw(img, size, size)
path := filepath.Join(outDir, p.Name+".png")
f, err := os.Create(path)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating %s: %v\n", path, err)
continue
}
if err := png.Encode(f, img); err != nil {
fmt.Fprintf(os.Stderr, "error encoding %s: %v\n", path, err)
}
f.Close()
fmt.Printf("✓ %s\n", path)
}
fmt.Println("\n🎪 All done!")
}
func clamp(v float64) uint8 {
if v < 0 {
return 0
}
if v > 255 {
return 255
}
return uint8(v)
}
// 1: Pixel Sunrise Corruption — sine waves tearing through a dawn
func drawPixelSunrise(img *image.RGBA, w, h int) {
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
fx, fy := float64(x), float64(y)
wave := math.Sin(fx*0.15+fy*0.05)*127 + 128
glitch := float64((x * y * 7) % 255)
blend := fy / float64(h)
r := wave*(1-blend) + glitch*blend
g := math.Sin(fy*0.1)*60 + 40
b := 255 - wave*blend
// Horizontal tear glitch
dx := 0
if math.Sin(fy*0.35) > 0.9 {
dx = rand.Intn(6) - 3
}
px := x + dx
if px >= 0 && px < w {
img.SetRGBA(px, y, color.RGBA{clamp(r), clamp(g), clamp(b), 255})
}
}
}
}
// 2: Memory Leak Quilt — XOR patterns with data rot
func drawMemoryLeakQuilt(img *image.RGBA, w, h int) {
colors := []color.RGBA{
{0xff, 0x00, 0x6e, 255},
{0x83, 0x38, 0xec, 255},
{0x3a, 0x86, 0xff, 255},
{0x06, 0xd6, 0xa0, 255},
{0xff, 0xbe, 0x0b, 255},
}
blockSize := 4 // scale up from the 64px version
for y := 0; y < h; y += blockSize {
for x := 0; x < w; x += blockSize {
bx, by := x/blockSize, y/blockSize
i := ((bx ^ by) * 13 + (bx & by) * 7) % len(colors)
corrupt := math.Sin(float64(bx)*float64(by)*0.01) > 0.7
if corrupt {
// Data rot — black streak
stretchW := rand.Intn(8)*blockSize + blockSize
for dy := 0; dy < blockSize && y+dy < h; dy++ {
for dx := 0; dx < stretchW && x+dx < w; dx++ {
img.SetRGBA(x+dx, y+dy, color.RGBA{0x0a, 0x0a, 0x0a, 255})
}
}
} else {
c := colors[i]
for dy := 0; dy < blockSize && y+dy < h; dy++ {
for dx := 0; dx < blockSize && x+dx < w; dx++ {
img.SetRGBA(x+dx, y+dy, c)
}
}
}
}
}
}
// 3: Signal From Nowhere — concentric rings dissolving into static
func drawSignalFromNowhere(img *image.RGBA, w, h int) {
for y := 0; y < h; y++ {
scanline := math.Sin(float64(y)*0.2)*0.3 + 0.7
for x := 0; x < w; x++ {
fx, fy := float64(x), float64(y)
dist := math.Hypot(fx-float64(w)/2, fy-float64(h)/2)
ring := math.Sin(dist*0.25)*127 + 128
noise := float64((x*2347 + y*8461) % 256)
blend := math.Max(0, 1-dist/(float64(w)*0.5))
v := (ring*blend + noise*(1-blend)) * scanline
shift := 0.0
if math.Sin(fy*0.075) > 0.85 {
shift = 30
}
r := clamp(v + shift)
g := clamp(v * 0.7)
b := clamp(v * 1.2)
img.SetRGBA(x, y, color.RGBA{r, g, b, 255})
}
}
}
// 4: Databend Sunset — channel-swapped sky with corruption streaks
func drawDatabendSunset(img *image.RGBA, w, h int) {
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
sky := float64(y) / float64(h)
r := 255 * (1 - sky*0.6)
g := 100*(1-sky) + 50*sky
b := 50 + 200*sky
corrupt := ((x*131 + y*97) % 100) < 8
if corrupt {
// Channel swap + stretch
r, g, b = b, r, g
stretchW := 3 + (x % 5)
for dx := 0; dx < stretchW && x+dx < w; dx++ {
img.SetRGBA(x+dx, y, color.RGBA{clamp(r), clamp(g), clamp(b), 255})
}
} else {
// Sun
sunDist := math.Hypot(float64(x)-float64(w)*0.5, float64(y)-float64(h)*0.35)
sunRadius := float64(w) * 0.09
if sunDist < sunRadius {
r = 255
g = 200 + math.Sin(sunDist)*55
b = 50
}
img.SetRGBA(x, y, color.RGBA{clamp(r), clamp(g), clamp(b), 255})
}
}
}
}
// 5: Ghost in the Machine — a figure emerging from noise
func drawGhostInTheMachine(img *image.RGBA, w, h int) {
// Fill dark
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.SetRGBA(x, y, color.RGBA{0x05, 0x05, 0x10, 255})
}
}
// Scale particle count with image size
particles := size * 50
for i := 0; i < particles; i++ {
t := float64(i) / float64(particles)
cx := float64(w)/2 + math.Sin(t*20)*float64(w)*(0.12-t*0.09)
cy := float64(h)*0.15 + t*float64(h)*0.7
spread := math.Sin(t*math.Pi) * float64(w) * 0.09
px := int(cx + (rand.Float64()-0.5)*spread*2)
py := int(cy + (rand.Float64()-0.5)*3)
if px < 0 || px >= w || py < 0 || py >= h {
continue
}
alpha := (1 - t) * 0.8
if rand.Float64() > 0.95 {
// Glitch scar — red horizontal streak
streakLen := 10 + rand.Intn(int(float64(w)*0.06))
for dx := -5; dx < streakLen; dx++ {
sx := px + dx
if sx >= 0 && sx < w {
r := uint8(float64(255) * alpha)
img.SetRGBA(sx, py, color.RGBA{r, 0, uint8(float64(100) * alpha), 255})
}
}
} else {
r := uint8(float64(150) * alpha)
g := uint8(float64(200) * alpha)
b := uint8(float64(255) * alpha)
img.SetRGBA(px, py, color.RGBA{r, g, b, 255})
}
}
}
// 6: Bitfield Tapestry — AND, OR, XOR woven into color
func drawBitfieldTapestry(img *image.RGBA, w, h int) {
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
v1 := (x ^ y) % 16
v2 := (x & y) % 32
v3 := ((x | y) * 3) % 64
r := uint8(v1 * 16)
g := uint8(v2 * 8)
b := uint8(v3 * 4)
img.SetRGBA(x, y, color.RGBA{r, g, b, 255})
}
}
}