aboutsummaryrefslogtreecommitdiffstats
path: root/tools/unique.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/unique.py')
-rwxr-xr-xtools/unique.py106
1 files changed, 106 insertions, 0 deletions
diff --git a/tools/unique.py b/tools/unique.py
new file mode 100755
index 00000000..34530a3e
--- /dev/null
+++ b/tools/unique.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Usage: python unique.py [-f|--flip] [-x|--cross] image.png
+
+Erase duplicate tiles from an input image.
+-f or --flip counts X/Y mirrored tiles as duplicates.
+-x or --cross erases with a cross instead of a blank tile.
+"""
+
+import sys
+
+import png
+
+def rgb5_pixels(row):
+ yield from (tuple(c // 8 for c in row[x:x+3]) for x in range(0, len(row), 4))
+
+def rgb8_pixels(row):
+ yield from (c * 8 + c // 4 for pixel in row for c in pixel)
+
+def gray_pixels(row):
+ yield from (pixel[0] // 10 for pixel in row)
+
+def rows_to_tiles(rows, width, height):
+ assert len(rows) == height and len(rows[0]) == width
+ yield from (tuple(tuple(row[x:x+8]) for row in rows[y:y+8])
+ for y in range(0, height, 8) for x in range(0, width, 8))
+
+def tiles_to_rows(tiles, width, height):
+ assert width % 8 == 0 and height % 8 == 0
+ width, height = width // 8, height // 8
+ tiles = list(tiles)
+ assert len(tiles) == width * height
+ tile_rows = (tiles[y:y+width] for y in range(0, width * height, width))
+ yield from ([tile[y][x] for tile in tile_row for x in range(8)]
+ for tile_row in tile_rows for y in range(8))
+
+def tile_variants(tile, flip):
+ yield tile
+ if flip:
+ yield tile[::-1]
+ yield tuple(row[::-1] for row in tile)
+ yield tuple(row[::-1] for row in tile[::-1])
+
+def unique_tiles(tiles, flip, cross):
+ if cross:
+ blank = [[(0, 0, 0)] * 8 for _ in range(8)]
+ for y in range(8):
+ blank[y][y] = blank[y][7 - y] = (31, 31, 31)
+ blank = tuple(tuple(row) for row in blank)
+ else:
+ blank = tuple(tuple([(31, 31, 31)] * 8) for _ in range(8))
+ seen = set()
+ for tile in tiles:
+ if any(variant in seen for variant in tile_variants(tile, flip)):
+ yield blank
+ else:
+ yield tile
+ seen.add(tile)
+
+def is_grayscale(colors):
+ return (colors.issubset({(31, 31, 31), (21, 21, 21), (10, 10, 10), (0, 0, 0)}) or
+ colors.issubset({(31, 31, 31), (20, 20, 20), (10, 10, 10), (0, 0, 0)}))
+
+def erase_duplicates(filename, flip, cross):
+ with open(filename, 'rb') as file:
+ width, height, rows = png.Reader(file).asRGBA8()[:3]
+ rows = [list(rgb5_pixels(row)) for row in rows]
+ if width % 8 or height % 8:
+ return False
+ tiles = unique_tiles(rows_to_tiles(rows, width, height), flip, cross)
+ rows = list(tiles_to_rows(tiles, width, height))
+ if is_grayscale({c for row in rows for c in row}):
+ rows = [list(gray_pixels(row)) for row in rows]
+ writer = png.Writer(width, height, greyscale=True, bitdepth=2, compression=9)
+ else:
+ rows = [list(rgb8_pixels(row)) for row in rows]
+ writer = png.Writer(width, height, greyscale=False, bitdepth=8, compression=9)
+ with open(filename, 'wb') as file:
+ writer.write(file, rows)
+ return True
+
+def main():
+ flip = cross = False
+ while True:
+ if len(sys.argv) < 2:
+ print(f'Usage: {sys.argv[0]} [-f|--flip] [-x|--cross] tileset.png', file=sys.stderr)
+ sys.exit(1)
+ elif sys.argv[1] in {'-f', '--flip'}:
+ flip = True
+ elif sys.argv[1] in {'-x', '--cross'}:
+ cross = True
+ elif sys.argv[1] in {'-fx', '-xf'}:
+ flip = cross = True
+ else:
+ break
+ sys.argv.pop(1)
+ for filename in sys.argv[1:]:
+ if not filename.lower().endswith('.png'):
+ print(f'{filename} is not a .png file!', file=sys.stderr)
+ elif not erase_duplicates(filename, flip, cross):
+ print(f'{filename} is not divisible into 8x8 tiles!', file=sys.stderr)
+
+if __name__ == '__main__':
+ main()