🎪 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.
This commit is contained in:
commit
bc94c774dd
8 changed files with 423 additions and 0 deletions
176
index.html
Normal file
176
index.html
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Ed's Glitch Garden</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; background: #0a0a0a; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; min-height: 100vh; gap: 16px; padding: 16px; font-family: monospace; }
|
||||||
|
canvas { image-rendering: pixelated; border: 2px solid #222; }
|
||||||
|
h1 { width: 100%; text-align: center; color: #0ff; font-size: 14px; letter-spacing: 4px; text-transform: uppercase; }
|
||||||
|
p { width: 100%; text-align: center; color: #555; font-size: 11px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🎪 Ed's Glitch Garden 🎪</h1>
|
||||||
|
<script>
|
||||||
|
// Each piece is a tiny generative world
|
||||||
|
const pieces = [
|
||||||
|
// 1: Pixel Sunrise Corruption
|
||||||
|
(ctx, w, h) => {
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
const wave = Math.sin(x * 0.3 + y * 0.1) * 127 + 128;
|
||||||
|
const glitch = (x * y * 7) % 255;
|
||||||
|
const blend = y / h;
|
||||||
|
const r = wave * (1 - blend) + glitch * blend;
|
||||||
|
const g = Math.sin(y * 0.2) * 60 + 40;
|
||||||
|
const b = 255 - wave * blend;
|
||||||
|
// Horizontal tear glitch
|
||||||
|
const tear = Math.sin(y * 0.7) > 0.9 ? Math.floor(Math.random() * 6) - 3 : 0;
|
||||||
|
ctx.fillStyle = `rgb(${r|0},${g|0},${b|0})`;
|
||||||
|
ctx.fillRect(x + tear, y, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 2: Memory Leak Quilt
|
||||||
|
(ctx, w, h) => {
|
||||||
|
const colors = ['#ff006e','#8338ec','#3a86ff','#06d6a0','#ffbe0b'];
|
||||||
|
for (let y = 0; y < h; y += 2) {
|
||||||
|
for (let x = 0; x < w; x += 2) {
|
||||||
|
const i = ((x ^ y) * 13 + (x & y) * 7) % colors.length;
|
||||||
|
const corrupt = Math.sin(x * y * 0.01) > 0.7;
|
||||||
|
if (corrupt) {
|
||||||
|
ctx.fillStyle = '#0a0a0a';
|
||||||
|
ctx.fillRect(x, y, Math.random() * 8 | 0, 2);
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = colors[i];
|
||||||
|
ctx.fillRect(x, y, 2, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 3: Signal From Nowhere
|
||||||
|
(ctx, w, h) => {
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
const scanline = Math.sin(y * 0.4) * 0.3 + 0.7;
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
const dist = Math.hypot(x - w/2, y - h/2);
|
||||||
|
const ring = Math.sin(dist * 0.5) * 127 + 128;
|
||||||
|
const noise = ((x * 2347 + y * 8461) % 256);
|
||||||
|
const blend = Math.max(0, 1 - dist / (w * 0.5));
|
||||||
|
const v = (ring * blend + noise * (1 - blend)) * scanline;
|
||||||
|
const shift = Math.sin(y * 0.15) > 0.85 ? 30 : 0;
|
||||||
|
ctx.fillStyle = `rgb(${(v + shift)|0},${(v * 0.7)|0},${(v * 1.2)|0})`;
|
||||||
|
ctx.fillRect(x, y, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 4: Databend Sunset
|
||||||
|
(ctx, w, h) => {
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
const sky = y / h;
|
||||||
|
let r = 255 * (1 - sky * 0.6);
|
||||||
|
let g = 100 * (1 - sky) + 50 * sky;
|
||||||
|
let b = 50 + 200 * sky;
|
||||||
|
// Databend: shift channels based on pseudo-random corruption
|
||||||
|
const corrupt = ((x * 131 + y * 97) % 100) < 8;
|
||||||
|
if (corrupt) {
|
||||||
|
const tmp = r; r = b; b = g; g = tmp;
|
||||||
|
// Stretch pixel
|
||||||
|
ctx.fillStyle = `rgb(${r|0},${g|0},${b|0})`;
|
||||||
|
ctx.fillRect(x, y, 3 + (x % 5), 1);
|
||||||
|
} else {
|
||||||
|
// Sun
|
||||||
|
const sunDist = Math.hypot(x - w * 0.5, y - h * 0.35);
|
||||||
|
if (sunDist < 12) {
|
||||||
|
r = 255; g = 200 + Math.sin(sunDist) * 55; b = 50;
|
||||||
|
}
|
||||||
|
ctx.fillStyle = `rgb(${r|0},${g|0},${b|0})`;
|
||||||
|
ctx.fillRect(x, y, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 5: Ghost in the Machine
|
||||||
|
(ctx, w, h) => {
|
||||||
|
// Fill black
|
||||||
|
ctx.fillStyle = '#050510';
|
||||||
|
ctx.fillRect(0, 0, w, h);
|
||||||
|
// Draw ghostly figure from noise
|
||||||
|
for (let i = 0; i < 3000; i++) {
|
||||||
|
const t = i / 3000;
|
||||||
|
const cx = w/2 + Math.sin(t * 20) * (8 - t * 6);
|
||||||
|
const cy = h * 0.15 + t * h * 0.7;
|
||||||
|
const spread = Math.sin(t * 3.14) * 6;
|
||||||
|
const x = cx + (Math.random() - 0.5) * spread * 2;
|
||||||
|
const y = cy + (Math.random() - 0.5) * 3;
|
||||||
|
const alpha = (1 - t) * 0.8;
|
||||||
|
const glitch = Math.random() > 0.95;
|
||||||
|
if (glitch) {
|
||||||
|
ctx.fillStyle = `rgba(255,0,100,${alpha})`;
|
||||||
|
ctx.fillRect(x - 5, y, 10 + Math.random() * 15, 1);
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = `rgba(150,200,255,${alpha})`;
|
||||||
|
ctx.fillRect(x, y, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 6: Bitfield Tapestry
|
||||||
|
(ctx, w, h) => {
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
const v1 = (x ^ y) % 16;
|
||||||
|
const v2 = (x & y) % 32;
|
||||||
|
const v3 = ((x | y) * 3) % 64;
|
||||||
|
const r = v1 * 16;
|
||||||
|
const g = v2 * 8;
|
||||||
|
const b = v3 * 4;
|
||||||
|
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
||||||
|
ctx.fillRect(x, y, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const titles = [
|
||||||
|
'Pixel Sunrise Corruption',
|
||||||
|
'Memory Leak Quilt',
|
||||||
|
'Signal From Nowhere',
|
||||||
|
'Databend Sunset',
|
||||||
|
'Ghost in the Machine',
|
||||||
|
'Bitfield Tapestry',
|
||||||
|
];
|
||||||
|
|
||||||
|
pieces.forEach((draw, i) => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const size = 64;
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
canvas.style.width = '256px';
|
||||||
|
canvas.style.height = '256px';
|
||||||
|
canvas.title = titles[i];
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
draw(ctx, size, size);
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.style.textAlign = 'center';
|
||||||
|
wrapper.appendChild(canvas);
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.style.color = '#0ff';
|
||||||
|
label.style.fontSize = '11px';
|
||||||
|
label.style.marginTop = '4px';
|
||||||
|
label.style.fontFamily = 'monospace';
|
||||||
|
label.textContent = titles[i];
|
||||||
|
wrapper.appendChild(label);
|
||||||
|
document.body.appendChild(wrapper);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<p>each piece is 64×64 pixels of pure math × chaos</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
247
main.go
Normal file
247
main.go
Normal file
|
|
@ -0,0 +1,247 @@
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
output/01-pixel-sunrise-corruption.png
Normal file
BIN
output/01-pixel-sunrise-corruption.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 428 KiB |
BIN
output/02-memory-leak-quilt.png
Normal file
BIN
output/02-memory-leak-quilt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
output/03-signal-from-nowhere.png
Normal file
BIN
output/03-signal-from-nowhere.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 311 KiB |
BIN
output/04-databend-sunset.png
Normal file
BIN
output/04-databend-sunset.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
output/05-ghost-in-the-machine.png
Normal file
BIN
output/05-ghost-in-the-machine.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
output/06-bitfield-tapestry.png
Normal file
BIN
output/06-bitfield-tapestry.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Loading…
Reference in a new issue