diff --git a/scripts/assets.py b/scripts/assets.py index f1187bc..f688232 100755 --- a/scripts/assets.py +++ b/scripts/assets.py @@ -3,7 +3,9 @@ import glob import json import os +import pygame import random +import re import sys from PIL import Image @@ -99,7 +101,7 @@ def unglob(list_of_globs): def input_wh(prompt): while True: - geometry = input(prompt) + geometry = input(prompt).strip() try: cols, rows = [int(x) for x in geometry.split(' ')] return cols, rows @@ -107,9 +109,154 @@ def input_wh(prompt): pass -def main(): +# Returns True or False. +def input_ok(prompt): + while True: + ok = input(prompt).strip() + if ok.startswith('y'): + return True + if ok.startswith('n'): + return False + + +def draw_checkerboard(size): + surface = pygame.display.get_surface() + surface.fill((224, 224, 224)) + for i in range(surface.get_width() // size + 1): + for j in range(surface.get_height() // size + 1): + if (i + j) % 2 == 0: + continue + rect = pygame.Rect(i * size, j * size, size, size) + surface.fill((192, 192, 192), rect) + + +def show_splits(image_width, image_height, cols, rows): + surface = pygame.display.get_surface() + split_width = image_width / cols + split_height = image_height / rows + for i in range(cols): + for j in range(rows): + rect = pygame.Rect( + i * split_width, j * split_height, split_width + 1, split_height + 1) + pygame.draw.rect(surface, (255, 0, 255), rect, 1) + pygame.display.flip() + + +def render_text(text, pos, color): + surface = pygame.display.get_surface() + font = pygame.font.SysFont('notomono', 16) + image = font.render(text, True, color) + surface.blit(image, pos) + pygame.display.flip() + + +def render_sprite(metadata): + line_color = (255, 0, 255) + + surface = pygame.display.get_surface() + draw_checkerboard(8) + image = pygame.image.load(metadata['filename']) + surface.blit(image, (0, 0)) + + if metadata.get('chunks'): + for chunk in metadata['chunks']: + rect = pygame.Rect( + chunk['x'], chunk['y'], chunk['width'] + 1, chunk['height'] + 1) + pygame.draw.rect(surface, line_color, rect, 1) + label_pos = (chunk['x'] + 4, chunk['y']) + render_text(str(chunk['index']), label_pos, line_color) + caption_pos = (4, 4 + metadata['image_height'] + chunk['index'] * 20) + caption = '%d: %s' % (chunk['index'], chunk.get('name', '')) + render_text(caption, caption_pos, (0, 0, 0)) + + pygame.display.flip() + + +def set_sprite_chunk_size(metadata): + cols, rows = input_wh('how many columns & rows of sprites? ') + metadata['chunk_columns'] = cols + metadata['chunk_rows'] = rows + metadata['chunk_width'] = metadata['image_width'] // cols + metadata['chunk_height'] = metadata['image_height'] // rows + metadata['chunks'] = [] + for i in range(cols * rows): + x = i % cols + y = i // cols + chunk_md = { + 'index': i, + 'x': x * metadata['chunk_width'], + 'y': y * metadata['chunk_height'], + 'width': metadata['chunk_width'], + 'height': metadata['chunk_height'] + } + metadata['chunks'].append(chunk_md) + render_sprite(metadata) + + +def edit_sprite_chunk_metadata(chunk): + while True: + name = input('name for chunk #%d: ' % chunk['index']).strip() + print(name) + if re.fullmatch(r'\w+', name): + chunk['name'] = name + return + + +def edit_sprite_metadata(filename, metadata=None): + if metadata is None: + image = pygame.image.load(filename) + metadata = { + 'filename': filename, + 'image_width': image.get_width(), + 'image_height': image.get_height(), + } + + print('\nprocessing %s (%dx%d)' % ( + filename, metadata['image_width'], metadata['image_height'])) + + render_sprite(metadata) + + if not metadata.get('chunk_width'): + set_sprite_chunk_size(metadata) + + while True: + render_sprite(metadata) + prompt = 'edit (c)hunk sizes, type a chunk #, (n)ext, or (q)uit: ' + choice = input(prompt).strip() + if choice == 'n': + return metadata, False + elif choice == 'q': + return metadata, True + elif choice == 'c': + set_sprite_chunk_size(metadata) + elif re.fullmatch(r'\d+', choice): + chunk_num = int(choice) + if 0 <= chunk_num < len(metadata['chunks']): + edit_sprite_chunk_metadata(metadata['chunks'][chunk_num]) + else: + print('invalid chunk #') + else: + print('invalid choice') + + +def annotate_sprites(sprite_files, all_metadata): + pygame.init() + surface = pygame.display.set_mode((1200, 900), pygame.RESIZABLE) + + for filename in sprite_files: + sprite_metadata, quit = edit_sprite_metadata( + filename, all_metadata.get(filename)) + all_metadata[filename] = sprite_metadata + with open('metadata.json', 'w') as f: + json.dump(all_metadata, f) + if quit: + return + + +def main(args): os.chdir(os.path.expanduser('~/time_fantasy')) - metadata = json.load(open('metadata.json')) + with open('metadata.json') as f: + all_metadata = json.load(f) sprite_files = unglob(SPRITE_FILES) tileset_files = unglob(TILESET_FILES) @@ -120,22 +267,14 @@ def main(): (len(sprite_files), len(tileset_files), len(animation_files), len(icon_files), len(background_files))) - for filename in sprite_files: - with Image.open(filename) as image: - x = image.show() - cols, rows = input_wh('%s (%dx%d): ' % ( - filename, image.size[0], image.size[1])) - width = image.size[0] / cols - height = image.size[1] / rows - print(width, height) - if rows == 1 and cols == 1: - continue - for i in range(rows): - for j in range(cols): - box = (j * width, i * height, (j + 1) * width, (i + 1) * height) - sprites = image.crop(box) - sprites.show() + if len(args) < 1: + return + + command = args[0] + + if command == 'annotate-sprites': + annotate_sprites(sprite_files, all_metadata) if __name__ == '__main__': - main() + main(sys.argv[1:])