diff --git a/art/generated/textures/PROMPTS.md b/art/generated/textures/PROMPTS.md index f1eb84e..6c6a5cf 100644 --- a/art/generated/textures/PROMPTS.md +++ b/art/generated/textures/PROMPTS.md @@ -85,3 +85,19 @@ Keep this exact seamless tileable earth texture (same colours, same soil, same s Gold has four variants (`--size 128` and `--size 256` like the others). Silver, ruby and diamond have two variants: one seamless tile for both (`--size 64 --colors 32 --band 8` and `--size 128 --colors 48 --band 14`), copied to `assets/textures/.png` and `-128.png`. + +## Five variants per kind (2026-11-07) + +Floor, earth, rock and the ores (gold, silver, ruby, diamond) now have five tiles each that fit +together in any order (`HD_SETS` in `src/gfx.rs`; a tile picks one by its position). No new +pictures: `tools/make_tile.py --variants 5` takes the seamless sheet of a raw picture, makes one +quarter of it seamless on its own as the shared border and fills the middles of the other tiles +from other windows of the sheet: + +```bash +python3 tools/make_tile.py -raw.png --size 256 --colors 48 --band 28 --variants 5 # 128x128 tiles +python3 tools/make_tile.py -raw.png --size 128 --colors 32 --band 14 --variants 5 # 64x64 tiles +``` + +`-v64.png` / `-v128.png` are copied to `assets/textures/`; `-v128-preview.png` +shows a floor laid out at random. Water and lava keep their 2x2 blocks (they flow). diff --git a/art/generated/textures/diamond-v128-preview.png b/art/generated/textures/diamond-v128-preview.png new file mode 100644 index 0000000..e898651 Binary files /dev/null and b/art/generated/textures/diamond-v128-preview.png differ diff --git a/art/generated/textures/diamond-v128.png b/art/generated/textures/diamond-v128.png new file mode 100644 index 0000000..cd7e7c5 Binary files /dev/null and b/art/generated/textures/diamond-v128.png differ diff --git a/art/generated/textures/diamond-v64-preview.png b/art/generated/textures/diamond-v64-preview.png new file mode 100644 index 0000000..e2d2675 Binary files /dev/null and b/art/generated/textures/diamond-v64-preview.png differ diff --git a/art/generated/textures/diamond-v64.png b/art/generated/textures/diamond-v64.png new file mode 100644 index 0000000..6013cfa Binary files /dev/null and b/art/generated/textures/diamond-v64.png differ diff --git a/art/generated/textures/earth-v128-preview.png b/art/generated/textures/earth-v128-preview.png new file mode 100644 index 0000000..5d6ec43 Binary files /dev/null and b/art/generated/textures/earth-v128-preview.png differ diff --git a/art/generated/textures/earth-v128.png b/art/generated/textures/earth-v128.png new file mode 100644 index 0000000..d6ac285 Binary files /dev/null and b/art/generated/textures/earth-v128.png differ diff --git a/art/generated/textures/earth-v64-preview.png b/art/generated/textures/earth-v64-preview.png new file mode 100644 index 0000000..1b6674c Binary files /dev/null and b/art/generated/textures/earth-v64-preview.png differ diff --git a/art/generated/textures/earth-v64.png b/art/generated/textures/earth-v64.png new file mode 100644 index 0000000..548e77e Binary files /dev/null and b/art/generated/textures/earth-v64.png differ diff --git a/art/generated/textures/floor-v128-preview.png b/art/generated/textures/floor-v128-preview.png new file mode 100644 index 0000000..4772bdf Binary files /dev/null and b/art/generated/textures/floor-v128-preview.png differ diff --git a/art/generated/textures/floor-v128.png b/art/generated/textures/floor-v128.png new file mode 100644 index 0000000..ba1f883 Binary files /dev/null and b/art/generated/textures/floor-v128.png differ diff --git a/art/generated/textures/floor-v64-preview.png b/art/generated/textures/floor-v64-preview.png new file mode 100644 index 0000000..1e03b22 Binary files /dev/null and b/art/generated/textures/floor-v64-preview.png differ diff --git a/art/generated/textures/floor-v64.png b/art/generated/textures/floor-v64.png new file mode 100644 index 0000000..711921d Binary files /dev/null and b/art/generated/textures/floor-v64.png differ diff --git a/art/generated/textures/gold-v128-preview.png b/art/generated/textures/gold-v128-preview.png new file mode 100644 index 0000000..523fd8b Binary files /dev/null and b/art/generated/textures/gold-v128-preview.png differ diff --git a/art/generated/textures/gold-v128.png b/art/generated/textures/gold-v128.png new file mode 100644 index 0000000..6b7526a Binary files /dev/null and b/art/generated/textures/gold-v128.png differ diff --git a/art/generated/textures/gold-v64-preview.png b/art/generated/textures/gold-v64-preview.png new file mode 100644 index 0000000..6202a8b Binary files /dev/null and b/art/generated/textures/gold-v64-preview.png differ diff --git a/art/generated/textures/gold-v64.png b/art/generated/textures/gold-v64.png new file mode 100644 index 0000000..3d265ff Binary files /dev/null and b/art/generated/textures/gold-v64.png differ diff --git a/art/generated/textures/rock-v128-preview.png b/art/generated/textures/rock-v128-preview.png new file mode 100644 index 0000000..e195d78 Binary files /dev/null and b/art/generated/textures/rock-v128-preview.png differ diff --git a/art/generated/textures/rock-v128.png b/art/generated/textures/rock-v128.png new file mode 100644 index 0000000..a7ba4a0 Binary files /dev/null and b/art/generated/textures/rock-v128.png differ diff --git a/art/generated/textures/rock-v64-preview.png b/art/generated/textures/rock-v64-preview.png new file mode 100644 index 0000000..e080088 Binary files /dev/null and b/art/generated/textures/rock-v64-preview.png differ diff --git a/art/generated/textures/rock-v64.png b/art/generated/textures/rock-v64.png new file mode 100644 index 0000000..248144f Binary files /dev/null and b/art/generated/textures/rock-v64.png differ diff --git a/art/generated/textures/ruby-v128-preview.png b/art/generated/textures/ruby-v128-preview.png new file mode 100644 index 0000000..2cc81d1 Binary files /dev/null and b/art/generated/textures/ruby-v128-preview.png differ diff --git a/art/generated/textures/ruby-v128.png b/art/generated/textures/ruby-v128.png new file mode 100644 index 0000000..aebf711 Binary files /dev/null and b/art/generated/textures/ruby-v128.png differ diff --git a/art/generated/textures/ruby-v64-preview.png b/art/generated/textures/ruby-v64-preview.png new file mode 100644 index 0000000..f045b89 Binary files /dev/null and b/art/generated/textures/ruby-v64-preview.png differ diff --git a/art/generated/textures/ruby-v64.png b/art/generated/textures/ruby-v64.png new file mode 100644 index 0000000..b364e4c Binary files /dev/null and b/art/generated/textures/ruby-v64.png differ diff --git a/art/generated/textures/silver-v128-preview.png b/art/generated/textures/silver-v128-preview.png new file mode 100644 index 0000000..d9a1e6b Binary files /dev/null and b/art/generated/textures/silver-v128-preview.png differ diff --git a/art/generated/textures/silver-v128.png b/art/generated/textures/silver-v128.png new file mode 100644 index 0000000..a4b47d4 Binary files /dev/null and b/art/generated/textures/silver-v128.png differ diff --git a/art/generated/textures/silver-v64-preview.png b/art/generated/textures/silver-v64-preview.png new file mode 100644 index 0000000..f84843c Binary files /dev/null and b/art/generated/textures/silver-v64-preview.png differ diff --git a/art/generated/textures/silver-v64.png b/art/generated/textures/silver-v64.png new file mode 100644 index 0000000..6808c39 Binary files /dev/null and b/art/generated/textures/silver-v64.png differ diff --git a/assets/textures/diamond-128.png b/assets/textures/diamond-128.png deleted file mode 100644 index 90ed79b..0000000 Binary files a/assets/textures/diamond-128.png and /dev/null differ diff --git a/assets/textures/diamond-v128.png b/assets/textures/diamond-v128.png new file mode 100644 index 0000000..cd7e7c5 Binary files /dev/null and b/assets/textures/diamond-v128.png differ diff --git a/assets/textures/diamond-v64.png b/assets/textures/diamond-v64.png new file mode 100644 index 0000000..6013cfa Binary files /dev/null and b/assets/textures/diamond-v64.png differ diff --git a/assets/textures/diamond.png b/assets/textures/diamond.png deleted file mode 100644 index 442342c..0000000 Binary files a/assets/textures/diamond.png and /dev/null differ diff --git a/assets/textures/earth-256.png b/assets/textures/earth-256.png deleted file mode 100644 index 53c2363..0000000 Binary files a/assets/textures/earth-256.png and /dev/null differ diff --git a/assets/textures/earth-v128.png b/assets/textures/earth-v128.png new file mode 100644 index 0000000..d6ac285 Binary files /dev/null and b/assets/textures/earth-v128.png differ diff --git a/assets/textures/earth-v64.png b/assets/textures/earth-v64.png new file mode 100644 index 0000000..548e77e Binary files /dev/null and b/assets/textures/earth-v64.png differ diff --git a/assets/textures/earth.png b/assets/textures/earth.png deleted file mode 100644 index ce671c2..0000000 Binary files a/assets/textures/earth.png and /dev/null differ diff --git a/assets/textures/floor-256.png b/assets/textures/floor-256.png deleted file mode 100644 index bf2fe93..0000000 Binary files a/assets/textures/floor-256.png and /dev/null differ diff --git a/assets/textures/floor-v128.png b/assets/textures/floor-v128.png new file mode 100644 index 0000000..ba1f883 Binary files /dev/null and b/assets/textures/floor-v128.png differ diff --git a/assets/textures/floor-v64.png b/assets/textures/floor-v64.png new file mode 100644 index 0000000..711921d Binary files /dev/null and b/assets/textures/floor-v64.png differ diff --git a/assets/textures/floor.png b/assets/textures/floor.png deleted file mode 100644 index d8fc61b..0000000 Binary files a/assets/textures/floor.png and /dev/null differ diff --git a/assets/textures/gold-256.png b/assets/textures/gold-256.png deleted file mode 100644 index 17fb91b..0000000 Binary files a/assets/textures/gold-256.png and /dev/null differ diff --git a/assets/textures/gold-v128.png b/assets/textures/gold-v128.png new file mode 100644 index 0000000..6b7526a Binary files /dev/null and b/assets/textures/gold-v128.png differ diff --git a/assets/textures/gold-v64.png b/assets/textures/gold-v64.png new file mode 100644 index 0000000..3d265ff Binary files /dev/null and b/assets/textures/gold-v64.png differ diff --git a/assets/textures/gold.png b/assets/textures/gold.png deleted file mode 100644 index 60883e0..0000000 Binary files a/assets/textures/gold.png and /dev/null differ diff --git a/assets/textures/rock-256.png b/assets/textures/rock-256.png deleted file mode 100644 index 1b1ec82..0000000 Binary files a/assets/textures/rock-256.png and /dev/null differ diff --git a/assets/textures/rock-v128.png b/assets/textures/rock-v128.png new file mode 100644 index 0000000..a7ba4a0 Binary files /dev/null and b/assets/textures/rock-v128.png differ diff --git a/assets/textures/rock-v64.png b/assets/textures/rock-v64.png new file mode 100644 index 0000000..248144f Binary files /dev/null and b/assets/textures/rock-v64.png differ diff --git a/assets/textures/rock.png b/assets/textures/rock.png deleted file mode 100644 index 9356781..0000000 Binary files a/assets/textures/rock.png and /dev/null differ diff --git a/assets/textures/ruby-128.png b/assets/textures/ruby-128.png deleted file mode 100644 index f6d337a..0000000 Binary files a/assets/textures/ruby-128.png and /dev/null differ diff --git a/assets/textures/ruby-v128.png b/assets/textures/ruby-v128.png new file mode 100644 index 0000000..aebf711 Binary files /dev/null and b/assets/textures/ruby-v128.png differ diff --git a/assets/textures/ruby-v64.png b/assets/textures/ruby-v64.png new file mode 100644 index 0000000..b364e4c Binary files /dev/null and b/assets/textures/ruby-v64.png differ diff --git a/assets/textures/ruby.png b/assets/textures/ruby.png deleted file mode 100644 index f8df016..0000000 Binary files a/assets/textures/ruby.png and /dev/null differ diff --git a/assets/textures/silver-128.png b/assets/textures/silver-128.png deleted file mode 100644 index a3b44e8..0000000 Binary files a/assets/textures/silver-128.png and /dev/null differ diff --git a/assets/textures/silver-v128.png b/assets/textures/silver-v128.png new file mode 100644 index 0000000..a4b47d4 Binary files /dev/null and b/assets/textures/silver-v128.png differ diff --git a/assets/textures/silver-v64.png b/assets/textures/silver-v64.png new file mode 100644 index 0000000..6808c39 Binary files /dev/null and b/assets/textures/silver-v64.png differ diff --git a/assets/textures/silver.png b/assets/textures/silver.png deleted file mode 100644 index 9f3df1c..0000000 Binary files a/assets/textures/silver.png and /dev/null differ diff --git a/src/gfx.rs b/src/gfx.rs index c782c2e..2a82c19 100644 --- a/src/gfx.rs +++ b/src/gfx.rs @@ -6,7 +6,7 @@ use macroquad::prelude::*; pub const CELL: f32 = 16.0; pub const ATLAS_COLS: u32 = 16; -pub const ATLAS_ROWS: u32 = 16; // rows 14-15: cells only the high-res atlases fill (see HD_FLOORS) +pub const ATLAS_ROWS: u32 = 18; // rows 14-17: cells only the high-res atlases fill (see HD_SETS) // atlas indices (4 variants each) pub const T_ROCK: u32 = 0; @@ -337,39 +337,42 @@ fn earth_px(x: i32, y: i32, v: u32) -> Color { // tiles and has no seams), for kinds with two variants one seamless tile for both. pub const HD_TILE: u32 = 64; pub const HD_TILE2: u32 = 128; -// (first cell, variants, 64x64 level, 128x128 level) -const HD_TILES: [(u32, u32, &[u8], &[u8]); 9] = [ - (T_FLOOR, 4, include_bytes!("../assets/textures/floor.png"), include_bytes!("../assets/textures/floor-256.png")), - (T_EARTH, 4, include_bytes!("../assets/textures/earth.png"), include_bytes!("../assets/textures/earth-256.png")), - (T_ROCK, 4, include_bytes!("../assets/textures/rock.png"), include_bytes!("../assets/textures/rock-256.png")), - (T_GOLD, 4, include_bytes!("../assets/textures/gold.png"), include_bytes!("../assets/textures/gold-256.png")), +// the liquids: (first cell, variants, 64x64 level, 128x128 level); a seamless 2x2 block of +// tiles each (variant k = quarter k % 2, k / 2), which the shader lays out by position while +// the texture flows +const HD_TILES: [(u32, u32, &[u8], &[u8]); 2] = [ (T_WATER, 4, include_bytes!("../assets/textures/water.png"), include_bytes!("../assets/textures/water-256.png")), (T_LAVA, 4, include_bytes!("../assets/textures/lava.png"), include_bytes!("../assets/textures/lava-256.png")), - (T_SILVER, 2, include_bytes!("../assets/textures/silver.png"), include_bytes!("../assets/textures/silver-128.png")), - (T_RUBY, 2, include_bytes!("../assets/textures/ruby.png"), include_bytes!("../assets/textures/ruby-128.png")), - (T_DIAMOND, 2, include_bytes!("../assets/textures/diamond.png"), include_bytes!("../assets/textures/diamond-128.png")), ]; -// The masters' claimed floors in the high-res style: the same grey, worn-smooth castle -// flagstones for everybody (cut so they fit in any order), every tile with a polished inlay in -// its master's colour and an engraving of their own (the player: a demon skull, Mossgrim: a -// mushroom, Lady Nightwell: a lily under the moon, Saffrax: an alchemical sun). Five tiles each: -// plain, cracked, chipped, broken, scratched. They go into cells of their own (from T_HDFLOOR on, -// five per master); a tile picks one by its position, weighted (plain ones most often). -// (the claimed floor's cells, the first own cell, 64x64 tiles side by side, 128x128 ones, weights) -pub const T_HDFLOOR: u32 = 224; -const HD_FLOORS: [(u32, u32, &[u8], &[u8], [u32; 5]); 4] = [ - (T_CLAIM, T_HDFLOOR, include_bytes!("../assets/textures/floor-player.png"), include_bytes!("../assets/textures/floor-player-128.png"), [4, 2, 2, 1, 3]), - (T_RIVAL1, T_HDFLOOR + 5, include_bytes!("../assets/textures/floor-mossgrim.png"), include_bytes!("../assets/textures/floor-mossgrim-128.png"), [4, 2, 2, 1, 3]), - (T_RIVAL2, T_HDFLOOR + 10, include_bytes!("../assets/textures/floor-nightwell.png"), include_bytes!("../assets/textures/floor-nightwell-128.png"), [4, 2, 2, 1, 3]), - (T_RIVAL3, T_HDFLOOR + 15, include_bytes!("../assets/textures/floor-saffrax.png"), include_bytes!("../assets/textures/floor-saffrax-128.png"), [4, 2, 2, 1, 3]), +// Sets of five tiles that fit together in any order, so a floor or a wall shows no pattern: +// the ground kinds (all five tiles share one seamless border, their middles differ; see +// tools/make_tile.py --variants) and the masters' claimed floors (the same grey, worn-smooth +// castle flagstones for everybody, every tile with a polished inlay in its master's colour and +// an engraving of their own: the player a demon skull, Mossgrim a mushroom, Lady Nightwell a lily +// under the moon, Saffrax an alchemical sun; plain, cracked, chipped, broken, scratched). They go +// into cells of their own from T_HDSET on, five per set; a tile picks one by its position, +// weighted. (the kind's cells: first and count, the first own cell, 64x64 tiles side by side, +// 128x128 ones, weights) +pub const T_HDSET: u32 = 224; +const HD_SETS: [(u32, u32, u32, &[u8], &[u8], [u32; 5]); 11] = [ + (T_CLAIM, 4, T_HDSET, include_bytes!("../assets/textures/floor-player.png"), include_bytes!("../assets/textures/floor-player-128.png"), [4, 2, 2, 1, 3]), + (T_RIVAL1, 4, T_HDSET + 5, include_bytes!("../assets/textures/floor-mossgrim.png"), include_bytes!("../assets/textures/floor-mossgrim-128.png"), [4, 2, 2, 1, 3]), + (T_RIVAL2, 4, T_HDSET + 10, include_bytes!("../assets/textures/floor-nightwell.png"), include_bytes!("../assets/textures/floor-nightwell-128.png"), [4, 2, 2, 1, 3]), + (T_RIVAL3, 4, T_HDSET + 15, include_bytes!("../assets/textures/floor-saffrax.png"), include_bytes!("../assets/textures/floor-saffrax-128.png"), [4, 2, 2, 1, 3]), + (T_FLOOR, 4, T_HDSET + 20, include_bytes!("../assets/textures/floor-v64.png"), include_bytes!("../assets/textures/floor-v128.png"), [1; 5]), + (T_EARTH, 4, T_HDSET + 25, include_bytes!("../assets/textures/earth-v64.png"), include_bytes!("../assets/textures/earth-v128.png"), [1; 5]), + (T_ROCK, 4, T_HDSET + 30, include_bytes!("../assets/textures/rock-v64.png"), include_bytes!("../assets/textures/rock-v128.png"), [1; 5]), + (T_GOLD, 4, T_HDSET + 35, include_bytes!("../assets/textures/gold-v64.png"), include_bytes!("../assets/textures/gold-v128.png"), [1; 5]), + (T_SILVER, 2, T_HDSET + 40, include_bytes!("../assets/textures/silver-v64.png"), include_bytes!("../assets/textures/silver-v128.png"), [1; 5]), + (T_RUBY, 2, T_HDSET + 45, include_bytes!("../assets/textures/ruby-v64.png"), include_bytes!("../assets/textures/ruby-v128.png"), [1; 5]), + (T_DIAMOND, 2, T_HDSET + 50, include_bytes!("../assets/textures/diamond-v64.png"), include_bytes!("../assets/textures/diamond-v128.png"), [1; 5]), ]; -// the cell a tile at (x, z) shows in the high-res style: generated textures with four variants -// are laid out by the position (see HD_TILES), the masters' floors pick one of their five slabs -// (HD_FLOORS), everything else stays +// the cell a tile at (x, z) shows in the high-res style: the kinds with a set pick one of its +// five tiles (HD_SETS), the liquids are laid out by the position (HD_TILES), everything else stays pub fn hd_tile(id: u32, x: i32, z: i32) -> u32 { - if let Some(&(_, own, _, _, w)) = HD_FLOORS.iter().find(|f| (f.0..f.0 + 4).contains(&id)) { + if let Some(&(_, _, own, _, _, w)) = HD_SETS.iter().find(|f| (f.0..f.0 + f.1).contains(&id)) { let h = (x as u32).wrapping_mul(73_856_093) ^ (z as u32).wrapping_mul(19_349_663); let h = (h ^ (h >> 13)).wrapping_mul(0x5bd1_e995); let mut r = (h >> 8) % w.iter().sum::(); @@ -410,8 +413,8 @@ fn gen_hd_atlas(tile: u32) -> Image { } } } - for &(_, own, png, png2, _) in HD_FLOORS.iter() { - let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).expect("hd floor"); + for &(_, _, own, png, png2, _) in HD_SETS.iter() { + let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).expect("hd set"); for v in 0..5 { let id = own + v; let (ox, oy) = ((id % ATLAS_COLS) * tile, (id / ATLAS_COLS) * tile); @@ -2768,8 +2771,8 @@ mod hd_atlas_tests { assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, sx + 7, sy + 40)); } } - // the masters' floors: five slabs each in their own cells (inside the atlas) - for &(_, own, png, png2, _) in HD_FLOORS.iter() { + // the sets: five tiles each in their own cells (inside the atlas) + for &(_, _, own, png, png2, _) in HD_SETS.iter() { let src = Image::from_file_with_format(if tile == HD_TILE2 { png2 } else { png }, Some(ImageFormat::Png)).unwrap(); assert_eq!((src.width as u32, src.height as u32), (5 * tile, tile)); assert!(own + 5 <= ATLAS_COLS * ATLAS_ROWS); @@ -2785,17 +2788,18 @@ mod hd_atlas_tests { for z in 0..60 { for x in 0..60 { let id = hd_tile(T_CLAIM + (x as u32 % 4), x, z); - assert!((T_HDFLOOR..T_HDFLOOR + 5).contains(&id)); - n[(id - T_HDFLOOR) as usize] += 1; + assert!((T_HDSET..T_HDSET + 5).contains(&id)); + n[(id - T_HDSET) as usize] += 1; } } assert!(n.iter().all(|&k| k > 100), "{n:?}"); assert!(n[0] > n[3] * 2, "{n:?}"); - assert!((T_HDFLOOR + 15..T_HDFLOOR + 20).contains(&hd_tile(T_RIVAL3 + 2, 3, 7))); - // four variants by position, two variants keep theirs, others stay - assert_eq!(hd_tile(T_FLOOR + 3, 0, 0), T_FLOOR); - assert_eq!(hd_tile(T_FLOOR, 1, 1), T_FLOOR + 3); - assert_eq!(hd_tile(T_RUBY + 1, 1, 0), T_RUBY + 1); + assert!((T_HDSET + 15..T_HDSET + 20).contains(&hd_tile(T_RIVAL3 + 2, 3, 7))); + assert!((T_HDSET + 50..T_HDSET + 55).contains(&hd_tile(T_DIAMOND + 1, 8, 2))); + assert!((T_HDSET + 25..T_HDSET + 30).contains(&hd_tile(T_EARTH + 3, 5, 5))); + // the liquids: four variants by position; others stay + assert_eq!(hd_tile(T_WATER + 3, 0, 0), T_WATER); + assert_eq!(hd_tile(T_LAVA, 1, 1), T_LAVA + 3); assert_eq!(hd_tile(T_TREASURY + 2, 1, 0), T_TREASURY + 2); } } diff --git a/tools/make_tile.py b/tools/make_tile.py index 3d265cb..625caeb 100644 --- a/tools/make_tile.py +++ b/tools/make_tile.py @@ -2,7 +2,7 @@ """Turn a generated texture into a seamless pixel-art tile sheet for the high-res atlas. Usage: - make_tile.py raw.png out_prefix [--size 128] [--colors 32] [--band 14] + make_tile.py raw.png out_prefix [--size 128] [--colors 32] [--band 14] [--variants N] The picture is scaled down to size x size (default 128: a 2x2 block of 64x64 tiles; the game lays the four quarters out by the tiles' positions, so the floor repeats only every two tiles), @@ -10,9 +10,15 @@ made seamless (near the edges it fades into a copy shifted by half the size, who around without a seam; `--band` pixels wide) and reduced to a palette without dithering. Writes out_prefix-.png and out_prefix--preview.png (3x3 copies scaled up 3x, to -check the seams). Needs Pillow. +check the seams). + +--variants N: instead N single tiles of size/2 that fit together in any order: all share the +same seamless border (a quarter of the sheet made seamless on its own), their middles come from +different windows of the sheet. Writes out_prefix-v.png (the tiles side by side) and +out_prefix-v-preview.png (a floor of them laid out at random). Needs Pillow. """ import argparse +import random from PIL import Image, ImageChops @@ -29,6 +35,38 @@ def seamless(img, band): return Image.composite(img, shifted, mask) +def window(img, x, y, n): + # an n x n window of a seamless picture, wrapping around + return ImageChops.offset(img, -x, -y).crop((0, 0, n, n)) + + +def variants(sheet, a): + t = a.size // 2 + base = seamless(sheet.crop((0, 0, t, t)), a.band) + band = max(4, t * 18 // 100) + mask = Image.new("L", (t, t)) + for y in range(t): + for x in range(t): + d = min(x, t - 1 - x, y, t - 1 - y) + mask.putpixel((x, y), int(255 * max(0.0, min(1.0, (d - band * 0.4) / (band * 0.6))))) + tiles = [base] + for k in range(1, a.variants): + w = window(sheet, (k * t * 37 // 100 + t // 2 * (k % 2)) % a.size, (k * t * 61 // 100) % a.size, t) + tiles.append(Image.composite(w, base, mask)) + strip = Image.new("RGB", (t * len(tiles), t)) + for k, tile in enumerate(tiles): + strip.paste(tile, (k * t, 0)) + strip = strip.quantize(colors=a.colors, method=Image.Quantize.MEDIANCUT, dither=Image.Dither.NONE).convert("RGB") + strip.save(f"{a.out}-v{t}.png") + rnd = random.Random(3) + prev = Image.new("RGB", (t * 6, t * 4)) + for k in range(24): + v = rnd.randrange(len(tiles)) + prev.paste(strip.crop((v * t, 0, (v + 1) * t, t)), ((k % 6) * t, (k // 6) * t)) + prev.resize((prev.width * 2, prev.height * 2), Image.NEAREST).save(f"{a.out}-v{t}-preview.png") + print(f"{a.out}-v{t}.png: {len(tiles)} tiles") + + def main(): ap = argparse.ArgumentParser() ap.add_argument("raw") @@ -36,6 +74,7 @@ def main(): ap.add_argument("--size", type=int, default=128) ap.add_argument("--colors", type=int, default=32) ap.add_argument("--band", type=int, default=14) + ap.add_argument("--variants", type=int, default=0) a = ap.parse_args() img = Image.open(a.raw).convert("RGB") @@ -43,6 +82,8 @@ def main(): img = img.crop(((img.width - s) // 2, (img.height - s) // 2, (img.width + s) // 2, (img.height + s) // 2)) img = img.resize((a.size, a.size), Image.LANCZOS) img = seamless(img, a.band) + if a.variants: + return variants(img, a) img = img.quantize(colors=a.colors, method=Image.Quantize.MEDIANCUT, dither=Image.Dither.NONE).convert("RGB") img.save(f"{a.out}-{a.size}.png") prev = Image.new("RGB", (a.size * 3, a.size * 3))