mirror of https://github.com/pret/pokecrystal.git
107 lines
3.3 KiB
Python
107 lines
3.3 KiB
Python
|
#!/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()
|