#!/usr/bin/env python3 """Deterministic pixel-art atlases for LivingVillage.Desktop. Generates three RGBA PNGs into src/LivingVillage.Desktop/Assets: jiangnan-world.png 672x32 (21 ground/prop tiles, 32px each) jiangnan-characters.png 1280x48 (5 variants x 4 directions x 2 frames, 32x48 cells) jiangnan-interior.png 392x96 (variable-size bottom-aligned interior cells) Cell geometry is mirrored in VillageArt.fs; keep both in sync. """ import struct import zlib from pathlib import Path ASSET_DIR = Path(__file__).resolve().parents[1] / "src" / "LivingVillage.Desktop" / "Assets" OUTLINE = (23, 31, 34, 255) SKIN = (232, 180, 139, 255) HAIR = (42, 36, 37, 255) SHOE = (40, 30, 28, 255) EYE = (43, 32, 30, 255) class Canvas: def __init__(self, width, height): self.width = width self.height = height self.pixels = [[(0, 0, 0, 0)] * width for _ in range(height)] def set(self, x, y, color): if 0 <= x < self.width and 0 <= y < self.height: self.pixels[y][x] = color def get(self, x, y): return self.pixels[y][x] def rect(self, x, y, w, h, color): for yy in range(y, y + h): for xx in range(x, x + w): self.set(xx, yy, color) def outline(self, x0, y0, w, h, color=OUTLINE): opaque = { (x, y) for y in range(y0, y0 + h) for x in range(x0, x0 + w) if self.get(x, y)[3] == 255 } for (x, y) in opaque: for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): nx, ny = x + dx, y + dy if x0 <= nx < x0 + w and y0 <= ny < y0 + h and self.get(nx, ny)[3] == 0: self.set(nx, ny, color) def write_png(path, canvas): raw = bytearray() for y in range(canvas.height): raw.append(0) for x in range(canvas.width): raw.extend(canvas.pixels[y][x]) def chunk(tag, payload): return ( struct.pack(">I", len(payload)) + tag + payload + struct.pack(">I", zlib.crc32(tag + payload) & 0xFFFFFFFF) ) header = struct.pack(">IIBBBBB", canvas.width, canvas.height, 8, 6, 0, 0, 0) data = ( b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", header) + chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b"") ) path.write_bytes(data) def verify_png(path, expected_w, expected_h): data = path.read_bytes() assert data[:8] == b"\x89PNG\r\n\x1a\n", path width, height = struct.unpack(">II", data[16:24]) assert (width, height) == (expected_w, expected_h), (path, width, height) idat = b"" offset = 8 while offset < len(data): (length,) = struct.unpack(">I", data[offset : offset + 4]) tag = data[offset + 4 : offset + 8] if tag == b"IDAT": idat += data[offset + 8 : offset + 8 + length] offset += 12 + length raw = zlib.decompress(idat) stride = expected_w * 4 + 1 assert len(raw) == stride * expected_h, (path, len(raw)) print(f"ok {path.name} {width}x{height} bytes={len(data)}") def build_world_atlas(): c = Canvas(672, 32) tile = 32 def slot(index): return index * tile grass = (66, 112, 70, 255) grass_light = (92, 139, 82, 255) grass_dark = (54, 92, 60, 255) water = (54, 116, 145, 255) water_light = (105, 174, 184, 255) water_deep = (44, 93, 124, 255) path = (170, 153, 123, 255) path_light = (201, 183, 145, 255) path_grout = (140, 124, 96, 255) roof = (45, 49, 57, 255) roof_light = (76, 80, 85, 255) roof_dark = (30, 33, 38, 255) wall = (225, 216, 195, 255) wall_light = (238, 232, 214, 255) wall_shadow = (166, 147, 119, 255) wall_seam = (196, 180, 152, 255) wood = (119, 76, 47, 255) wood_light = (164, 111, 61, 255) wood_dark = (83, 52, 39, 255) bamboo = (51, 101, 61, 255) bamboo_light = (107, 153, 81, 255) bamboo_node = (38, 76, 46, 255) soil = (122, 78, 49, 255) soil_light = (146, 96, 60, 255) sprout = (86, 139, 68, 255) sprout_light = (107, 171, 85, 255) x0 = slot(0) c.rect(x0, 0, 32, 32, grass) for y in range(32): for x in range(32): if (x * 13 + y * 7) % 29 == 0: c.set(x0 + x, y, grass_light) elif (x * 7 + y * 11) % 43 == 0: c.set(x0 + x, y, grass_dark) for bx, by in ((5, 7), (13, 19), (22, 11), (28, 25), (9, 27), (18, 3)): c.set(x0 + bx, by, grass_light) c.set(x0 + bx, by + 1, grass_light) x0 = slot(1) c.rect(x0, 0, 32, 32, water) c.rect(x0 + 8, 2, 2, 26, water_deep) c.rect(x0 + 23, 2, 2, 26, water_deep) for y in (5, 16, 27): for x in range(3, 29): if (x + y) % 5 != 0: c.set(x0 + x, y, water_light) for sx, sy in ((12, 9), (26, 20), (6, 22)): c.set(x0 + sx, sy, (214, 231, 238, 255)) x0 = slot(2) c.rect(x0, 0, 32, 32, path) c.rect(x0, 0, 32, 1, path_light) c.rect(x0, 31, 32, 1, path_light) for y in (10, 21): c.rect(x0, y, 32, 1, path_grout) for x in (11, 22): c.rect(x0 + x, 0, 1, 10, path_grout) for x in (5, 17, 28): c.rect(x0 + x, 11, 1, 10, path_grout) for x in (12, 24): c.rect(x0 + x, 22, 1, 10, path_grout) for y in range(1, 31): for x in range(1, 31): if (x * 5 + y * 3) % 23 == 0 and c.get(x0 + x, y) == path: c.set(x0 + x, y, path_light) x0 = slot(3) c.rect(x0, 0, 32, 32, roof) for band in range(4): y = band * 8 c.rect(x0, y, 32, 1, roof_light) for x in range((band % 2) * 4, 32, 8): c.rect(x0 + x, y + 1, 1, 7, roof_dark) c.rect(x0, 31, 32, 1, roof_dark) x0 = slot(4) c.rect(x0, 0, 32, 32, wall) c.rect(x0, 0, 32, 1, wall_light) c.rect(x0 + 15, 0, 2, 32, wall_seam) c.rect(x0, 9, 32, 1, wall_seam) c.rect(x0, 18, 32, 1, wall_seam) c.rect(x0, 27, 32, 5, wall_shadow) c.rect(x0, 31, 32, 1, (120, 102, 80, 255)) x0 = slot(5) c.rect(x0, 0, 32, 32, wood) for y in (0, 8, 16, 24): c.rect(x0, y, 32, 1, wood_light) for y in (7, 15, 23, 31): c.rect(x0, y, 32, 1, wood_dark) for y in (4, 12, 20, 28): c.set(x0 + 4, y, (61, 45, 49, 255)) c.set(x0 + 27, y, (61, 45, 49, 255)) x0 = slot(6) for stem in (5, 15, 25): c.rect(x0 + stem, 2, 3, 30, bamboo) c.set(x0 + stem, 2, bamboo_light) c.set(x0 + stem, 3, bamboo_light) c.set(x0 + stem, 4, bamboo_light) for y in (8, 16, 24): c.rect(x0 + stem, y, 3, 1, bamboo_node) for i, ly in enumerate((6, 14, 22)): if i % 2 == 0: c.rect(x0 + stem - 4, ly, 5, 2, bamboo_light) c.rect(x0 + stem + 3, ly + 3, 4, 2, bamboo) else: c.rect(x0 + stem + 3, ly, 5, 2, bamboo_light) c.rect(x0 + stem - 4, ly + 3, 4, 2, bamboo) c.outline(x0, 0, 32, 32) x0 = slot(7) for row_y in (5, 14, 23): c.rect(x0 + 2, row_y, 28, 4, soil) c.rect(x0 + 2, row_y, 28, 1, soil_light) for sx in (6, 15, 24): c.rect(x0 + sx, row_y - 3, 2, 3, sprout) c.set(x0 + sx, row_y - 3, sprout_light) c.set(x0 + sx + 1, row_y - 3, sprout_light) c.outline(x0, 0, 32, 32) x0 = slot(8) ridge_bar = (38, 42, 50, 255) for y in range(14, 28): c.rect(x0 + 3, y, 26, 1, ridge_bar) c.rect(x0 + 3, 13, 26, 1, roof_light) c.rect(x0 + 3, 27, 26, 1, roof_dark) for end_x, end_w in ((0, 6), (26, 6)): for step in range(5): c.rect(x0 + end_x + (step if end_x == 0 else 0), 14 - step * 2, end_w, 2, ridge_bar) c.rect(x0 + end_x, 6 if end_x == 0 else 8, end_w, 1, roof_light) c.rect(x0 + 2, 27, 28, 1, roof_dark) for x in range(7, 26, 6): c.rect(x0 + x, 14, 1, 13, roof_dark) x0 = slot(9) c.rect(x0, 0, 32, 14, roof) for x in range(0, 32, 8): c.rect(x0 + x, 0, 1, 14, roof_dark) c.rect(x0, 3, 32, 1, roof_light) c.rect(x0, 14, 32, 2, roof_dark) c.rect(x0, 16, 32, 2, roof_light) c.rect(x0, 18, 32, 3, (23, 31, 34, 70)) for tip_x in (2, 29): c.rect(x0 + tip_x, 12, 1, 4, roof_light) x0 = slot(10) c.rect(x0, 0, 32, 32, wall) c.rect(x0, 0, 32, 3, wood) c.rect(x0, 0, 32, 1, wood_light) c.rect(x0, 3, 32, 1, wood_dark) for post_x in (0, 29): c.rect(x0 + post_x, 0, 3, 32, wood) c.rect(x0 + post_x, 0, 1, 32, wood_light) c.rect(x0 + post_x + 2, 0, 1, 32, wood_dark) c.rect(x0 + 3, 9, 26, 1, wall_seam) c.rect(x0 + 3, 19, 26, 1, wall_seam) c.rect(x0 + 3, 28, 26, 4, (128, 128, 126, 255)) c.rect(x0 + 3, 28, 26, 1, (150, 150, 146, 255)) x0 = slot(11) c.rect(x0, 0, 32, 32, wall) c.rect(x0, 0, 32, 3, wood) c.rect(x0, 0, 32, 1, wood_light) c.rect(x0, 3, 32, 1, wood_dark) c.rect(x0 + 7, 8, 18, 16, (94, 66, 40, 255)) c.rect(x0 + 9, 10, 14, 12, (61, 45, 49, 255)) for lx in (12, 16, 20): c.rect(x0 + lx, 10, 1, 12, (196, 180, 152, 255)) for ly in (14, 18): c.rect(x0 + 9, ly, 14, 1, (196, 180, 152, 255)) c.rect(x0 + 7, 8, 18, 1, (164, 111, 61, 255)) c.rect(x0 + 3, 28, 26, 4, (128, 128, 126, 255)) c.rect(x0 + 3, 28, 26, 1, (150, 150, 146, 255)) x0 = slot(12) c.rect(x0, 0, 32, 32, wall) c.rect(x0, 0, 32, 3, wood) c.rect(x0, 0, 32, 1, wood_light) c.rect(x0, 3, 32, 1, wood_dark) c.rect(x0 + 6, 4, 20, 28, (94, 66, 40, 255)) c.rect(x0 + 8, 6, 16, 26, wood) c.rect(x0 + 8, 6, 16, 1, wood_light) c.rect(x0 + 15, 6, 2, 26, wood_dark) c.rect(x0 + 10, 12, 4, 1, wood_dark) c.rect(x0 + 19, 18, 3, 3, (196, 158, 74, 255)) c.rect(x0 + 6, 29, 20, 3, (83, 52, 39, 255)) x0 = slot(13) c.rect(x0, 0, 32, 32, water) c.rect(x0 + 4, 3, 2, 26, water_deep) c.rect(x0 + 19, 8, 2, 22, water_deep) for y in (9, 20, 29): for x in range(3, 29): if (x * 3 + y) % 5 != 0: c.set(x0 + x, y, water_light) for y in (14, 25): for x in range(5, 27): if (x + y * 2) % 6 == 0: c.set(x0 + x, y, water_deep) for sx, sy in ((9, 6), (24, 13), (15, 24), (5, 19)): c.set(x0 + sx, sy, (214, 231, 238, 255)) c.set(x0 + sx + 1, sy, (214, 231, 238, 120)) willow_leaf = (86, 138, 74, 255) willow_leaf_light = (128, 172, 96, 255) willow_leaf_dark = (62, 104, 58, 255) trunk = (98, 64, 44, 255) trunk_light = (132, 92, 60, 255) x0 = slot(14) for cx, cy, rx, ry in ((16, 12, 13, 10), (8, 8, 7, 7), (24, 8, 7, 7), (16, 6, 6, 6)): for py in range(cy - ry, cy + ry): for px in range(cx - rx, cx + rx): if ((px - cx) * (px - cx)) * 2 + ((py - cy) * (py - cy)) * 3 <= rx * ry: shade = willow_leaf if (px * 5 + py * 3) % 11 == 0: shade = willow_leaf_light elif (px * 3 + py * 7) % 13 == 0: shade = willow_leaf_dark c.set(x0 + px, py, shade) for dx, dy in ((-9, 12), (-5, 16), (0, 19), (6, 16), (10, 12), (-2, 10), (3, 10)): for drop in range(9): c.set(x0 + 16 + dx, 18 + dy // 2 + drop, willow_leaf_dark if drop % 3 else willow_leaf_light) c.rect(x0 + 14, 24, 4, 8, trunk) c.rect(x0 + 14, 24, 1, 8, trunk_light) for bx, by in ((10, 18), (20, 16)): c.rect(x0 + bx, by, 3, 2, willow_leaf_dark) x0 = slot(15) c.rect(x0 + 13, 8, 6, 20, trunk) c.rect(x0 + 13, 8, 2, 20, trunk_light) for bx, by in ((8, 22), (21, 19), (6, 26), (24, 25)): c.rect(x0 + bx, by, 4, 2, trunk) for ex, ey in ((-10, 12), (-6, 8), (-2, 6), (3, 7), (8, 11), (11, 15)): for drop in range(14): yy = ey + drop if yy < 30: c.set(x0 + 16 + ex, yy, willow_leaf_dark if drop % 3 else willow_leaf) if drop % 4 == 1: c.set(x0 + 17 + ex, yy, willow_leaf_light) c.outline(x0, 0, 32, 32) shrub = (74, 118, 64, 255) shrub_light = (108, 152, 84, 255) shrub_dark = (56, 92, 54, 255) x0 = slot(16) for cx, cy, rx, ry in ((11, 22, 9, 8), (22, 24, 8, 7), (16, 17, 7, 6)): for py in range(cy - ry, cy + ry): for px in range(cx - rx, cx + rx): if ((px - cx) * (px - cx)) + ((py - cy) * (py - cy)) * 2 <= rx * ry: shade = shrub if (px * 7 + py * 5) % 9 == 0: shade = shrub_light elif (px * 5 + py * 3) % 7 == 0: shade = shrub_dark c.set(x0 + px, py, shade) for fx, fy in ((9, 17), (17, 14), (24, 21), (13, 24), (20, 27)): c.set(x0 + fx, fy, sprout_light) c.outline(x0, 0, 32, 32) x0 = slot(17) flower_red = (196, 92, 96, 255) flower_yellow = (222, 188, 92, 255) flower_white = (232, 228, 214, 255) flower_stem = (74, 118, 64, 255) for fx, fy, fc in ((7, 22, flower_red), (12, 18, flower_yellow), (17, 23, flower_white), (22, 19, flower_red), (26, 24, flower_yellow), (10, 27, flower_white), (19, 27, flower_red)): c.rect(x0 + fx, fy + 2, 1, 4, flower_stem) c.rect(x0 + fx - 1, fy, 3, 3, fc) c.set(x0 + fx, fy + 1, (150, 62, 58, 255) if fc == flower_red else (172, 132, 52, 255)) for gx, gy in ((5, 25), (14, 21), (24, 26), (20, 15)): c.rect(x0 + gx, gy, 2, 1, flower_stem) c.outline(x0, 0, 32, 32) reed = (104, 142, 82, 255) reed_light = (146, 178, 110, 255) reed_seed = (172, 140, 92, 255) x0 = slot(18) for rx, height in ((6, 18), (11, 24), (15, 20), (20, 26), (25, 17)): for drop in range(height): yy = 31 - drop sway = (drop // 5) if rx % 2 else -(drop // 5) c.set(x0 + rx + sway, yy, reed if drop % 3 else reed_light) c.rect(x0 + rx - 1, 31 - height, 3, 3, reed_seed) c.rect(x0 + rx, 31 - height - 2, 1, 2, reed_light) c.outline(x0, 0, 32, 32) stone = (150, 150, 146, 255) stone_light = (178, 178, 172, 255) stone_dark = (118, 118, 114, 255) x0 = slot(19) c.rect(x0 + 4, 18, 24, 7, stone) c.rect(x0 + 4, 18, 24, 1, stone_light) c.rect(x0 + 4, 24, 24, 1, stone_dark) for sx in (10, 17, 24): c.rect(x0 + sx, 19, 1, 5, stone_dark) for lx in (7, 23): c.rect(x0 + lx, 14, 3, 4, stone) c.rect(x0 + lx, 14, 3, 1, stone_light) c.rect(x0 + lx - 1, 17, 5, 2, stone_dark) c.rect(x0 + 6, 25, 20, 4, stone_dark) c.rect(x0 + 7, 29, 18, 2, (96, 96, 92, 255)) c.outline(x0, 0, 32, 32) lantern_paper = (212, 92, 74, 255) lantern_paper_light = (238, 140, 104, 255) lantern_rib = (150, 54, 46, 255) x0 = slot(20) c.rect(x0 + 15, 4, 2, 22, wood_dark) c.rect(x0 + 12, 2, 8, 3, wood) c.rect(x0 + 12, 2, 8, 1, wood_light) c.rect(x0 + 10, 8, 12, 12, lantern_paper) c.rect(x0 + 10, 8, 12, 1, lantern_paper_light) c.rect(x0 + 10, 19, 12, 1, lantern_rib) for rx in (12, 15, 18): c.rect(x0 + rx, 9, 1, 10, lantern_rib) c.rect(x0 + 13, 12, 6, 4, (252, 216, 148, 255)) c.rect(x0 + 11, 20, 10, 2, wood) c.rect(x0 + 13, 22, 6, 2, (252, 216, 148, 255)) c.rect(x0 + 14, 25, 4, 3, lantern_rib) c.outline(x0, 0, 32, 32) return c VARIANTS = [ {"robe": (46, 57, 108, 255), "light": (104, 145, 183, 255), "dark": (32, 41, 80, 255), "extra": "kerchief"}, {"robe": (141, 91, 45, 255), "light": (205, 163, 76, 255), "dark": (104, 66, 32, 255), "extra": "bun_vest"}, {"robe": (46, 113, 91, 255), "light": (121, 185, 139, 255), "dark": (32, 84, 68, 255), "extra": "hairpin_scarf"}, {"robe": (96, 102, 112, 255), "light": (150, 156, 164, 255), "dark": (62, 66, 74, 255), "extra": "round_cap"}, {"robe": (122, 96, 58, 255), "light": (176, 141, 84, 255), "dark": (92, 71, 42, 255), "extra": "straw_hat"}, ] DIRECTIONS = ["north", "south", "west", "east"] def draw_character_cell(c, ox, variant, direction, frame): robe = variant["robe"] light = variant["light"] dark = variant["dark"] extra = variant.get("extra") hat = (196, 158, 74, 255) hat_light = (228, 197, 112, 255) hat_dark = (168, 132, 58, 255) c.rect(ox + 8, 44, 16, 1, (23, 31, 34, 110)) c.rect(ox + 10, 45, 12, 1, (23, 31, 34, 90)) if frame == 0: c.rect(ox + 10, 36, 4, 7, dark) c.rect(ox + 9, 42, 6, 3, SHOE) c.rect(ox + 18, 36, 4, 5, dark) c.rect(ox + 18, 39, 6, 3, SHOE) else: c.rect(ox + 18, 36, 4, 7, dark) c.rect(ox + 18, 42, 6, 3, SHOE) c.rect(ox + 10, 36, 4, 5, dark) c.rect(ox + 10, 39, 6, 3, SHOE) c.rect(ox + 7, 22, 18, 14, robe) c.rect(ox + 23, 22, 2, 14, dark) c.rect(ox + 7, 31, 18, 3, light) c.rect(ox + 13, 22, 6, 2, light) c.rect(ox + 15, 24, 2, 2, light) c.rect(ox + 5, 24, 2, 9, robe) c.rect(ox + 25, 24, 2, 9, robe) if direction != "north": c.rect(ox + 5, 32, 2, 2, light) c.rect(ox + 25, 32, 2, 2, light) c.rect(ox + 5, 34, 2, 3, SKIN) c.rect(ox + 25, 34, 2, 3, SKIN) c.rect(ox + 10, 12, 12, 10, SKIN) c.rect(ox + 9, 8, 14, 4, HAIR) c.rect(ox + 9, 12, 2, 3, HAIR) c.rect(ox + 21, 12, 2, 3, HAIR) if direction == "north": c.rect(ox + 10, 12, 12, 5, HAIR) elif direction == "south": c.rect(ox + 12, 15, 2, 2, EYE) c.rect(ox + 18, 15, 2, 2, EYE) c.rect(ox + 15, 19, 2, 1, (196, 120, 104, 255)) elif direction == "west": c.rect(ox + 18, 12, 4, 6, HAIR) c.rect(ox + 11, 15, 2, 2, EYE) elif direction == "east": c.rect(ox + 10, 12, 4, 6, HAIR) c.rect(ox + 19, 15, 2, 2, EYE) if extra == "straw_hat": c.rect(ox + 11, 3, 10, 7, hat) c.rect(ox + 12, 4, 8, 1, hat_light) c.rect(ox + 6, 9, 20, 2, hat) c.rect(ox + 7, 11, 18, 1, hat_dark) elif extra == "kerchief": c.rect(ox + 8, 7, 16, 4, (40, 52, 96, 255)) c.rect(ox + 8, 7, 16, 1, (68, 84, 138, 255)) c.rect(ox + 24, 10, 3, 3, (40, 52, 96, 255)) c.rect(ox + 24, 12, 2, 4, (68, 84, 138, 255)) elif extra == "bun_vest": c.rect(ox + 14, 4, 4, 4, HAIR) c.rect(ox + 15, 3, 2, 1, HAIR) c.rect(ox + 10, 23, 12, 9, dark) c.rect(ox + 10, 23, 12, 1, light) c.rect(ox + 15, 24, 2, 8, light) elif extra == "hairpin_scarf": c.rect(ox + 21, 9, 5, 1, hat) c.rect(ox + 24, 8, 2, 1, hat_light) c.rect(ox + 9, 21, 14, 2, (150, 62, 58, 255)) c.rect(ox + 9, 21, 14, 1, (188, 92, 84, 255)) c.rect(ox + 22, 22, 3, 6, (150, 62, 58, 255)) elif extra == "round_cap": c.rect(ox + 9, 5, 14, 5, (48, 52, 66, 255)) c.rect(ox + 10, 4, 12, 2, (48, 52, 66, 255)) c.rect(ox + 10, 9, 12, 1, (70, 74, 90, 255)) c.rect(ox + 15, 3, 2, 2, hat) c.outline(ox, 0, 32, 48) def build_character_atlas(): c = Canvas(1280, 48) cell = 0 for variant in VARIANTS: for direction in DIRECTIONS: for frame in (0, 1): draw_character_cell(c, cell * 32, variant, direction, frame) cell += 1 return c def build_interior_atlas(): c = Canvas(392, 96) floor = (172, 138, 96, 255) floor_line = (146, 113, 74, 255) floor_light = (188, 155, 110, 255) wall = (225, 216, 195, 255) wall_light = (238, 232, 214, 255) wall_seam = (196, 180, 152, 255) wood = (119, 76, 47, 255) wood_light = (164, 111, 61, 255) wood_dark = (83, 52, 39, 255) frame_wood = (94, 66, 40, 255) ink = (61, 45, 49, 255) brass = (196, 158, 74, 255) sky = (86, 143, 170, 255) cloud = (214, 231, 238, 255) x0, y0 = 0, 64 c.rect(x0, y0, 32, 32, floor) for y in (7, 15, 23, 31): c.rect(x0, y0 + y, 32, 1, floor_line) c.rect(x0 + 20, y0, 1, 7, floor_line) for x in (6, 26): c.rect(x0 + x, y0 + 8, 1, 7, floor_line) c.rect(x0 + 13, y0 + 16, 1, 7, floor_line) for x in (3, 22): c.rect(x0 + x, y0 + 24, 1, 7, floor_line) for y in range(32): for x in range(32): if (x * 11 + y * 5) % 37 == 0: c.set(x0 + x, y0 + y, floor_light) x0, y0 = 32, 64 c.rect(x0, y0, 32, 32, wall) c.rect(x0, y0, 32, 1, wall_light) c.rect(x0 + 15, y0, 2, 32, wall_seam) c.rect(x0, y0 + 28, 32, 4, floor_line) c.rect(x0, y0 + 31, 32, 1, wood_dark) x0, y0 = 64, 56 c.rect(x0, y0, 48, 40, frame_wood) c.rect(x0 + 3, y0 + 3, 42, 28, sky) c.rect(x0 + 6, y0 + 7, 10, 3, cloud) c.rect(x0 + 20, y0 + 5, 12, 3, cloud) c.rect(x0 + 14, y0 + 3, 3, 28, frame_wood) c.rect(x0 + 30, y0 + 3, 3, 28, frame_wood) c.rect(x0 + 3, y0 + 14, 42, 3, frame_wood) c.rect(x0, y0 + 33, 48, 7, wood) c.rect(x0, y0 + 38, 48, 2, wood_dark) c.outline(x0, y0, 48, 40) x0, y0 = 112, 48 c.rect(x0 + 6, y0 + 16, 8, 28, frame_wood) c.rect(x0 + 50, y0 + 16, 8, 28, frame_wood) c.rect(x0 + 12, y0 + 16, 2, 28, wood_dark) c.rect(x0 + 56, y0 + 16, 2, 28, wood_dark) c.rect(x0, y0 + 4, 64, 14, wood) c.rect(x0, y0 + 4, 64, 3, wood_light) c.rect(x0, y0 + 16, 64, 2, wood_dark) c.rect(x0 + 12, y0 + 10, 6, 5, wall) c.rect(x0 + 13, y0 + 11, 4, 2, sky) c.rect(x0 + 40, y0 + 7, 10, 8, ink) c.rect(x0 + 43, y0 + 5, 4, 2, ink) c.rect(x0 + 50, y0 + 9, 3, 3, ink) c.outline(x0, y0, 64, 48) x0, y0 = 176, 0 c.rect(x0, y0, 48, 96, frame_wood) c.rect(x0, y0, 5, 96, wood_dark) c.rect(x0 + 43, y0, 5, 96, wood_dark) c.rect(x0 + 5, y0 + 4, 38, 88, (224, 211, 185, 255)) c.rect(x0 + 9, y0 + 8, 30, 14, (238, 232, 214, 255)) c.rect(x0 + 9, y0 + 22, 30, 2, wall_seam) quilt = (114, 62, 67, 255) quilt_line = (94, 48, 52, 255) c.rect(x0 + 5, y0 + 28, 38, 64, quilt) c.rect(x0 + 5, y0 + 28, 38, 2, quilt_line) for y in (38, 48, 58, 68, 78): c.rect(x0 + 5, y0 + y, 38, 2, quilt_line) c.rect(x0, y0 + 88, 48, 6, wood_dark) c.rect(x0, y0 + 92, 48, 2, ink) c.outline(x0, y0, 48, 96) x0, y0 = 224, 0 c.rect(x0, y0, 32, 96, ink) c.rect(x0 + 4, y0 + 4, 24, 88, (142, 102, 72, 255)) c.rect(x0 + 6, y0 + 6, 20, 84, (163, 124, 86, 255)) c.rect(x0 + 8, y0 + 60, 16, 4, frame_wood) c.rect(x0 + 10, y0 + 56, 12, 4, frame_wood) c.rect(x0 + 12, y0 + 52, 8, 4, frame_wood) c.rect(x0 + 8, y0 + 68, 16, 2, (188, 163, 126, 255)) c.outline(x0, y0, 32, 96) x0, y0 = 256, 32 rug = (150, 62, 58, 255) rug_inner = (171, 84, 72, 255) rug_dark = (134, 52, 50, 255) c.rect(x0, y0, 96, 64, rug) c.rect(x0, y0, 96, 5, brass) c.rect(x0, y0 + 59, 96, 5, brass) c.rect(x0, y0, 5, 64, brass) c.rect(x0 + 91, y0, 5, 64, brass) c.rect(x0 + 9, y0 + 9, 78, 46, rug_inner) for dy in range(-9, 10): w = (9 - abs(dy)) * 2 c.rect(x0 + 48 - w // 2, y0 + 32 + dy, w, 1, brass) for cx, cy in ((12, 12), (82, 12), (12, 50), (82, 50)): c.rect(x0 + cx, y0 + cy, 2, 2, brass) for y in range(9, 55): for x in range(9, 87): if (x * 7 + y * 13) % 41 == 0: c.set(x0 + x, y0 + y, rug_dark) x0, y0 = 352, 60 c.rect(x0, y0 + 8, 40, 28, wood) c.rect(x0, y0, 40, 12, frame_wood) c.rect(x0, y0, 40, 2, wood_light) c.rect(x0, y0 + 11, 40, 2, ink) c.rect(x0 + 17, y0 + 9, 6, 8, brass) c.rect(x0 + 19, y0 + 12, 2, 3, ink) for bx, by in ((0, 8), (37, 8), (0, 31), (37, 31)): c.rect(x0 + bx, y0 + by, 3, 3, wood_light) c.rect(x0, y0 + 33, 40, 3, ink) c.outline(x0, y0, 40, 36) return c def main(): ASSET_DIR.mkdir(parents=True, exist_ok=True) targets = [ ("jiangnan-world.png", build_world_atlas(), 672, 32), ("jiangnan-characters.png", build_character_atlas(), 1280, 48), ("jiangnan-interior.png", build_interior_atlas(), 392, 96), ] for name, canvas, w, h in targets: path = ASSET_DIR / name write_png(path, canvas) verify_png(path, w, h) if __name__ == "__main__": main()