add tool for annotating sprite metadata

This commit is contained in:
Colin McMillen 2019-10-02 15:17:05 -04:00
parent 3bb110172f
commit 46e5a34120

View File

@ -3,7 +3,9 @@
import glob import glob
import json import json
import os import os
import pygame
import random import random
import re
import sys import sys
from PIL import Image from PIL import Image
@ -99,7 +101,7 @@ def unglob(list_of_globs):
def input_wh(prompt): def input_wh(prompt):
while True: while True:
geometry = input(prompt) geometry = input(prompt).strip()
try: try:
cols, rows = [int(x) for x in geometry.split(' ')] cols, rows = [int(x) for x in geometry.split(' ')]
return cols, rows return cols, rows
@ -107,9 +109,154 @@ def input_wh(prompt):
pass 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')) 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) sprite_files = unglob(SPRITE_FILES)
tileset_files = unglob(TILESET_FILES) tileset_files = unglob(TILESET_FILES)
@ -120,22 +267,14 @@ def main():
(len(sprite_files), len(tileset_files), len(animation_files), (len(sprite_files), len(tileset_files), len(animation_files),
len(icon_files), len(background_files))) len(icon_files), len(background_files)))
for filename in sprite_files: if len(args) < 1:
with Image.open(filename) as image: return
x = image.show()
cols, rows = input_wh('%s (%dx%d): ' % ( command = args[0]
filename, image.size[0], image.size[1]))
width = image.size[0] / cols if command == 'annotate-sprites':
height = image.size[1] / rows annotate_sprites(sprite_files, all_metadata)
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 __name__ == '__main__': if __name__ == '__main__':
main() main(sys.argv[1:])