High-res tile textures: floor, claimed floor, earth, rock, gold, churning water and lava

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-07 21:26:22 +01:00
1 parent 69a6b13006
commit 8197b07ac5
33 files changed
+256 -7

No files matched your search

+1
View File
@@ -5,3 +5,4 @@ www/dungeoncraft.wasm
www/mq_js_bundle.js www/mq_js_bundle.js
play/ play/
dist/ dist/
art/generated/creature-previews/
+65
View File
@@ -0,0 +1,65 @@
# High-res tile textures (test)
Generated on 2026-11-07 with fal.ai for the high-res graphics style (`HD_TILES` in `src/gfx.rs`).
- Model: `fal-ai/flux-2-pro`, `image_size: square_hd` (1024x1024), `output_format: png`,
`seed: 4242` (floor and gold: a second try with `seed: 4243` and the prompts below; the first
floor had a frame and a centre motif, the first gold too few veins)
- Prompt: the kind's sentence followed by the shared style text.
- `tools/make_tile.py <name>-raw.png <name> --size 128 --colors 32` scales it to a 2x2 block of
64x64 tiles, makes it seamless and reduces the colours; `<name>-128.png` is copied to
`assets/textures/<name>.png`. `<name>-128-preview.png`: 3x3 copies to check the seams.
Shared style text:
```text
Seamless tileable square texture, viewed straight from above (orthographic top-down, no perspective), flat even lighting, no shadows, no vignette, no border, no text. Hand-painted pixel art style for a dark-fantasy dungeon strategy game: chunky clear shapes, crisp edges, limited muted palette, readable at small size. The pattern continues seamlessly across all four edges.
```
## floor: T_FLOOR (dug-out floor)
```text
A uniform all-over pattern of rough grey-brown cobblestones of mixed sizes set in packed dirt, evenly spread from edge to edge with no frame, no border and no centre motif, like a cropped piece of a large dungeon floor.
```
## claimed: T_CLAIM (claimed floor)
```text
The floor of an evil dungeon master's lair: fitted dark crimson-purple flagstones with thin black grout lines, some stones carry a small faint carved demonic rune, worn and polished.
```
## earth: T_EARTH (earth blocks)
```text
Solid packed brown earth seen from above, soil with small stones, roots and darker clumps, slightly lumpy.
```
## rock: T_ROCK (bedrock)
```text
Hard dark blue-grey bedrock seen from above, angular rock facets and deep cracks, very solid and impenetrable.
```
## gold: T_GOLD (gold seam)
```text
A uniform all-over pattern of brown earth crossed by many thick glittering veins of bright yellow gold ore with large shining gold nuggets, the gold covering about a third of the surface, evenly spread from edge to edge.
```
## water: T_WATER
```text
Deep dark blue underground river water seen from above, gentle wave crests and ripples in lighter blue-cyan, slight foam streaks.
```
## lava: T_LAVA
```text
Molten lava seen from above, glowing orange and yellow molten rock flowing in swirls, darker red cooling streaks, very hot and bright, no black crust.
```
## In the game
Water and lava flow as before (the shader moves the texture along the river and lets the surface
swell); in the high-res style a second, larger layer of the same texture drifts slower and
sideways over it, so the surface churns.
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+86 -1
View File
@@ -99,6 +99,7 @@ pub struct Gfx {
pub atlas: Texture2D, pub atlas: Texture2D,
pub sprites: Texture2D, pub sprites: Texture2D,
pub hd_sprites: Texture2D, // detailed sprites (see HD_SPRITES, hd_cell_view) pub hd_sprites: Texture2D, // detailed sprites (see HD_SPRITES, hd_cell_view)
pub atlas_hd: Texture2D, // the tile atlas of the high-res style (see HD_TILES)
pub cursors: Texture2D, // mouse pointer frames, 16x16 each (see CURSOR_*) pub cursors: Texture2D, // mouse pointer frames, 16x16 each (see CURSOR_*)
pub badges: Texture2D, // achievement badges, 32x32 each (see badges.rs) pub badges: Texture2D, // achievement badges, 32x32 each (see badges.rs)
pub font: Texture2D, // Dinowyde (glyphs of 6x8 px ordered by Unicode, see font_slot) and DINOBYTE pub font: Texture2D, // Dinowyde (glyphs of 6x8 px ordered by Unicode, see font_slot) and DINOBYTE
@@ -325,6 +326,55 @@ fn earth_px(x: i32, y: i32, v: u32) -> Color {
shade(base, (big + fine) as i32) shade(base, (big + fine) as i32)
} }
// ---- high-res tiles
// The high-res style's atlas: the same cells as the 16x16 atlas, HD_TILE x HD_TILE texels each.
// Cells without an own texture are the 16x16 ones scaled up. HD_TILES: generated textures (see
// art/generated/textures/), each a seamless 2x2 block of tiles for the four variants of a kind
// (variant k = quarter k % 2, k / 2); the world lays the quarters out by the tiles' positions
// (see hd_tile), so a floor repeats only every two tiles and has no seams.
pub const HD_TILE: u32 = 64;
pub const ATLAS_HD_W: f32 = (ATLAS_COLS * HD_TILE) as f32;
pub const ATLAS_HD_H: f32 = (ATLAS_ROWS * HD_TILE) as f32;
const HD_TILES: [(u32, &[u8]); 7] = [
(T_FLOOR, include_bytes!("../assets/textures/floor.png")),
(T_CLAIM, include_bytes!("../assets/textures/claimed.png")),
(T_EARTH, include_bytes!("../assets/textures/earth.png")),
(T_ROCK, include_bytes!("../assets/textures/rock.png")),
(T_GOLD, include_bytes!("../assets/textures/gold.png")),
(T_WATER, include_bytes!("../assets/textures/water.png")),
(T_LAVA, include_bytes!("../assets/textures/lava.png")),
];
// whether a cell has a generated high-res texture (its variants are laid out by position)
pub fn hd_tile(id: u32) -> bool {
HD_TILES.iter().any(|&(base, _)| id / 4 * 4 == base)
}
fn gen_hd_atlas() -> Image {
let small = gen_atlas();
let k = HD_TILE / 16;
let mut img = Image::gen_image_color(ATLAS_HD_W as u16, ATLAS_HD_H as u16, Color::new(0.0, 0.0, 0.0, 0.0));
for y in 0..ATLAS_HD_H as u32 {
for x in 0..ATLAS_HD_W as u32 {
img.set_pixel(x, y, small.get_pixel(x / k, y / k));
}
}
for &(base, png) in HD_TILES.iter() {
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).expect("hd tile");
for v in 0..4 {
let (sx, sy) = ((v % 2) * HD_TILE, (v / 2) * HD_TILE);
let id = base + v;
let (ox, oy) = ((id % ATLAS_COLS) * HD_TILE, (id / ATLAS_COLS) * HD_TILE);
for y in 0..HD_TILE {
for x in 0..HD_TILE {
img.set_pixel(ox + x, oy + y, src.get_pixel(sx + x, sy + y));
}
}
}
}
img
}
fn gen_atlas() -> Image { fn gen_atlas() -> Image {
let mut cv = Canvas { img: Image::gen_image_color((ATLAS_COLS * 16) as u16, (ATLAS_ROWS * 16) as u16, Color::new(0.0, 0.0, 0.0, 0.0)) }; let mut cv = Canvas { img: Image::gen_image_color((ATLAS_COLS * 16) as u16, (ATLAS_ROWS * 16) as u16, Color::new(0.0, 0.0, 0.0, 0.0)) };
for v in 0..4u32 { for v in 0..4u32 {
@@ -2360,9 +2410,11 @@ impl Gfx {
cursors.set_filter(FilterMode::Nearest); cursors.set_filter(FilterMode::Nearest);
let badges = Texture2D::from_image(&crate::badges::gen_badges()); let badges = Texture2D::from_image(&crate::badges::gen_badges());
badges.set_filter(FilterMode::Nearest); badges.set_filter(FilterMode::Nearest);
let atlas_hd = Texture2D::from_image(&gen_hd_atlas());
atlas_hd.set_filter(FilterMode::Nearest);
let hd_sprites = Texture2D::from_image(&gen_hd_sprites()); let hd_sprites = Texture2D::from_image(&gen_hd_sprites());
hd_sprites.set_filter(FilterMode::Nearest); hd_sprites.set_filter(FilterMode::Nearest);
Gfx { atlas, sprites, hd_sprites, cursors, badges, font, font_map } Gfx { atlas, sprites, hd_sprites, atlas_hd, cursors, badges, font, font_map }
} }
pub fn cell(id: u32) -> Rect { pub fn cell(id: u32) -> Rect {
@@ -2632,6 +2684,39 @@ impl Gfx {
} }
} }
#[cfg(test)]
mod hd_atlas_tests {
use super::*;
#[test]
fn high_res_atlas() {
let small = gen_atlas();
let hd = gen_hd_atlas();
assert_eq!((hd.width as f32, hd.height as f32), (ATLAS_HD_W, ATLAS_HD_H));
let px = |img: &Image, x: u32, y: u32| {
let p = img.get_pixel(x, y);
(p.r, p.g, p.b, p.a)
};
// a cell without an own texture: the 16x16 one scaled up
let (cx, cy) = ((T_TREASURY % ATLAS_COLS) * 16, (T_TREASURY / ATLAS_COLS) * 16);
for (x, y) in [(0, 0), (5, 9), (15, 15)] {
assert_eq!(px(&hd, (cx + x) * 4 + 2, (cy + y) * 4 + 1), px(&small, cx + x, cy + y));
}
// the generated ones: the quarters of their 2x2 sheet
for &(base, png) in HD_TILES.iter() {
let src = Image::from_file_with_format(png, Some(ImageFormat::Png)).unwrap();
assert_eq!((src.width, src.height), (2 * HD_TILE as u16, 2 * HD_TILE as u16));
for v in 0..4 {
let id = base + v;
assert!(hd_tile(id));
let (ox, oy) = ((id % ATLAS_COLS) * HD_TILE, (id / ATLAS_COLS) * HD_TILE);
assert_eq!(px(&hd, ox + 7, oy + 40), px(&src, (v % 2) * HD_TILE + 7, (v / 2) * HD_TILE + 40));
}
}
assert!(!hd_tile(T_TREASURY));
}
}
#[cfg(test)] #[cfg(test)]
mod sprite_tests { mod sprite_tests {
use super::*; use super::*;
+48 -6
View File
@@ -259,6 +259,19 @@ uniform vec4 LightCol[24];
uniform float LightCount; uniform float LightCount;
uniform highp float Time; // same precision as in the vertex shader (required for linking) uniform highp float Time; // same precision as in the vertex shader (required for linking)
uniform vec2 AtlasSize; uniform vec2 AtlasSize;
uniform float CellSize; // texels per atlas cell (16; high-res 64)
uniform float HdLiquid; // 1: high-res liquids (2x2 blocks of tiles, a second flowing layer)
// a flowing liquid's texture at world position p inside the kind's cells (uv names one of them);
// high-res: the four variant cells form a 2x2 block that repeats every two tiles
vec2 liquid_uv(vec2 p) {
vec2 cell = floor(uv * AtlasSize / CellSize);
if (HdLiquid > 0.5) {
vec2 q = mod(floor(p), 2.0);
cell.x = floor(cell.x / 4.0) * 4.0 + q.x + 2.0 * q.y;
}
return (cell + fract(p)) * CellSize / AtlasSize;
}
void main() { void main() {
vec2 tuv = uv; vec2 tuv = uv;
@@ -276,12 +289,22 @@ void main() {
float ph2 = wpos.z * 1.9 - wpos.x * 0.5 - Time * ts * 1.3; float ph2 = wpos.z * 1.9 - wpos.x * 0.5 - Time * ts * 1.3;
vec2 slope = vec2(cos(ph1) * 1.3 * 0.6 - cos(ph2) * 0.5 * 0.4, cos(ph1) * 0.4 * 0.6 + cos(ph2) * 1.9 * 0.4); vec2 slope = vec2(cos(ph1) * 1.3 * 0.6 - cos(ph2) * 0.5 * 0.4, cos(ph1) * 0.4 * 0.6 + cos(ph2) * 1.9 * 0.4);
vec2 p = wpos.xz - dir * speed * Time + slope * (lava ? 0.05 : 0.07); vec2 p = wpos.xz - dir * speed * Time + slope * (lava ? 0.05 : 0.07);
vec2 cell = floor(uv * AtlasSize / 16.0) * 16.0; tuv = liquid_uv(p);
tuv = (cell + fract(p) * 16.0) / AtlasSize;
float ripple = sin(wpos.x * 5.0 + wpos.z * 3.0 + Time * ts * 1.4) * sin(wpos.z * 4.0 - wpos.x * 2.0 + Time * ts); float ripple = sin(wpos.x * 5.0 + wpos.z * 3.0 + Time * ts * 1.4) * sin(wpos.z * 4.0 - wpos.x * 2.0 + Time * ts);
wave = dot(slope, vec2(0.45, 0.35)) * 0.6 + ripple * 0.4; wave = dot(slope, vec2(0.45, 0.35)) * 0.6 + ripple * 0.4;
} }
vec4 tex = texture2D(Texture, tuv); vec4 tex = texture2D(Texture, tuv);
if (liquid && HdLiquid > 0.5) {
// a second, larger layer drifting slower and sideways: the surface churns instead of
// only sliding along
bool lava = color.a < 0.5;
vec2 dir = wnorm.xz / max(length(wnorm.xz), 0.001);
vec2 side = vec2(-dir.y, dir.x);
float speed = lava ? 0.07 : 0.15;
vec2 p2 = wpos.xz * 0.6 + vec2(0.37, 0.71) - dir * speed * Time + side * sin(Time * (lava ? 0.21 : 0.33)) * 0.3;
vec4 tex2 = texture2D(Texture, liquid_uv(p2));
tex = vec4(mix(tex.rgb, tex2.rgb, 0.42), 1.0);
}
if (tex.a < 0.5) discard; if (tex.a < 0.5) discard;
// see-through units (invisibility): every other pixel left out // see-through units (invisibility): every other pixel left out
if (color.a < 0.37 && mod(floor(gl_FragCoord.x) + floor(gl_FragCoord.y), 2.0) < 1.0) discard; if (color.a < 0.37 && mod(floor(gl_FragCoord.x) + floor(gl_FragCoord.y), 2.0) < 1.0) discard;
@@ -319,6 +342,8 @@ fn lit_material() -> Material {
UniformDesc::new("LightCount", UniformType::Float1), UniformDesc::new("LightCount", UniformType::Float1),
UniformDesc::new("Time", UniformType::Float1), UniformDesc::new("Time", UniformType::Float1),
UniformDesc::new("AtlasSize", UniformType::Float2), UniformDesc::new("AtlasSize", UniformType::Float2),
UniformDesc::new("CellSize", UniformType::Float1),
UniformDesc::new("HdLiquid", UniformType::Float1),
], ],
textures: vec![], textures: vec![],
}, },
@@ -766,7 +791,8 @@ fn block_shell(mk: &mut MB, map: &Map, x: i32, z: i32, n: usize, fill: [u8; 4],
} }
} }
fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall_sub: usize) -> [Option<Mesh>; 4] { // `hd`: the high-res atlas; its generated textures are laid out by the tiles' positions
fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall_sub: usize, hd: bool) -> [Option<Mesh>; 4] {
let mut mb = MB::new(); let mut mb = MB::new();
let ov = MB::new(); // textured overlays (currently unused) let ov = MB::new(); // textured overlays (currently unused)
let mut ln = MB::new(); let mut ln = MB::new();
@@ -784,6 +810,9 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
if t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0 { if t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0 {
tex = crate::gfx::T_MOSS + (t.var as u32 & 3); tex = crate::gfx::T_MOSS + (t.var as u32 & 3);
} }
if hd && crate::gfx::hd_tile(tex) {
tex = tex / 4 * 4 + (x & 1) as u32 + (z & 1) as u32 * 2;
}
let (ua, ub) = cell_uv(tex); let (ua, ub) = cell_uv(tex);
let (xf, zf) = (x as f32, z as f32); let (xf, zf) = (x as f32, z as f32);
if !map.shown(idx(x, z)) { if !map.shown(idx(x, z)) {
@@ -1202,6 +1231,7 @@ pub struct R3d {
pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off) pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off)
target: Option<(RenderTarget, u32, u32)>, // low resolution render target target: Option<(RenderTarget, u32, u32)>, // low resolution render target
lava: Vec<f32>, // lava light per tile (see lava_field) lava: Vec<f32>, // lava light per tile (see lava_field)
chunks_hd: bool, // the chunks were built with the high-res atlas
} }
// The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a // The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a
@@ -1252,7 +1282,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
impl R3d { impl R3d {
pub fn new() -> R3d { pub fn new() -> R3d {
R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: crate::settings::PIXEL_ART_ROWS as u32, target: None, lava: Vec::new() } R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: crate::settings::PIXEL_ART_ROWS as u32, target: None, lava: Vec::new(), chunks_hd: false }
} }
// Changing the wall detail rebuilds all chunks // Changing the wall detail rebuilds all chunks
@@ -1292,6 +1322,14 @@ impl R3d {
ch.sig = 0; ch.sig = 0;
} }
} }
// the graphics style changed: the chunks are built again with the other atlas
let hd = self.pixel_rows >= crate::settings::HIGH_RES_ROWS as u32;
if hd != self.chunks_hd {
self.chunks_hd = hd;
for ch in self.chunks.iter_mut() {
ch.sig = 0;
}
}
if view_side() != side { if view_side() != side {
// another master looks: the marks change colour // another master looks: the marks change colour
VIEW_SIDE.with(|v| v.set(side)); VIEW_SIDE.with(|v| v.set(side));
@@ -1316,7 +1354,8 @@ impl R3d {
lava_fresh = true; lava_fresh = true;
} }
let ch = &mut self.chunks[(cz * self.ncx + cx) as usize]; let ch = &mut self.chunks[(cz * self.ncx + cx) as usize];
let [m, o, l, k] = build_chunk(&g.map, a, &self.lava, cx, cz, &gfx.atlas, self.wall_sub); let atlas = if self.chunks_hd { &gfx.atlas_hd } else { &gfx.atlas };
let [m, o, l, k] = build_chunk(&g.map, a, &self.lava, cx, cz, atlas, self.wall_sub, self.chunks_hd);
ch.mesh = m; ch.mesh = m;
ch.overlay = o; ch.overlay = o;
ch.lines = l; ch.lines = l;
@@ -1548,7 +1587,10 @@ impl R3d {
self.lit.set_uniform("LightCount", count); self.lit.set_uniform("LightCount", count);
// wrapped so the shader keeps its precision (the flow speeds are whole tiles per period) // wrapped so the shader keeps its precision (the flow speeds are whole tiles per period)
self.lit.set_uniform("Time", (time % 1000.0) as f32); self.lit.set_uniform("Time", (time % 1000.0) as f32);
self.lit.set_uniform("AtlasSize", vec2(ATLAS_W, ATLAS_H)); let hd_tiles = self.chunks_hd;
self.lit.set_uniform("AtlasSize", if hd_tiles { vec2(crate::gfx::ATLAS_HD_W, crate::gfx::ATLAS_HD_H) } else { vec2(ATLAS_W, ATLAS_H) });
self.lit.set_uniform("CellSize", if hd_tiles { crate::gfx::HD_TILE as f32 } else { 16.0 });
self.lit.set_uniform("HdLiquid", if hd_tiles { 1.0f32 } else { 0.0 });
gl_use_material(&self.lit); gl_use_material(&self.lit);
for cz in 0..self.ncz { for cz in 0..self.ncz {
for cx in 0..self.ncx { for cx in 0..self.ncx {
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""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]
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),
made seamless (near the edges it fades into a copy shifted by half the size, whose edges wrap
around without a seam; `--band` pixels wide) and reduced to a palette without dithering.
Writes out_prefix-<size>.png and out_prefix-<size>-preview.png (3x3 copies scaled up 3x, to
check the seams). Needs Pillow.
"""
import argparse
from PIL import Image, ImageChops
def seamless(img, band):
n = img.width
shifted = ImageChops.offset(img, n // 2, n // 2)
mask = Image.new("L", (n, n))
for y in range(n):
for x in range(n):
# 0 at the edges (the shifted copy, continuous across the wrap), 255 inside
d = min(x, n - 1 - x, y, n - 1 - y)
mask.putpixel((x, y), int(255 * min(1.0, d / band)))
return Image.composite(img, shifted, mask)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("raw")
ap.add_argument("out")
ap.add_argument("--size", type=int, default=128)
ap.add_argument("--colors", type=int, default=32)
ap.add_argument("--band", type=int, default=14)
a = ap.parse_args()
img = Image.open(a.raw).convert("RGB")
s = min(img.size)
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)
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))
for k in range(9):
prev.paste(img, ((k % 3) * a.size, (k // 3) * a.size))
prev.resize((prev.width * 2, prev.height * 2), Image.NEAREST).save(f"{a.out}-{a.size}-preview.png")
print(f"{a.out}-{a.size}.png")
if __name__ == "__main__":
main()