diff --git a/art/generated/textures/all/PROMPTS.md b/art/generated/textures/all/PROMPTS.md index 0ba97a6..4fc0e81 100644 --- a/art/generated/textures/all/PROMPTS.md +++ b/art/generated/textures/all/PROMPTS.md @@ -45,9 +45,9 @@ Side view, standing on the bottom edge of the image and filling most of its widt | steel | WALL | A wall of dark riveted iron plates with rows of round rivets along the seams, a little rust and soot. | | obsidian | STYLE | Cooled lava rock: black and very dark purple volcanic glass with smooth glassy facets and a few thin faintly glowing orange seams. | | face | WALL | A cut vertical face of packed brown earth with small stones and roots, slightly uneven. | -| treasury | STYLE | The floor of a treasure vault: polished dark stone flagstones with inlaid golden bands and a few scattered gold coins. | +| treasury | STYLE | The floor of a treasure vault: a checkerboard of square floor tiles in warm golden-ochre and darker bronze-brown, each tile slightly bevelled and worn, a few small gold coins lying in some joints. | | lair | STYLE | A monster lair floor: packed earth covered with scattered straw, old furs, a few bones and dirty rags. | -| farm | STYLE | A mushroom garden floor: dark moist soil in tilled furrows with tiny sprouting mushrooms and spores. | +| farm | STYLE | A mushroom garden floor: brown moist soil covered with large irregular patches of bright green moss, a few tiny pale mushrooms growing in the moss. | | training | STYLE | A training hall floor: worn wooden planks with sawdust, scuff marks and a few dark stains. | | library | STYLE | A repeating herringbone parquet of dark wooden boards, worn, with a few faint scratched arcane symbols and ink stains - no carpet, no large motif, no border. | | torture | STYLE | A torture chamber floor: dark stained flagstones with rusty iron drain grates and a few chain links. | @@ -86,3 +86,12 @@ Side view, standing on the bottom edge of the image and filling most of its widt The reinforced wall of the pixel-art atlas is drawn in code (`T_REINF` in `gen_atlas`: courses of 6, 5 and 5 pixels cut into blocks of 5-9 pixels). + +## Closer to the pixel art (2026-11-07) + +- Treasury and mushroom garden were made again (`seed: 4302`, the prompts in the table above) + after the pixel-art cells: a golden checkerboard and brown soil with green moss; the first + pictures are in `old/`. +- The game moves the colours of every generated picture most of the way to those of the 16x16 + cells it replaces (mean and contrast per channel, `match_colors` in `src/gfx.rs`), so both + styles keep the same colours. diff --git a/art/generated/textures/all/batch.py b/art/generated/textures/all/batch.py index a7566d4..0cb8a21 100644 --- a/art/generated/textures/all/batch.py +++ b/art/generated/textures/all/batch.py @@ -16,9 +16,9 @@ TEX = { "obsidian": (STYLE, "Cooled lava rock: black and very dark purple volcanic glass with smooth glassy facets and a few thin faintly glowing orange seams."), "face": (WALL, "A cut vertical face of packed brown earth with small stones and roots, slightly uneven."), # rooms - "treasury": (STYLE, "The floor of a treasure vault: polished dark stone flagstones with inlaid golden bands and a few scattered gold coins."), + "treasury": (STYLE, "The floor of a treasure vault: a checkerboard of square floor tiles in warm golden-ochre and darker bronze-brown, each tile slightly bevelled and worn, a few small gold coins lying in some joints."), "lair": (STYLE, "A monster lair floor: packed earth covered with scattered straw, old furs, a few bones and dirty rags."), - "farm": (STYLE, "A mushroom garden floor: dark moist soil in tilled furrows with tiny sprouting mushrooms and spores."), + "farm": (STYLE, "A mushroom garden floor: brown moist soil covered with large irregular patches of bright green moss, a few tiny pale mushrooms growing in the moss."), "training": (STYLE, "A training hall floor: worn wooden planks with sawdust, scuff marks and a few dark stains."), "library": (STYLE, "A repeating herringbone parquet of dark wooden boards, worn, with a few faint scratched arcane symbols and ink stains - no carpet, no large motif, no border."), "torture": (STYLE, "A torture chamber floor: dark stained flagstones with rusty iron drain grates and a few chain links."), diff --git a/art/generated/textures/all/farm-raw.png b/art/generated/textures/all/farm-raw.png index fffabea..313afa2 100644 Binary files a/art/generated/textures/all/farm-raw.png and b/art/generated/textures/all/farm-raw.png differ diff --git a/art/generated/textures/all/farm-v128-preview.png b/art/generated/textures/all/farm-v128-preview.png index fadb461..c945442 100644 Binary files a/art/generated/textures/all/farm-v128-preview.png and b/art/generated/textures/all/farm-v128-preview.png differ diff --git a/art/generated/textures/all/farm-v128.png b/art/generated/textures/all/farm-v128.png index a1e90c0..0d6fb4e 100644 Binary files a/art/generated/textures/all/farm-v128.png and b/art/generated/textures/all/farm-v128.png differ diff --git a/art/generated/textures/all/farm-v64-preview.png b/art/generated/textures/all/farm-v64-preview.png index 582a0e2..f0ba539 100644 Binary files a/art/generated/textures/all/farm-v64-preview.png and b/art/generated/textures/all/farm-v64-preview.png differ diff --git a/art/generated/textures/all/farm-v64.png b/art/generated/textures/all/farm-v64.png index 64e57da..3764522 100644 Binary files a/art/generated/textures/all/farm-v64.png and b/art/generated/textures/all/farm-v64.png differ diff --git a/art/generated/textures/all/old/farm-raw.png b/art/generated/textures/all/old/farm-raw.png new file mode 100644 index 0000000..fffabea Binary files /dev/null and b/art/generated/textures/all/old/farm-raw.png differ diff --git a/art/generated/textures/all/old/treasury-raw.png b/art/generated/textures/all/old/treasury-raw.png new file mode 100644 index 0000000..6f57674 Binary files /dev/null and b/art/generated/textures/all/old/treasury-raw.png differ diff --git a/art/generated/textures/all/treasury-raw.png b/art/generated/textures/all/treasury-raw.png index 6f57674..665e353 100644 Binary files a/art/generated/textures/all/treasury-raw.png and b/art/generated/textures/all/treasury-raw.png differ diff --git a/art/generated/textures/all/treasury-v128-preview.png b/art/generated/textures/all/treasury-v128-preview.png index 97f6081..33657a6 100644 Binary files a/art/generated/textures/all/treasury-v128-preview.png and b/art/generated/textures/all/treasury-v128-preview.png differ diff --git a/art/generated/textures/all/treasury-v128.png b/art/generated/textures/all/treasury-v128.png index cc94262..31aabfb 100644 Binary files a/art/generated/textures/all/treasury-v128.png and b/art/generated/textures/all/treasury-v128.png differ diff --git a/art/generated/textures/all/treasury-v64-preview.png b/art/generated/textures/all/treasury-v64-preview.png index 7eb1b17..b62d282 100644 Binary files a/art/generated/textures/all/treasury-v64-preview.png and b/art/generated/textures/all/treasury-v64-preview.png differ diff --git a/art/generated/textures/all/treasury-v64.png b/art/generated/textures/all/treasury-v64.png index 3b2279b..58634fd 100644 Binary files a/art/generated/textures/all/treasury-v64.png and b/art/generated/textures/all/treasury-v64.png differ diff --git a/assets/textures/hd/farm-v128.png b/assets/textures/hd/farm-v128.png index a1e90c0..0d6fb4e 100644 Binary files a/assets/textures/hd/farm-v128.png and b/assets/textures/hd/farm-v128.png differ diff --git a/assets/textures/hd/farm-v64.png b/assets/textures/hd/farm-v64.png index 64e57da..3764522 100644 Binary files a/assets/textures/hd/farm-v64.png and b/assets/textures/hd/farm-v64.png differ diff --git a/assets/textures/hd/treasury-v128.png b/assets/textures/hd/treasury-v128.png index cc94262..31aabfb 100644 Binary files a/assets/textures/hd/treasury-v128.png and b/assets/textures/hd/treasury-v128.png differ diff --git a/assets/textures/hd/treasury-v64.png b/assets/textures/hd/treasury-v64.png index 3b2279b..58634fd 100644 Binary files a/assets/textures/hd/treasury-v64.png and b/assets/textures/hd/treasury-v64.png differ diff --git a/src/gfx.rs b/src/gfx.rs index d5a37d9..21e5bfa 100644 --- a/src/gfx.rs +++ b/src/gfx.rs @@ -458,6 +458,59 @@ pub fn hd_tile(id: u32, x: i32, z: i32) -> u32 { } } +// mean and standard deviation per channel of the opaque texels of some cells +fn cell_stats(img: &Image, cells: &[u32], tile: u32) -> ([f32; 3], [f32; 3]) { + let (mut sum, mut sq, mut n) = ([0f32; 3], [0f32; 3], 0f32); + for &id in cells { + let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile); + for y in 0..tile { + for x in 0..tile { + let p = img.get_pixel(ox + x, oy + y); + if p.a < 0.5 { + continue; + } + for (c, v) in [p.r, p.g, p.b].into_iter().enumerate() { + sum[c] += v; + sq[c] += v * v; + } + n += 1.0; + } + } + } + let n = n.max(1.0); + let mean = sum.map(|s| s / n); + let mut sd = [0f32; 3]; + for c in 0..3 { + sd[c] = (sq[c] / n - mean[c] * mean[c]).max(0.0).sqrt(); + } + (mean, sd) +} + +// moves the colours of the high-res cells `dst` towards those of the 16x16 cells `src` +fn match_colors(img: &mut Image, small: &Image, src: &[u32], dst: &[u32], tile: u32) { + const STRENGTH: f32 = 0.8; + let (pm, ps) = cell_stats(small, src, 16); + let (hm, hs) = cell_stats(img, dst, tile); + for &id in dst { + let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile); + for y in 0..tile { + for x in 0..tile { + let p = img.get_pixel(ox + x, oy + y); + if p.a < 0.5 { + continue; + } + let mut q = [p.r, p.g, p.b]; + for c in 0..3 { + let k = (ps[c] / hs[c].max(0.01)).clamp(0.6, 1.6); + let m = pm[c] + (q[c] - hm[c]) * k; + q[c] = (q[c] + (m - q[c]) * STRENGTH).clamp(0.0, 1.0); + } + img.set_pixel(ox + x, oy + y, Color::new(q[0], q[1], q[2], p.a)); + } + } + } +} + // a high-res atlas with `tile` x `tile` texel cells (HD_TILE or HD_TILE2) fn gen_hd_atlas(tile: u32) -> Image { let small = gen_atlas(); @@ -515,6 +568,25 @@ fn gen_hd_atlas(tile: u32) -> Image { } } } + // the generated pictures take on the colours of the 16x16 cells they replace (mean and + // contrast per channel, most of the way), so switching between the styles keeps the look + let range = |a: u32, n: u32| (a..a + n).collect::>(); + let mut groups: Vec<(Vec, Vec)> = Vec::new(); + for &(base, n, _, _) in HD_TILES.iter() { + groups.push((range(base, n), range(base, n))); + } + for &(first, n, own, _, _, _) in HD_SETS.iter() { + groups.push((range(first, n), range(own, 5))); + } + for &(base, n, _, _) in HD_VARS.iter() { + groups.push((range(base, n), range(base, n))); + } + for &(id, _, _) in HD_DECALS.iter().skip(2) { + groups.push((vec![id], vec![id])); + } + for (src, dst) in groups.iter() { + match_colors(&mut img, &small, src, dst, tile); + } img } @@ -2877,6 +2949,20 @@ mod hd_atlas_tests { let p = img.get_pixel(x, y); (p.r, p.g, p.b, p.a) }; + // the colours are matched to the 16x16 cells (see match_colors), but a picture keeps its + // shapes: what is lighter in the source stays lighter in the atlas + let placed = |hd: &Image, ox: u32, oy: u32, src: &Image, sx: u32, sy: u32| { + let lum = |p: Color| p.r + p.g + p.b; + let pts = [(3, 5), (7, 40), (30, 9), (50, 50), (20, 60), (61, 2)]; + for &(ax, ay) in pts.iter() { + for &(bx, by) in pts.iter() { + let (s1, s2) = (lum(src.get_pixel(sx + ax, sy + ay)), lum(src.get_pixel(sx + bx, sy + by))); + if s1 + 0.15 < s2 { + assert!(lum(hd.get_pixel(ox + ax, oy + ay)) <= lum(hd.get_pixel(ox + bx, oy + by)) + 0.02, "cell at {ox},{oy}"); + } + } + } + }; // a cell without an own texture: the 16x16 one scaled up let (cx, cy) = ((T_PUMPKIN % ATLAS_COLS) * 16, (T_PUMPKIN / ATLAS_COLS) * 16); for (x, y) in [(0, 0), (5, 9), (15, 15)] { @@ -2891,7 +2977,7 @@ mod hd_atlas_tests { let id = base + v; let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile); let (sx, sy) = if n == 4 { ((v % 2) * tile, (v / 2) * tile) } else { (0, 0) }; - assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, sx + 7, sy + 40)); + placed(&hd, ox, oy, &src, sx, sy); } } // the sets: five tiles each in their own cells (inside the atlas) @@ -2902,7 +2988,7 @@ mod hd_atlas_tests { for v in 0..5 { let id = own + v; let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile); - assert_eq!(px(&hd, ox + 9, oy + 30), px(&src, v * tile + 9, 30)); + placed(&hd, ox, oy, &src, v * tile, 0); } } } @@ -2972,6 +3058,24 @@ mod atlas_dump { } out.export_png("/tmp/atlas-cell.png"); } + + // `cargo test --bins dump_atlases -- --ignored`: the 16x16 atlas scaled up and the 64x64 + // high-res atlas side by side in /tmp/atlases.png (to compare the colours of the styles) + #[test] + #[ignore] + fn dump_atlases() { + let small = gen_atlas(); + let hd = gen_hd_atlas(HD_TILE); + let (w, h) = (hd.width as u32, hd.height as u32); + let mut out = Image::gen_image_color((2 * w + 16) as u16, h as u16, BLACK); + for y in 0..h { + for x in 0..w { + out.set_pixel(x, h - 1 - y, small.get_pixel(x / 4, y / 4)); + out.set_pixel(w + 16 + x, h - 1 - y, hd.get_pixel(x, y)); + } + } + out.export_png("/tmp/atlases.png"); + } } #[cfg(test)]