diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..feae5c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +__pycache__ \ No newline at end of file diff --git a/aseprite.py b/aseprite.py index 21280e8..804fe59 100644 --- a/aseprite.py +++ b/aseprite.py @@ -1,11 +1,8 @@ import struct import sys from dataclasses import dataclass -from pprint import pprint, pformat -import textwrap from typing import Union, Optional import zlib -from operator import itemgetter class CustomInt: value: int @@ -486,212 +483,58 @@ def parse_cel_chunk(mem): return cel_chunk, mem -with open(sys.argv[1], 'rb') as f: - buf = f.read() - mem = memoryview(buf) -def pprinti(o, i): - s = pformat(o) - print(textwrap.indent(s, ' ' * i)) +def parse_file(mem): + header, mem = parse_header(mem) + #pprint(header) + assert header.color_depth == 8, header.color_depth -def pack_bgr555(red, green, blue): - bgr = ( - ((red >> 3) << 0) | - ((green >> 3) << 5) | - ((blue >> 3) << 10) - ) - return struct.pack(">H", bgr) + frame_header, mem = parse_frame_header(mem) + #pprint(frame_header) -def pack_index(i): - return struct.pack(">I", i) + tilesets = dict() # by tileset id + layers = [] + palette = None + cel_chunks = dict() # by layer index -def pack_old_palette_chunk(old_palette_chunk): - with open("palette.bin", "wb") as f: - for color in old_palette_chunk.packets[0].colors: - f.write(pack_bgr555(*color)) + for _ in range(frame_header.number_of_chunks): + chunk, mem = parse_chunk(mem) + #pprinti(chunk, 1) + if chunk.chunk_type == 0x4: + old_palette_chunk, _ = parse_old_palette_chunk(chunk.data) + #pprinti(old_palette_chunk, 2) + assert palette is None + palette = old_palette_chunk + elif chunk.chunk_type == 0x2019: + palette_chunk, _ = parse_palette_chunk(chunk.data) + #pprinti(palette_chunk, 2) + assert palette is None + palette = palette_chunk + elif chunk.chunk_type == 0x2023: + tileset_chunk, _ = parse_tileset_chunk(chunk.data) + assert tileset_chunk.tileset_id not in tilesets + tilesets[tileset_chunk.tileset_id] = tileset_chunk + #pprinti(tileset_chunk, 2) + elif chunk.chunk_type == 0x2004: + layer_chunk, _ = parse_layer_chunk(chunk.data, header.flags) + assert layer_chunk.layer_type == 2 + layers.append(layer_chunk) + #pprinti(layer_chunk, 2) + elif chunk.chunk_type == 0x2005: + cel_chunk, _ = parse_cel_chunk(chunk.data) + #pprinti(cel_chunk, 2) + assert cel_chunk.layer_index not in cel_chunks + cel_chunks[cel_chunk.layer_index] = cel_chunk + elif chunk.chunk_type == 0x2020: + # user data + pass + elif chunk.chunk_type == 0x2007: + # color profile + pass + else: + print("unhandled chunk: ") + pprinti(chunk, 1) -def pack_palette_chunk(palette_chunk): - with open("palette.bin", "wb") as f: - assert palette_chunk.first_color_index_to_change == 0 + assert palette is not None - for entry in palette_chunk.entries: - color = (entry.red, entry.green, entry.blue) - f.write(pack_bgr555(*color)) - - print("palette.bin", f.tell(), file=sys.stderr) - -def pack_palette(palette): - if type(palette) is PaletteChunk: - pack_palette_chunk(palette) - elif type(palette) is OldPaletteChunk: - pack_old_palette_chunk(palette) - else: - assert False, type(palette) - -def pack_character_2x2(tileset_chunk, offset): - #tileset_chunk.number_of_tiles, - #tileset_chunk.tile_width, - #tileset_chunk.tile_height, - - assert tileset_chunk.tile_width == 16 - assert tileset_chunk.tile_height == 16 - assert type(tileset_chunk.data) == TilesetChunkInternal - - buf = bytearray(16 * 16) - - for cell_ix in range(4): - for y in range(8): - for x in range(8): - tileset_x = 8 * (cell_ix % 2) + x - tileset_y = 8 * (cell_ix // 2) + y - px = tileset_chunk.data.pixel[offset + tileset_y * 16 + tileset_x] - buf[cell_ix * 8 * 8 + y * 8 + x] = px - - return bytes(buf) - -def pack_character_1x1(tileset_chunk, offset): - assert tileset_chunk.tile_width == 8 - assert tileset_chunk.tile_height == 8 - assert type(tileset_chunk.data) == TilesetChunkInternal - - buf = bytearray(8 * 8) - - for y in range(8): - for x in range(8): - tileset_x = x - tileset_y = y - px = tileset_chunk.data.pixel[offset + tileset_y * 8 + tileset_x] - buf[y * 8 + x] = px - - return bytes(buf) - -def pack_character_patterns(filename, tileset_chunk): - with open(filename, "wb") as f: - for i in range(tileset_chunk.number_of_tiles): - offset = tileset_chunk.tile_width * tileset_chunk.tile_height * i - - if tileset_chunk.tile_width == 8 and tileset_chunk.tile_height == 8: - buf = pack_character_1x1(tileset_chunk, offset) - elif tileset_chunk.tile_width == 16 and tileset_chunk.tile_height == 16: - buf = pack_character_2x2(tileset_chunk, offset) - else: - assert False, (tileset_chunk.tile_width, tileset_chunk.tile_height) - - f.write(buf) - - print(filename, f.tell(), file=sys.stderr) - -def pack_pattern_name_table(filename, cel_chunk, x_cells, y_cells): - with open(filename, "wb") as f: - assert type(cel_chunk.data) == CelChunk_CompressedTilemap - #assert cel_chunk.data.width_in_number_of_tiles <= 64 - #assert cel_chunk.data.height_in_number_of_tiles <= 64 - - tile_width = cel_chunk.data.width_in_number_of_tiles - tile_height = cel_chunk.data.height_in_number_of_tiles - - print(tile_width, tile_height) - - h_pages = ((tile_width + (x_cells - 1)) & (~(x_cells - 1))) // x_cells - v_pages = ((tile_height + (y_cells - 1)) & (~(y_cells - 1))) // y_cells - - if h_pages > 2: - h_pages = 2 - if v_pages > 2: - v_pages = 2 - - print("h_pages, v_pages", h_pages, v_pages) - - for v_page in range(v_pages): - for h_page in range(h_pages): - for y in range(y_cells): - for x in range(x_cells): - tx = (h_page * x_cells) + x - ty = (v_page * y_cells) + y - if tx >= tile_width or ty >= tile_height: - f.write(pack_index(0)) - else: - cel_chunk_ix = ty * tile_width + tx - tile_data = cel_chunk.data.tile[cel_chunk_ix] - - tile_id = tile_data & cel_chunk.data.bitmask_for_tile_id.value - x_flip = (tile_data & cel_chunk.data.bitmask_for_x_flip.value) != 0 - y_flip = (tile_data & cel_chunk.data.bitmask_for_y_flip.value) != 0 - - pattern = (int(y_flip) << 31) | (int(x_flip) << 30) | tile_id - - f.write(pack_index(pattern)) - - print(filename, f.tell(), file=sys.stderr) - -header, mem = parse_header(mem) -#pprint(header) -assert header.color_depth == 8, header.color_depth - -frame_header, mem = parse_frame_header(mem) -#pprint(frame_header) - -tilesets = dict() # by tileset id -layers = [] -palette = None -cel_chunks = dict() # by layer index - -for _ in range(frame_header.number_of_chunks): - chunk, mem = parse_chunk(mem) - #pprinti(chunk, 1) - if chunk.chunk_type == 0x4: - old_palette_chunk, _ = parse_old_palette_chunk(chunk.data) - #pprinti(old_palette_chunk, 2) - assert palette is None - palette = old_palette_chunk - elif chunk.chunk_type == 0x2019: - palette_chunk, _ = parse_palette_chunk(chunk.data) - #pprinti(palette_chunk, 2) - assert palette is None - palette = palette_chunk - elif chunk.chunk_type == 0x2023: - tileset_chunk, _ = parse_tileset_chunk(chunk.data) - assert tileset_chunk.tileset_id not in tilesets - tilesets[tileset_chunk.tileset_id] = tileset_chunk - #pprinti(tileset_chunk, 2) - elif chunk.chunk_type == 0x2004: - layer_chunk, _ = parse_layer_chunk(chunk.data, header.flags) - assert layer_chunk.layer_type == 2 - layers.append(layer_chunk) - #pprinti(layer_chunk, 2) - elif chunk.chunk_type == 0x2005: - cel_chunk, _ = parse_cel_chunk(chunk.data) - #pprinti(cel_chunk, 2) - assert cel_chunk.layer_index not in cel_chunks - cel_chunks[cel_chunk.layer_index] = cel_chunk - elif chunk.chunk_type == 0x2020: - # user data - pass - elif chunk.chunk_type == 0x2007: - # color profile - pass - else: - print("unhandled chunk: ") - pprinti(chunk, 1) - -assert palette is not None - -pack_palette(palette) - -for tileset_index, tileset_chunk in sorted(tilesets.items(), key=itemgetter(0)): - filename = f"character_pattern__tileset_{tileset_index}.bin" - pack_character_patterns(filename, tileset_chunk) - -for layer_index, cel_chunk in sorted(cel_chunks.items(), key=itemgetter(0)): - filename = f"pattern_name_table__layer_{layer_index}.bin" - #layers[layer_index] - print(f"layer={layer_index} layer_name={layers[layer_index].layer_name} tileset={layers[layer_index].tileset_index}"); - tileset_chunk = tilesets[layers[layer_index].tileset_index] - - x_cells = 64 // (tileset_chunk.tile_width // 8) - y_cells = 64 // (tileset_chunk.tile_height // 8) - - pack_pattern_name_table(filename, cel_chunk, x_cells, y_cells) - -#for layer_index, layer_chunk in enumerate(layers): -# print(f"layer={layer_index} layer_name={layer_chunk.layer_name} tileset={layer_chunk.tileset_index}"); + return tilesets, layers, palette, cel_chunks diff --git a/background.py b/background.py new file mode 100644 index 0000000..4d13795 --- /dev/null +++ b/background.py @@ -0,0 +1,168 @@ +import sys +from pprint import pprint, pformat +import textwrap +import struct +from operator import itemgetter + +from aseprite import parse_file +from aseprite import PaletteChunk, OldPaletteChunk, TilesetChunkInternal, CelChunk_CompressedTilemap + +def pprinti(o, i): + s = pformat(o) + print(textwrap.indent(s, ' ' * i)) + +def pack_bgr555(red, green, blue): + bgr = ( + ((red >> 3) << 0) | + ((green >> 3) << 5) | + ((blue >> 3) << 10) + ) + return struct.pack(">H", bgr) + +def pack_index(i): + return struct.pack(">I", i) + +def pack_old_palette_chunk(old_palette_chunk): + with open("palette.bin", "wb") as f: + for color in old_palette_chunk.packets[0].colors: + f.write(pack_bgr555(*color)) + +def pack_palette_chunk(palette_chunk): + with open("palette.bin", "wb") as f: + assert palette_chunk.first_color_index_to_change == 0 + + for entry in palette_chunk.entries: + color = (entry.red, entry.green, entry.blue) + f.write(pack_bgr555(*color)) + + print("palette.bin", f.tell(), file=sys.stderr) + +def pack_palette(palette): + if type(palette) is PaletteChunk: + pack_palette_chunk(palette) + elif type(palette) is OldPaletteChunk: + pack_old_palette_chunk(palette) + else: + assert False, type(palette) + +def pack_character_2x2(tileset_chunk, offset): + #tileset_chunk.number_of_tiles, + #tileset_chunk.tile_width, + #tileset_chunk.tile_height, + + assert tileset_chunk.tile_width == 16 + assert tileset_chunk.tile_height == 16 + assert type(tileset_chunk.data) == TilesetChunkInternal + + buf = bytearray(16 * 16) + + for cell_ix in range(4): + for y in range(8): + for x in range(8): + tileset_x = 8 * (cell_ix % 2) + x + tileset_y = 8 * (cell_ix // 2) + y + px = tileset_chunk.data.pixel[offset + tileset_y * 16 + tileset_x] + buf[cell_ix * 8 * 8 + y * 8 + x] = px + + return bytes(buf) + +def pack_character_1x1(tileset_chunk, offset): + assert tileset_chunk.tile_width == 8 + assert tileset_chunk.tile_height == 8 + assert type(tileset_chunk.data) == TilesetChunkInternal + + buf = bytearray(8 * 8) + + for y in range(8): + for x in range(8): + tileset_x = x + tileset_y = y + px = tileset_chunk.data.pixel[offset + tileset_y * 8 + tileset_x] + buf[y * 8 + x] = px + + return bytes(buf) + +def pack_character_patterns(filename, tileset_chunk): + with open(filename, "wb") as f: + for i in range(tileset_chunk.number_of_tiles): + offset = tileset_chunk.tile_width * tileset_chunk.tile_height * i + + if tileset_chunk.tile_width == 8 and tileset_chunk.tile_height == 8: + buf = pack_character_1x1(tileset_chunk, offset) + elif tileset_chunk.tile_width == 16 and tileset_chunk.tile_height == 16: + buf = pack_character_2x2(tileset_chunk, offset) + else: + assert False, (tileset_chunk.tile_width, tileset_chunk.tile_height) + + f.write(buf) + + print(filename, f.tell(), file=sys.stderr) + +def pack_pattern_name_table(filename, cel_chunk, x_cells, y_cells): + with open(filename, "wb") as f: + assert type(cel_chunk.data) == CelChunk_CompressedTilemap + #assert cel_chunk.data.width_in_number_of_tiles <= 64 + #assert cel_chunk.data.height_in_number_of_tiles <= 64 + + tile_width = cel_chunk.data.width_in_number_of_tiles + tile_height = cel_chunk.data.height_in_number_of_tiles + + print(tile_width, tile_height) + + h_pages = ((tile_width + (x_cells - 1)) & (~(x_cells - 1))) // x_cells + v_pages = ((tile_height + (y_cells - 1)) & (~(y_cells - 1))) // y_cells + + if h_pages > 2: + h_pages = 2 + if v_pages > 2: + v_pages = 2 + + print("h_pages, v_pages", h_pages, v_pages) + + for v_page in range(v_pages): + for h_page in range(h_pages): + for y in range(y_cells): + for x in range(x_cells): + tx = (h_page * x_cells) + x + ty = (v_page * y_cells) + y + if tx >= tile_width or ty >= tile_height: + f.write(pack_index(0)) + else: + cel_chunk_ix = ty * tile_width + tx + tile_data = cel_chunk.data.tile[cel_chunk_ix] + + tile_id = tile_data & cel_chunk.data.bitmask_for_tile_id.value + x_flip = (tile_data & cel_chunk.data.bitmask_for_x_flip.value) != 0 + y_flip = (tile_data & cel_chunk.data.bitmask_for_y_flip.value) != 0 + + pattern = (int(y_flip) << 31) | (int(x_flip) << 30) | tile_id + + f.write(pack_index(pattern)) + + print(filename, f.tell(), file=sys.stderr) + +with open(sys.argv[1], 'rb') as f: + buf = f.read() + mem = memoryview(buf) + +tilesets, layers, palette, cel_chunks = parse_file(buf) + +pack_palette(palette) + +for tileset_index, tileset_chunk in sorted(tilesets.items(), key=itemgetter(0)): + filename = f"character_pattern__tileset_{tileset_index}.bin" + pack_character_patterns(filename, tileset_chunk) + +for layer_index, cel_chunk in sorted(cel_chunks.items(), key=itemgetter(0)): + filename = f"pattern_name_table__layer_{layer_index}.bin" + #layers[layer_index] + print(f"layer={layer_index} layer_name={layers[layer_index].layer_name} tileset={layers[layer_index].tileset_index}"); + tileset_chunk = tilesets[layers[layer_index].tileset_index] + + x_cells = 64 // (tileset_chunk.tile_width // 8) + y_cells = 64 // (tileset_chunk.tile_height // 8) + + pack_pattern_name_table(filename, cel_chunk, x_cells, y_cells) + +#for layer_index, layer_chunk in enumerate(layers): +# print(f"layer={layer_index} layer_name={layer_chunk.layer_name} tileset={layer_chunk.tileset_index}");