High-res textures take on the pixel-art colours; treasury and mushroom garden closer to the pixel art
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2e905fe374
commit
9cba545bc7
19 files changed
+119
-6
No files matched your search
+106
-2
@@ -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::<Vec<u32>>();
|
||||
let mut groups: Vec<(Vec<u32>, Vec<u32>)> = 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)]
|
||||
|
||||
Reference in new issue
Block a user