#!/usr/bin/env python3 """Generate stylized ad/poster/sign textures for Akiba Crossing. Bold two-tone layouts, punchy shapes and fake-glyph text blocks (random stroke clusters that read as kanji/kana from gameplay distance) — the graphic-design density of an anime city street, generated as a reusable library: assets/textures/ads/ad_XX.png (vertical 256x512 posters/banners) assets/textures/ads/board_XX.png (horizontal 512x256 billboards) assets/textures/ads/column_XX.png (128x512 vertical sign columns) Run: python tools/generate_ads.py """ import random from pathlib import Path from PIL import Image, ImageDraw OUT = Path("assets/textures/ads") OUT.mkdir(parents=True, exist_ok=True) PALETTES = [ [(238, 51, 94), (255, 255, 255), (24, 24, 32)], [(38, 206, 220), (24, 24, 40), (255, 255, 255)], [(255, 196, 37), (30, 30, 34), (238, 51, 94)], [(120, 90, 240), (255, 255, 255), (255, 196, 37)], [(46, 230, 120), (18, 24, 24), (255, 255, 255)], [(255, 255, 255), (238, 51, 94), (24, 24, 32)], [(24, 28, 44), (38, 206, 220), (255, 196, 37)], [(255, 120, 40), (255, 255, 255), (30, 30, 34)], ] rng = random.Random(20260719) def glyph(d: ImageDraw.ImageDraw, x, y, s, color): """A fake kanji-ish glyph: a few thick strokes in an s x s box.""" w = max(2, s // 7) strokes = rng.randint(2, 4) for _ in range(strokes): kind = rng.randint(0, 2) if kind == 0: # horizontal bar yy = y + rng.randint(0, s - w) d.rectangle([x, yy, x + s, yy + w], fill=color) elif kind == 1: # vertical bar xx = x + rng.randint(0, s - w) d.rectangle([xx, y, xx + w, y + s], fill=color) else: # box m = rng.randint(s // 5, s // 3) d.rectangle([x + m, y + m, x + s - m, y + s - m], outline=color, width=w) def text_block(d, x, y, s, count, color, vertical=True): for i in range(count): if vertical: glyph(d, x, y + i * int(s * 1.25), s, color) else: glyph(d, x + i * int(s * 1.25), y, s, color) def background(d, w, h, pal): d.rectangle([0, 0, w, h], fill=pal[0]) layout = rng.randint(0, 3) if layout == 0: # diagonal split d.polygon([(0, h), (w, h), (w, rng.randint(0, h // 2))], fill=pal[1]) elif layout == 1: # bottom band d.rectangle([0, int(h * 0.72), w, h], fill=pal[1]) elif layout == 2: # big circle r = int(min(w, h) * 0.42) cx, cy = rng.randint(r, w - r), rng.randint(r, h - r) d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=pal[1]) else: # stripes n = rng.choice([3, 4]) for i in range(n): if i % 2 == 0: d.rectangle([i * w // n, 0, (i + 1) * w // n, h], fill=pal[1]) def poster(path, w, h): pal = rng.choice(PALETTES) img = Image.new("RGB", (w, h)) d = ImageDraw.Draw(img) background(d, w, h, pal) ink = pal[2] if h > w: # vertical: glyph column + accent dot s = w // 4 text_block(d, w - s - s // 2, s // 2, s, rng.randint(3, 5), ink, vertical=True) if rng.random() < 0.6: r = w // 6 d.ellipse([s // 2, h - 2 * r - s // 2, s // 2 + 2 * r, h - s // 2], fill=ink) else: # horizontal: glyph row s = h // 3 text_block(d, s // 2, (h - s) // 2, s, rng.randint(3, 6), ink, vertical=False) # border d.rectangle([0, 0, w - 1, h - 1], outline=(15, 14, 20), width=max(3, w // 60)) img.save(path) def column(path, w, h): """Vertical sign column: stacked glyphs, one per segment.""" pal = rng.choice(PALETTES) img = Image.new("RGB", (w, h)) d = ImageDraw.Draw(img) d.rectangle([0, 0, w, h], fill=pal[0]) s = int(w * 0.6) n = h // int(s * 1.4) for i in range(n): glyph(d, (w - s) // 2, int(i * s * 1.4 + s * 0.25), s, pal[2]) d.rectangle([0, 0, w - 1, h - 1], outline=(15, 14, 20), width=3) img.save(path) def main(): for i in range(10): poster(OUT / f"ad_{i:02d}.png", 256, 512) for i in range(8): poster(OUT / f"board_{i:02d}.png", 512, 256) for i in range(6): column(OUT / f"column_{i:02d}.png", 128, 512) print(f"generated {10 + 8 + 6} ad textures in {OUT}") if __name__ == "__main__": main()