diff --git a/ff5reader.py b/ff5reader.py index 9f31aa5..c44578c 100755 --- a/ff5reader.py +++ b/ff5reader.py @@ -152,30 +152,32 @@ class FF5Reader(QMainWindow): (data & 0x03F000) >> 12, (data & 0xFC0000) >> 18] return ' '.join([hex(i,2) for i in tilesets]) + def split_blockmaps(data): + blockmaps = [(data & 0x000003FF) - 1, + ((data & 0x000FFC00) >> 10) - 1, + ((data & 0x3FF00000) >> 20) - 1] + return ' '.join([hex(i,3) for i in blockmaps]) - zone_structure = [('NPC Layer', 2, None), - ('Name', 1, [z[2] for z in zone_names]), - ('ShadowFlags', 1, None), - (hex(4, 2), 1, None), - (hex(5, 2), 1, None), - ('Flags '+hex(6,2),1, None), - (hex(7, 2), 1, None), - ('Tilesets', 3, split_tilesets), - (hex(11, 2), 1, None), - ('Collision Layer',1, None), - (hex(13, 2), 1, None), - (hex(14, 2), 1, None), - (hex(15, 2), 1, None), - (hex(16, 2), 1, None), - (hex(17, 2), 1, None), - (hex(18, 2), 1, None), - (hex(19, 2), 1, None), - (hex(20, 2), 1, None), - (hex(21, 2), 1, None), - ('Palette', 1, None), - (hex(23, 2), 1, None), - (hex(24, 2), 1, None), - ('Music', 1, const.BGM_Tracks)] + zone_structure = [('NPC Layer', 2, None), # 00 01 + ('Name', 1, [z[2] for z in zone_names]), # 02 + ('ShadowFlags', 1, None), # 03 + ('Graphic maths', 1, None), # 04 + ('Tile properties', 1, None), # 05 + ('Flags '+hex(6), 1, None), # 06 + (hex(7), 1, None), # 07 + ('Blockset', 1, None), # 08 + ('Tilesets', 3, split_tilesets), # 09 0A 0B + ('Blockmaps', 4, split_blockmaps), # 0C 0D 0E 0F + (hex(16), 1, None), # 10 + (hex(17), 1, None), # 11 + (hex(18), 1, None), # 12 + (hex(19), 1, None), # 13 + (hex(20), 1, None), # 14 + (hex(21), 1, None), # 15 + ('Palette', 1, None), # 16 + (hex(23), 1, None), # 17 + (hex(24), 1, None), # 18 + ('Music', 1, const.BGM_Tracks)] # 19 zone_headers = ['Address'] + [z[0] for z in zone_structure] zone_data = [parse_struct(ROM_en, 0x0E9C00 + (i*0x1A), zone_structure) for i in range(const.zone_count)] @@ -223,20 +225,21 @@ class FF5Reader(QMainWindow): perfcount() print('Generating map tiles') worldmap_palettes = [generate_palette(ROM_jp, 0x0FFCC0+(i*0x100), length=0x160, transparent=True) for i in range(3)] - world_tiles = [make_worldmap_tiles(ROM_jp, 0x0FF0C0+(i*0x300), 0x1B8000+(i*0x2000), 0x0FF9C0+(i*0x100)) for i in range(3)] + world_tiles = [make_worldmap_blocks(ROM_jp, 0x0FF0C0+(i*0x300), 0x1B8000+(i*0x2000), 0x0FF9C0+(i*0x100)) for i in range(3)] worldpixmaps = [make_worldmap_pixmap(ROM_jp, i, 0x0FFCC0+(t*0x100), world_tiles[t]) for i, t in enumerate([0, 1, 0, 2, 2])] - world_tiles_pixmaps = [] + world_blocks_pixmaps = [] for i, tiles in enumerate(world_tiles): a = [] for t in tiles: t.setColorTable(worldmap_palettes[i]) a.append(QPixmap.fromImage(t)) - world_tiles_pixmaps.append(a) + world_blocks_pixmaps.append(a) perfcount() - worldmap_subtiles = make_worldmap_subtiles_pixmap(ROM_jp, 0x1B8000, 0x0FF9C0, 0x0FFCC0) - worldmap_subtiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BA000, 0x0FFAC0, 0x0FFDC0) - worldmap_subtiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BC000, 0x0FFBC0, 0x0FFEC0, length=128) + worldmap_tiles = make_worldmap_tiles_pixmap(ROM_jp, 0x1B8000, 0x0FF9C0, 0x0FFCC0) + worldmap_tiles += make_worldmap_tiles_pixmap(ROM_jp, 0x1BA000, 0x0FFAC0, 0x0FFDC0) + worldmap_tiles += make_worldmap_tiles_pixmap(ROM_jp, 0x1BC000, 0x0FFBC0, 0x0FFEC0, length=128) perfcount() + field_tiles = make_all_field_tiles(ROM_jp) field_minitiles = make_all_field_minitiles(ROM_jp) perfcount() @@ -245,6 +248,19 @@ class FF5Reader(QMainWindow): perfcount() fieldmap_tiles = [make_field_map_tile_pixmap(ROM_jp, i, st_field_tiles, st_field_minitiles) for i in range(const.zone_count)] perfcount() + print('Generating field map blocks') + zones = [parse_zone(ROM_jp, i) for i in range(const.zone_count)] + field_blocksets = [get_field_map_block_layouts(ROM_jp, i) for i in range(28)] + perfcount() + blockmaps = get_blockmaps(ROM_jp) + field_blocks = [] + zone_pxs = [] + for z in zones: + blocks = make_field_map_blocks_px(ROM_jp, z.blockset, field_tiles, field_minitiles, field_blocksets) + field_blocks.append(stitch_tileset_px(blocks)) + zone_pxs += make_zone_pxs(blocks, [blockmaps[b] for b in z.blockmaps if b!=-1]) + perfcount() + print('Generating Battle backgrounds') battle_bgs = make_battle_backgrounds(ROM_jp) perfcount() @@ -275,31 +291,36 @@ class FF5Reader(QMainWindow): strings_tab = QTabWidget() structs_tab = QTabWidget() sprites_tab = QTabWidget() + backgrounds_tab = QTabWidget() self.ff5widget.addTab(strings_tab, 'Strings') self.ff5widget.addTab(structs_tab, 'Structs') self.ff5widget.addTab(sprites_tab, 'Images') + self.ff5widget.addTab(backgrounds_tab, 'Backgrounds') - sprites_tab.addTab(make_pixmap_table(self.glyph_sprites['glyphs_en_s'], scale=4), 'Glyphs (EN)') - sprites_tab.addTab(make_pixmap_table(self.glyph_sprites['glyphs_en_l'], scale=2), 'Glyphs (Dialogue EN)') - sprites_tab.addTab(make_pixmap_table(self.glyph_sprites['glyphs_jp_s'], scale=4), 'Glyphs (JP)') - sprites_tab.addTab(make_pixmap_table(self.glyph_sprites['glyphs_jp_l'], scale=2), 'Glyphs (Large JP)') - sprites_tab.addTab(make_pixmap_table(self.glyph_sprites['glyphs_kanji'], scale=2),'Glyphs (Kanji)') - sprites_tab.addTab(make_pixmap_table(worldmap_subtiles, cols=16, scale=4), 'Worldmap Subtiles') - sprites_tab.addTab(make_pixmap_table(world_tiles_pixmaps[0], cols=16, scale=4), 'World 1 Tiles') - sprites_tab.addTab(make_pixmap_table(world_tiles_pixmaps[1], cols=16, scale=4), 'World 2 Tiles') - sprites_tab.addTab(make_pixmap_table(world_tiles_pixmaps[2], cols=16, scale=4), 'Underwater Tiles') - sprites_tab.addTab(make_pixmap_table(worldpixmaps, cols=1, scale=1, large=True), 'Worldmaps') - sprites_tab.addTab(make_pixmap_table(fieldmap_tiles, cols=8, scale=2), 'Fieldmap Tiles') - sprites_tab.addTab(make_pixmap_table(battle_bgs, cols=8, scale=1), 'Battle BGs') - sprites_tab.addTab(make_pixmap_table(self.battle_strips, cols=22, scale=2), 'Character Battle Sprites') - sprites_tab.addTab(make_pixmap_table(status_strips, cols=22, scale=2), 'Status Sprites') - sprites_tab.addTab(make_pixmap_table(enemy_sprites_named, cols=32, scale=1), 'Enemy Sprites') + sprites_tab.addTab(make_px_table(self.glyph_sprites['glyphs_en_s'], scale=4), 'Glyphs (EN)') + sprites_tab.addTab(make_px_table(self.glyph_sprites['glyphs_en_l'], scale=2), 'Glyphs (Dialogue EN)') + sprites_tab.addTab(make_px_table(self.glyph_sprites['glyphs_jp_s'], scale=4), 'Glyphs (JP)') + sprites_tab.addTab(make_px_table(self.glyph_sprites['glyphs_jp_l'], scale=2), 'Glyphs (Large JP)') + sprites_tab.addTab(make_px_table(self.glyph_sprites['glyphs_kanji'], scale=2),'Glyphs (Kanji)') + sprites_tab.addTab(make_px_table(self.battle_strips, cols=22, scale=2), 'Character Battle Sprites') + sprites_tab.addTab(make_px_table(status_strips, cols=22, scale=2), 'Status Sprites') + sprites_tab.addTab(make_px_table(enemy_sprites_named, cols=32, scale=1), 'Enemy Sprites') - self.ff4widget.addTab(make_pixmap_table(self.battle_strips_ff4, cols=16, scale=2), 'Character Battle Sprites') - self.ff4widget.addTab(make_pixmap_table(self.portraits_ff4, cols=14, scale=2), 'Character Portraits') - self.ff4widget.addTab(make_pixmap_table(self.field_strips_ff4, cols=17, scale=2), 'Character Field Sprites') - self.ff6widget.addTab(make_pixmap_table(self.battle_strips_ff6, cols=32, scale=2), 'Character Sprites') - self.ff6widget.addTab(make_pixmap_table(self.portraits_ff6, cols=19, scale=2), 'Character Portraits') + backgrounds_tab.addTab(make_px_table(worldmap_tiles, cols=16, scale=4), 'Worldmap Tiles') + backgrounds_tab.addTab(make_px_table(world_blocks_pixmaps[0], cols=16, scale=4), 'World 1 Blocks') + backgrounds_tab.addTab(make_px_table(world_blocks_pixmaps[1], cols=16, scale=4), 'World 2 Blocks') + backgrounds_tab.addTab(make_px_table(world_blocks_pixmaps[2], cols=16, scale=4), 'Underwater Blocks') + backgrounds_tab.addTab(make_px_table(worldpixmaps, cols=1, scale=1, large=True), 'Worldmaps') + backgrounds_tab.addTab(make_px_table(fieldmap_tiles, cols=8, scale=1), 'Fieldmap Tiles') + backgrounds_tab.addTab(make_px_table(field_blocks, cols=16, scale=1), 'Field Blocks') + backgrounds_tab.addTab(make_px_table(zone_pxs, cols=4, scale=1, large=1), 'Zone') + backgrounds_tab.addTab(make_px_table(battle_bgs, cols=8, scale=1), 'Battle BGs') + + self.ff4widget.addTab(make_px_table(self.battle_strips_ff4, cols=16, scale=2), 'Character Battle Sprites') + self.ff4widget.addTab(make_px_table(self.portraits_ff4, cols=14, scale=2), 'Character Portraits') + self.ff4widget.addTab(make_px_table(self.field_strips_ff4, cols=17, scale=2), 'Character Field Sprites') + self.ff6widget.addTab(make_px_table(self.battle_strips_ff6, cols=32, scale=2), 'Character Sprites') + self.ff6widget.addTab(make_px_table(self.portraits_ff6, cols=19, scale=2), 'Character Portraits') structs_tab.addTab(make_table(zone_headers, zone_data, True), 'Zones') diff --git a/includes/helpers.py b/includes/helpers.py index e335941..7efbe31 100644 --- a/includes/helpers.py +++ b/includes/helpers.py @@ -29,7 +29,7 @@ def hex_length(i): ''' return divceil(i.bit_length(), 4) -def hex(num, digits): +def hex(num, digits=2): ''' Consolidate hex formatting for consistency ''' @@ -60,12 +60,16 @@ def parse_struct(rom, offset, structure): return out -def decompress_lzss(rom, start, header=False): +def decompress_lzss(rom, start, header=False, length=None): ''' Algorithm from http://slickproductions.org/slickwiki/index.php/Noisecross:Final_Fantasy_V_Compression ''' - uncompressed_length = indirect(rom, start) - ptr = start+2 + ptr = start + if length: + uncompressed_length = length + else: + uncompressed_length = indirect(rom, start) + ptr += 2 output = [] buffer = [0 for i in range(0x800)] buffer_p = 0x07DE diff --git a/includes/qthelpers.py b/includes/qthelpers.py index 962d302..e51fdac 100644 --- a/includes/qthelpers.py +++ b/includes/qthelpers.py @@ -156,7 +156,7 @@ def make_table(headers, items, sortable=False, row_labels=True, scale=2): return table -def make_pixmap_table(items, cols=16, scale=4, large=False): +def make_px_table(items, cols=16, scale=4, large=False): rows = divceil(len(items), cols) rd = hex_length(rows-1)+1 cd = hex_length(cols-1) diff --git a/includes/snes.py b/includes/snes.py index c0bfad0..a097b8a 100644 --- a/includes/snes.py +++ b/includes/snes.py @@ -20,6 +20,29 @@ Functions common to SNES FFs from includes.helpers import * from includes.snestile import * +from collections import namedtuple + +zone = namedtuple('zone', 'npcs name shadowflags blockset tilesets blockmaps palette music') + +def parse_zone(rom, id, start_address=0x0E9C00): + ptr = start_address+(id*0x1A) + npcs = indirect(rom, ptr) + name = rom[ptr+2] + shadowflags = rom[ptr+3] + blockset = rom[ptr+8] + tilesets_b = indirect(rom, ptr+9, length=3) + tilesets = [(tilesets_b & 0x00003F), + (tilesets_b & 0x000FC0) >> 6, + (tilesets_b & 0x03F000) >> 12, + (tilesets_b & 0xFC0000) >> 18] + pal_address = 0x03BB00 + (indirect(rom, ptr+0x16)<<8) + blockmaps_b = indirect(rom, ptr+0xC, length=4) + blockmaps = [(blockmaps_b & 0x000003FF) - 1, + ((blockmaps_b & 0x000FFC00) >> 10) - 1, + ((blockmaps_b & 0x3FF00000) >> 20) - 1] + palettes = [generate_palette(rom, pal_address+i*32, transparent=True) for i in range(8)] + music = rom[ptr+0x19] + return zone(npcs, name, shadowflags, blockset, tilesets, blockmaps, palettes, music) def make_battle_strip(rom, palette_address, tile_address, num_tiles, bpp=4): @@ -96,27 +119,27 @@ def make_character_status_sprites(rom): return pixmaps -def make_worldmap_subtiles(rom, tiles_address, lut_address, length=0x100): +def make_worldmap_tiles(rom, tiles_address, lut_address, length=0x100): subtiles = [] for i in range(length): pal_index = rom[lut_address+i]//16 subtiles.append(create_tile_mode7_compressed_indexed(rom[tiles_address+i*32:tiles_address+i*32+32], pal_index)) return subtiles -def stitch_worldmap_tiles(rom, subtiles, offset=0x0FF0C0): - tiles = [] +def stitch_worldmap_tiles(rom, tiles, offset=0x0FF0C0): + blocks = [] for i in range(0xC0): canvas = Canvas_Indexed(2, 2) for j in range(4): k = indirect(rom, offset+(j*0xC0)+i, length=1) - canvas.draw_tile(j%2, j//2, subtiles[k]) - tiles.append(canvas.image) - return tiles + canvas.draw_tile(j%2, j//2, tiles[k]) + blocks.append(canvas.image) + return blocks -def make_worldmap_tiles(rom, tiles_address, subtiles_address, lut_address, length=0x100): - return stitch_worldmap_tiles(rom, make_worldmap_subtiles(rom, subtiles_address, lut_address, length=length), tiles_address) +def make_worldmap_blocks(rom, blocks_address, tiles_address, lut_address, length=0x100): + return stitch_worldmap_tiles(rom, make_worldmap_tiles(rom, tiles_address, lut_address, length=length), blocks_address) -def make_worldmap_subtiles_pixmap(rom, tiles_address, lut_address, palette_address, length=0x100): +def make_worldmap_tiles_pixmap(rom, tiles_address, lut_address, palette_address, length=0x100): tiles = [] palettes = [generate_palette(rom, palette_address+i*32, transparent=True) for i in range(16)] for i in range(length): @@ -182,6 +205,12 @@ def stitch_tileset(tiles): canvas.draw_tile(i%16, i//16, tile) return canvas +def stitch_tileset_px(tiles_px): + canvas = Canvas(16, len(tiles_px)//16) + for i, tile in enumerate(tiles_px): + canvas.draw_pixmap(i%16, i//16, tile) + return canvas.pixmap() + def get_field_map_tiles(rom, id): ''' This is a bit of a mess of pointer chains for now, so generalising it will have to wait. @@ -214,6 +243,46 @@ def make_field_map_tile_pixmap(rom, id, st_tiles, st_minitiles): canvas.draw_pixmap(0, 48, st_minitiles[minitile].pixmap(p)) return canvas.pixmap() +def get_field_map_block_layouts(rom, id, start_address=0x0F0000): + ptr = indirect(rom, start_address+(id*2)) + start_address + data = decompress_lzss(rom, ptr) + output = [] + for i in range(0, 0x200, 2): + output.append([data[j+i+k] for j in range(0, 0x800, 0x200) for k in range(2)]) + #for i in range(0, 0x800, 8): + #output.append([data[i+k] for k in range(8)]) + return output + +def make_field_map_blocks_px(rom, id, tilesets, minitilesets, blockmaps): + *i_tiles, i_minitiles, palettes = get_field_map_tiles(rom, id) + tiles = tilesets[i_tiles[0]] + tilesets[i_tiles[1]] + tilesets[i_tiles[2]] + tiles += minitilesets[i_minitiles] + i_blockmap = rom[0x0E9C08 + (id * 0x1A)] + blockmap = blockmaps[i_blockmap] + + blocks = [make_tilemap_canvas(tm, tiles, cols=2, rows=2, pal_adjust=0, tile_modulo=0x1000) for tm in blockmap] + return [b.pixmap(palettes) for b in blocks] + +def get_blockmaps(rom, start_address=0x0B0000, num=0x148): + bank = 0x0B0000 + ptrs = [indirect(rom, start_address)+bank] + for i in range(1, num): + ptr = indirect(rom, start_address+(i*2)) + if (ptr+bank) < ptrs[-1]: + bank += 0x010000 + ptrs.append(ptr+bank) + blockmaps = [decompress_lzss(rom, ptr) for ptr in ptrs] + return blockmaps + +def make_zone_pxs(blocks, blockmaps): + output = [] + for bm in blockmaps: + canvas = Canvas(64, 64, tilesize=16) + for i, b in enumerate(bm): + canvas.draw_pixmap(i%64, i//64, blocks[b]) + output.append(canvas.pixmap()) + return output + def decompress_battle_tilemap(rom, address): ''' Decompresses the tilemap for a battle background. @@ -248,14 +317,13 @@ def decompress_battle_tilemap(rom, address): output[1::2] = [i&0xDF for i in o2[:length//2]] return bytes(output) -def apply_battle_tilemap_flips(rom, id, battle_terrain): +def apply_battle_tilemap_flips(rom, id, tilemap): if id==0xFF: - return battle_terrain + return tilemap ptr = indirect(rom, 0x14C736+(id*2))+0x140000 - length = len(battle_terrain)//2 - output = list(battle_terrain) + length = len(tilemap)//2 + output = list(tilemap) buffer = [] - while len(buffer) < length: a = rom[ptr] ptr += 1 @@ -267,49 +335,54 @@ def apply_battle_tilemap_flips(rom, id, battle_terrain): for b in reversed(range(0, 8, 1)): buffer.append((a>>b)&0x01) - for i in range(len(battle_terrain)//2): + for i in range(len(tilemap)//2): output[i*2+1] |= (buffer[i] << 6) return bytes(output) -def make_tilemap_canvas(tilemap, tiles, tile_adjust=0, pal_adjust=-1): +def parse_tileset_word(data): + a, b = data[:2] + tile_index = a|((b & 0x03) << 8) + palette = (b & 0x1C) >> 2 + priority = (b & 0x20) >> 5 + h_flip = (b & 0x40) >> 6 + v_flip = (b & 0x80) >> 7 + return tile_index, palette, h_flip, v_flip, priority + +def make_tilemap_canvas(tilemap, tiles, cols=64, rows=64, tile_adjust=0, pal_adjust=-1, tile_modulo=0x80): ''' Battle bg is 64x64 map size, 8x8 tile size 4bpp tiles ''' - canvas = Canvas_Indexed(64, 64) + canvas = Canvas_Indexed(cols, rows) for i in range(len(tilemap)//2): - a, b = tilemap[i*2:(i+1)*2] - tile_index = a|((b & 0x02) << 8) - p = (b & 0x1C) >> 2 - priority = (b & 0x20) >> 5 - h_flip = (b & 0x40) >> 6 - v_flip = (b & 0x80) >> 7 - - x = (i % 32) + 32*((i//1024) % 2) - y = (i //32) - 32*((i//1024) % 2) + tile_index, p, h_flip, v_flip, priority = parse_tileset_word(tilemap[i*2:(i+1)*2]) + if cols > 32: + x = (i % 32) + 32*((i//1024) % 2) + y = (i //32) - 32*((i//1024) % 2) + else: + x = i % cols + y = i //cols try: - tile = tiles[(tile_index+tile_adjust)%0x80] + tile = tiles[(tile_index+tile_adjust)%tile_modulo] canvas.draw_tile(x, y, tile, h_flip, v_flip, p+pal_adjust) except BaseException as e: print(e, p, hex(tile_index,2), hex(tile_adjust,2), hex(tile_index+tile_adjust,2)) return canvas -def make_tilemap_pixmap(tilemap, tiles, palettes, tile_adjust=0, pal_adjust=-1): +def make_tilemap_pixmap(tilemap, tiles, palettes, cols=64, rows=64, tile_adjust=0, pal_adjust=-1): ''' Battle bg is 64x64 map size, 8x8 tile size 4bpp tiles ''' - canvas = Canvas(64, 64) + canvas = Canvas(cols, rows) for i in range(len(tilemap)//2): - a, b = tilemap[i*2:(i+1)*2] - tile_index = a|((b & 0x02) << 8) - p = (b & 0x1C) >> 2 - priority = (b & 0x20) >> 5 - h_flip = (b & 0x40) >> 6 - v_flip = (b & 0x80) >> 7 - - x = (i % 32) + 32*((i//1024) % 2) - y = (i //32) - 32*((i//1024) % 2) + tile_index, p, h_flip, v_flip, priority = parse_tileset_word(tilemap[i*2:(i+1)*2]) + if cols > 32: + x = (i % 32) + 32*((i//1024) % 2) + y = (i //32) - 32*((i//1024) % 2) + else: + x = i % cols + y = i //cols try: palette = palettes[p+pal_adjust] tile = tiles[(tile_index+tile_adjust)%0x80] @@ -366,7 +439,7 @@ def make_battle_backgrounds(rom): a.append(b) a = [(i, j) for i, j in zip(a[0::2], a[1::2])] animations.append(a) - animation_time = 15 # Frames before changing + animation_time = 8 # Frames before changing pal_cycle_ptr_start = 0x14C6CD pal_cycle_ptrs = [indirect(rom, pal_cycle_ptr_start+(i*2))+0x140000 for i in range(3)] diff --git a/includes/snestile.py b/includes/snestile.py index 661bbad..dc17469 100644 --- a/includes/snestile.py +++ b/includes/snestile.py @@ -213,12 +213,13 @@ def generate_palette(rom, offset, length=32, transparent=False): class Canvas: - def __init__(self, cols, rows, color=bg_trans): - self.image = QImage(8*cols, 8*rows, QImage.Format_ARGB32_Premultiplied) + def __init__(self, cols, rows, color=bg_trans, tilesize=8): + self.image = QImage(tilesize*cols, tilesize*rows, QImage.Format_ARGB32_Premultiplied) + self.tilesize = tilesize self.image.fill(color) self.painter = QtGui.QPainter(self.image) - self.max_x = 1 - self.max_y = 1 + self.max_col = 1 + self.max_row = 1 def __del__(self): del self.painter @@ -226,19 +227,19 @@ class Canvas: def draw_pixmap(self, col, row, pixmap, h_flip=False, v_flip=False): h_s = -1 if h_flip else 1 v_s = -1 if v_flip else 1 - x = (col+h_flip)*8*h_s - y = (row+v_flip)*8*v_s + x = (col+h_flip)*self.tilesize*h_s + y = (row+v_flip)*self.tilesize*v_s self.painter.scale(h_s, v_s) self.painter.drawPixmap(x, y, pixmap) self.painter.scale(h_s, v_s) # Invert it again to restore it to normal - if col > self.max_x: - self.max_x = col - if row > self.max_y: - self.max_y = row + if col > self.max_col: + self.max_col = col + if row > self.max_row: + self.max_row = row def pixmap(self, trim=False): if trim: - return QPixmap.fromImage(self.image.copy(0, 0, self.max_x*8+8, self.max_y*8+8)) + return QPixmap.fromImage(self.image.copy(0, 0, (self.max_col+1)*self.tilesize, (self.max_row+1)*self.tilesize)) return QPixmap.fromImage(self.image) @@ -270,6 +271,8 @@ class Canvas_Indexed: self.max_row = max(row, self.max_row) def pixmap(self, palette, trim=False): + if isinstance(palette[0], list): + palette = [i for p in palette for i in p] # Flatten if trim: img = self.image.copy(0, 0, (self.max_col+1)*self.tilesize, (self.max_row+1)*self.tilesize) img.setColorTable(palette)