The (Claude) code:
--# Main
-- =========================================
-- Main
-- Simple placeholder image viewer with
-- "New Image" button
-- =========================================
local currentImage = nil
local buttonRect = {x=0, y=0, w=0, h=0}
local padding = 12
local buttonHeight = 50
function setup()
-- Generate initial image
generateNewImage()
end
function draw()
background(40, 40, 45)
-- Calculate button area
buttonRect.x = padding
buttonRect.y = HEIGHT - buttonHeight - padding
buttonRect.w = 120
buttonRect.h = buttonHeight
-- Draw button
drawButton()
-- Draw current image centered
if currentImage then
local imgW = currentImage.width
local imgH = currentImage.height
local availH = HEIGHT - buttonHeight - padding * 3
-- Scale to fit
local scale = math.min(
(WIDTH - padding * 2) / imgW,
availH / imgH
)
local drawW = imgW * scale
local drawH = imgH * scale
local drawX = WIDTH * 0.5
local drawY = (availH + padding) * 0.5 + padding
noTint()
sprite(currentImage, drawX, drawY, drawW, drawH)
end
end
function touched(touch)
if touch.state == ENDED then
if pointInRect(touch.x, touch.y, buttonRect) then
generateNewImage()
end
end
end
-- =========================================
-- Helper functions
-- =========================================
function generateNewImage()
local w = 512
local h = 512
currentImage = placeholderImage(w, h)
end
function drawButton()
-- Button background
fill(100, 150, 200, 255)
stroke(70, 120, 170, 255)
strokeWidth(2)
rect(buttonRect.x, buttonRect.y,
buttonRect.w, buttonRect.h)
-- Button text
fill(255, 255, 255, 255)
noStroke()
font("Helvetica")
fontSize(18)
textAlign(CENTER)
text("New Image",
buttonRect.x + buttonRect.w * 0.5,
buttonRect.y + buttonRect.h * 0.5)
end
function pointInRect(px, py, rect)
return px >= rect.x and
px <= rect.x + rect.w and
py >= rect.y and
py <= rect.y + rect.h
end
--# PlaceholderImage
-- PlaceholderImage.lua
-- Six procedural image generators.
-- Usage:
-- local img = PlaceholderImage.generate(w, h)
-- local img = PlaceholderImage.generate(w, h, {mode="flowfield"})
-- Modes: "flowfield", "circlepack", "mondrian", "phyllotaxis", "lissajous", "truchet"
PlaceholderImage = {}
local MODES = {
"flowfield", "circlepack", "mondrian",
"phyllotaxis", "lissajous", "truchet"
}
-- =========================================
-- Color utilities
-- =========================================
local function hsv(h, s, v)
h = h % 1
local i = math.floor(h * 6)
local f = h * 6 - i
local p = v * (1 - s)
local q = v * (1 - f * s)
local t = v * (1 - (1 - f) * s)
local r, g, b
i = i % 6
if i == 0 then r,g,b = v,t,p
elseif i == 1 then r,g,b = q,v,p
elseif i == 2 then r,g,b = p,v,t
elseif i == 3 then r,g,b = p,q,v
elseif i == 4 then r,g,b = t,p,v
else r,g,b = v,p,q
end
return color(r*255, g*255, b*255, 255)
end
local function randomHue()
return math.random()
end
-- =========================================
-- 1. Flow field
-- Uses noise() to set angles, draws short
-- strokes following the field.
-- =========================================
local function genFlowField(w, h)
local hue = randomHue()
local bg = hsv(hue, 0.7, 0.12)
background(bg.r, bg.g, bg.b, 255)
local nOff = math.random() * 100
local nScale = 0.003 + math.random() * 0.004
local nLines = 500
local nSteps = 30
local stepLen = math.max(w, h) * 0.018
local alpha = 120
strokeWidth(1.2)
noFill()
for _ = 1, nLines do
local x = math.random() * w
local y = math.random() * h
local sat = 0.5 + math.random() * 0.5
local val = 0.6 + math.random() * 0.4
local c = hsv(hue + math.random() * 0.15 - 0.07, sat, val)
stroke(c.r, c.g, c.b, alpha)
for _ = 1, nSteps do
local nx = x * nScale + nOff
local ny = y * nScale + nOff
local angle = noise(nx, ny) * math.pi * 4
local x2 = x + math.cos(angle) * stepLen
local y2 = y + math.sin(angle) * stepLen
line(x, y, x2, y2)
x, y = x2, y2
if x < 0 or x > w or y < 0 or y > h then break end
end
end
end
-- =========================================
-- 2. Circle packing
-- Place non-overlapping circles, grow them.
-- =========================================
local function genCirclePack(w, h)
local hue = randomHue()
local bg = hsv(hue + 0.5, 0.6, 0.1)
background(bg.r, bg.g, bg.b, 255)
local circles = {}
local maxR = math.min(w, h) * 0.18
local minR = math.min(w, h) * 0.02
local tries = 800
local function overlaps(cx, cy, cr)
for _, c in ipairs(circles) do
local dx = cx - c.x
local dy = cy - c.y
if dx*dx + dy*dy < (cr + c.r + 2)^2 then
return true
end
end
return false
end
for _ = 1, tries do
local cx = minR + math.random() * (w - minR * 2)
local cy = minR + math.random() * (h - minR * 2)
if not overlaps(cx, cy, minR) then
-- grow
local cr = minR
while cr < maxR do
if overlaps(cx, cy, cr + 1) then break end
cr = cr + 1
end
table.insert(circles, {x=cx, y=cy, r=cr})
end
end
noStroke()
for _, c in ipairs(circles) do
local t = c.r / maxR
local col = hsv(hue + t * 0.3, 0.7, 0.4 + t * 0.5)
fill(col.r, col.g, col.b, 210)
ellipse(c.x, c.y, c.r * 2)
-- inner highlight
fill(255, 255, 255, 30)
ellipse(c.x + c.r * 0.2, c.y + c.r * 0.2, c.r * 0.6)
end
end
-- =========================================
-- 3. Mondrian
-- Recursive rectangle subdivision.
-- =========================================
local function genMondrian(w, h)
background(240, 235, 220, 255)
local COLORS = {
color(220, 50, 40),
color(30, 80, 160),
color(240, 200, 50),
color(240, 235, 220),
color(240, 235, 220),
color(240, 235, 220),
}
local rects = {}
local border = 3
local function split(x, y, rw, rh, depth)
if depth == 0 or (rw < w*0.12 and rh < h*0.12) then
table.insert(rects, {x=x, y=y, w=rw, h=rh})
return
end
local horiz = rh > rw
if rw > w * 0.5 and rh > h * 0.5 then
horiz = math.random() < 0.5
elseif rw > rh then
horiz = false
end
if horiz then
local cut = rh * (0.3 + math.random() * 0.4)
split(x, y, rw, cut, depth - 1)
split(x, y+cut, rw, rh-cut, depth - 1)
else
local cut = rw * (0.3 + math.random() * 0.4)
split(x, y, cut, rh, depth - 1)
split(x+cut, y, rw-cut, rh, depth - 1)
end
end
split(0, 0, w, h, 5)
for _, r in ipairs(rects) do
local c = COLORS[math.random(#COLORS)]
fill(c.r, c.g, c.b, 255)
stroke(20, 20, 20, 255)
strokeWidth(border * 2)
rect(r.x + border, r.y + border,
r.w - border*2, r.h - border*2)
end
end
-- =========================================
-- 4. Phyllotaxis
-- Sunflower spiral using golden angle.
-- =========================================
local function genPhyllotaxis(w, h)
local hue = randomHue()
local bg = hsv(hue + 0.5, 0.8, 0.08)
background(bg.r, bg.g, bg.b, 255)
local golden = math.pi * (3 - math.sqrt(5))
local n = 600 + math.random(300)
local scale = math.min(w, h) * 0.46
local cx = w * 0.5
local cy = h * 0.5
local spread = 0.95 + math.random() * 0.3
noStroke()
for i = 1, n do
local r = scale * math.sqrt(i / n) * spread
local theta = i * golden
local x = cx + r * math.cos(theta)
local y = cy + r * math.sin(theta)
local t = i / n
local size = (2 + t * 6)
local col = hsv(hue + t * 0.25, 0.8, 0.5 + t * 0.5)
fill(col.r, col.g, col.b, 200)
ellipse(x, y, size, size)
end
end
-- =========================================
-- 5. Lissajous
-- Parametric curves: x=sin(at+d), y=sin(bt)
-- =========================================
local function genLissajous(w, h)
local hue = randomHue()
local bg = hsv(hue + 0.5, 0.9, 0.07)
background(bg.r, bg.g, bg.b, 255)
-- Pick two small coprime-ish integers for a, b
local pairs_ab = {
{1,2},{1,3},{2,3},{3,4},{3,5},{4,5},{5,6},{2,5}
}
local ab = pairs_ab[math.random(#pairs_ab)]
local a = ab[1]
local b = ab[2]
local delta = math.random() * math.pi
local nCurves = 4
local steps = 600
local rx = w * 0.42
local ry = h * 0.42
local cx = w * 0.5
local cy = h * 0.5
noFill()
for c = 1, nCurves do
local d = delta + (c - 1) * math.pi / (nCurves * 2)
local col = hsv(hue + c / nCurves * 0.3, 0.8, 0.9)
stroke(col.r, col.g, col.b, 180 - c * 20)
strokeWidth(1.5)
local prev_x, prev_y
for i = 0, steps do
local t = i / steps * math.pi * 2
local x = cx + rx * math.sin(a * t + d)
local y = cy + ry * math.sin(b * t)
if prev_x then line(prev_x, prev_y, x, y) end
prev_x, prev_y = x, y
end
end
end
-- =========================================
-- 6. Truchet tiles
-- Grid of squares with randomly oriented arcs.
-- =========================================
local function genTruchet(w, h)
local hue = randomHue()
local bg = hsv(hue, 0.5, 0.15)
background(bg.r, bg.g, bg.b, 255)
local cols = 10 + math.random(6)
local rows = cols
local tw = w / cols
local th = h / rows
local lw = tw * 0.18
noFill()
local fg = hsv(hue + 0.55, 0.7, 0.9)
stroke(fg.r, fg.g, fg.b, 220)
strokeWidth(lw)
-- Each tile: two quarter-circle arcs, one of two orientations
-- Orientation 0: arcs connect bottom-left to top-left, and top-right to bottom-right
-- Orientation 1: arcs connect bottom-left to bottom-right, and top-left to top-right
-- We approximate arcs with line segments
local arcSteps = 12
local function arc(ox, oy, r, startAngle, endAngle)
local prev_x, prev_y
for i = 0, arcSteps do
local t = i / arcSteps
local angle = startAngle + t * (endAngle - startAngle)
local x = ox + r * math.cos(angle)
local y = oy + r * math.sin(angle)
if prev_x then line(prev_x, prev_y, x, y) end
prev_x, prev_y = x, y
end
end
for col = 0, cols - 1 do
for row = 0, rows - 1 do
local x = col * tw
local y = row * th
local cx = x + tw * 0.5
local cy = y + th * 0.5
if math.random() < 0.5 then
-- Arcs from corners: BL-TL and TR-BR
arc(x, y, tw*0.5, 0, math.pi*0.5)
arc(x + tw, y + th, tw*0.5, math.pi, math.pi*1.5)
else
-- Arcs from corners: BL-BR and TL-TR
arc(x, y + th, tw*0.5, -math.pi*0.5, 0)
arc(x + tw, y, tw*0.5, math.pi*0.5, math.pi)
end
end
end
end
-- =========================================
-- Public API
-- =========================================
local generators = {
flowfield = genFlowField,
circlepack = genCirclePack,
mondrian = genMondrian,
phyllotaxis = genPhyllotaxis,
lissajous = genLissajous,
truchet = genTruchet,
}
function PlaceholderImage.generate(w, h, opts)
opts = opts or {}
-- Pick two different modes
local shuffled = {table.unpack(MODES)}
for i = #shuffled, 2, -1 do
local j = math.random(i)
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
end
local modeA = opts.mode or shuffled[1]
local modeB = shuffled[2]
-- Random background
local bgCol = hsv(math.random(), 0.4 + math.random() * 0.4, 0.1 + math.random() * 0.2)
-- Generate each pattern into its own offscreen image
local function makeLayer(modeName)
local img = image(w, h)
setContext(img)
pushStyle()
background(0, 0, 0, 0)
generators[modeName](w, h)
popStyle()
setContext()
return img
end
local imgA = makeLayer(modeA)
local imgB = makeLayer(modeB)
-- Composite onto final image
local final = image(w, h)
setContext(final)
pushStyle()
-- Background
background(bgCol.r, bgCol.g, bgCol.b, 255)
local function drawTransformed(img, alpha)
local angle = math.random() * 360
local zoom = 0.7 + math.random() * 0.9
pushMatrix()
translate(w * 0.5, h * 0.5)
rotate(angle)
scale(zoom)
translate(-w * 0.5, -h * 0.5)
tint(255, 255, 255, alpha)
sprite(img, w * 0.5, h * 0.5, w, h)
noTint()
popMatrix()
end
drawTransformed(imgA, 255)
drawTransformed(imgB, 80 + math.random(140))
popStyle()
setContext()
return final, modeA .. "+" .. modeB
end
function PlaceholderImage.modes()
return MODES
end
-- =========================================
-- Simple wrapper for convenience
-- =========================================
function placeholderImage(w, h)
local img, modes = PlaceholderImage.generate(w, h)
return img
end