From 45d2614affa0c7d38f430cba303673aee31f0aad Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 14 Nov 2013 17:05:13 -0500 Subject: gfx: 1bpp<->2bpp conversion this is the simplest way to support 1bpp without rewriting a lot --- pokemontools/gfx.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index c7c6bec..7583835 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1453,6 +1453,19 @@ def png_to_lz(filein): +def convert_2bpp_to_1bpp(data): + """ + Convert planar 2bpp image data to 1bpp. Assume images are two colors. + """ + return data[::2] + +def convert_1bpp_to_2bpp(data): + """ + Convert 1bpp image data to planar 2bpp (black/white). + """ + return type(data)(byte for byte in data for _ in (0, 1)) + + def mass_to_png(debug=False): # greyscale -- cgit v1.2.3 From 3acbf5023728d924b252ab8b15c0c740aab8645a Mon Sep 17 00:00:00 2001 From: yenatch Date: Sun, 17 Nov 2013 15:11:13 -0500 Subject: refactor png-to-2bpp conversion --- pokemontools/gfx.py | 194 ++++++++++++++++++++++++++++------------------------ 1 file changed, 105 insertions(+), 89 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 7583835..2bc613a 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1321,133 +1321,149 @@ def to_png(filein, fileout=None, pal_file=None, height=None, width=None): w.write(file, map) +def export_png_to_2bpp(filein, fileout=None, palout=None): + image, palette = png_to_2bpp(filein) + if fileout == None: + fileout = os.path.splitext(filein)[0] + '.2bpp' + to_file(fileout, image) -def to_2bpp(filein, fileout=None, palout=None): - """ - Take a png and converts it to planar 2bpp. - """ - - if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.2bpp' - - with open(filein, 'rb') as file: + if palout == None: + palout = os.path.splitext(fileout)[0] + '.pal' + export_palette(palette, palout) - r = png.Reader(file) - info = r.asRGBA8() - width = info[0] - height = info[1] +def get_image_padding(width, height, wstep=8, hstep=8): - rgba = list(info[2]) - greyscale = info[3]['greyscale'] + padding = { + 'left': 0, + 'right': 0, + 'top': 0, + 'bottom': 0, + } + if width % wstep: + pad = float(width % wstep) / 2 + padding['left'] = int(ceil(pad)) + padding['right'] = int(floor(pad)) - padding = { 'left': 0, - 'right': 0, - 'top': 0, - 'bottom': 0, } - #if width % 8 != 0: - # padding['left'] = int(ceil((width / 8 + 8 - width) / 2)) - # padding['right'] = int(floor((width / 8 + 8 - width) / 2)) - #if height % 8 != 0: - # padding['top'] = int(ceil((height / 8 + 8 - height) / 2)) - # padding['bottom'] = int(floor((height / 8 + 8 - height) / 2)) + if height % hstep: + pad = float(height % hstep) / 2 + padding['top'] = int(ceil(pad)) + padding['bottom'] = int(floor(pad)) + return padding - # turn the flat values into something more workable - pixel_length = 4 # rgba - image = [] +def png_to_2bpp(filein): + """ + Convert a png image to planar 2bpp. + """ - # while we're at it, let's size up the palette + with open(filein, 'rb') as data: + width, height, rgba, info = png.Reader(data).asRGBA8() + rgba = list(rgba) + greyscale = info['greyscale'] + # png.Reader returns flat pixel data. Nested is easier to work with + len_px = 4 # rgba + image = [] palette = [] - for line in rgba: newline = [] - for pixel in range(len(line)/pixel_length): - i = pixel * pixel_length - color = { 'r': line[i ], - 'g': line[i+1], - 'b': line[i+2], - 'a': line[i+3], } + for px in xrange(0, len(line), len_px): + color = { 'r': line[px ], + 'g': line[px+1], + 'b': line[px+2], + 'a': line[px+3], } newline += [color] - if color not in palette: palette += [color] - image.append(newline) + if color not in palette: + palette += [color] + image += [newline] - # pad out any small palettes + assert len(palette) <= 4, 'Palette should be 4 colors, is really %d' % len(palette) + + # Pad out smaller palettes with greyscale colors hues = { 'white': { 'r': 0xff, 'g': 0xff, 'b': 0xff, 'a': 0xff }, 'black': { 'r': 0x00, 'g': 0x00, 'b': 0x00, 'a': 0xff }, 'grey': { 'r': 0x55, 'g': 0x55, 'b': 0x55, 'a': 0xff }, 'gray': { 'r': 0xaa, 'g': 0xaa, 'b': 0xaa, 'a': 0xff }, } - while len(palette) < 4: - for hue in hues.values(): - if not any(color is hue for color in palette): - palette += [hue] - if len(palette) >= 4: break - - assert len(palette) <= 4, 'Palette should be 4 colors, is really ' + str(len(palette)) + for hue in hues.values(): + if len(palette) >= 4: + break + if hue not in palette: + palette += [hue] - # sort by luminance + # Sort palettes by luminance def luminance(color): - # this is actually in reverse, thanks to dmg/cgb palette ordering rough = { 'r': 4.7, 'g': 1.4, 'b': 13.8, } - return sum(color[key] * -rough[key] for key in rough.keys()) - palette = sorted(palette, key=luminance) + return sum(color[key] * rough[key] for key in rough.keys()) + palette.sort(key=luminance) - # spit out a new .pal file - # disable this if it causes problems with paletteless images - if palout == None: - if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): - palout = os.path.splitext(fileout)[0]+'.pal' - if palout != None: - output = [] - for color in palette: - word = rgb_to_dmg(color) - output += [word & 0xff] - output += [word >> 8] - to_file(palout, output) + # Game Boy palette order + palette.reverse() - # create a new map of quaternary color ids - map = [] - if padding['top']: map += [0] * (width + padding['left'] + padding['right']) * padding['top'] + # Map pixels to quaternary color ids + padding = get_image_padding(width, height) + width += padding['left'] + padding['right'] + height += padding['top'] + padding['bottom'] + pad = [0] + + qmap = [] + qmap += pad * width * padding['top'] for line in image: - if padding['left']: map += [0] * padding['left'] + qmap += pad * padding['left'] for color in line: - map.append(palette.index(color)) - if padding['right']: map += [0] * padding['right'] - if padding['bottom']: map += [0] * (width + padding['left'] + padding['right']) * padding['bottom'] - - # split it into strips of 8, and make them planar - num_columns = width / 8 - num_rows = height / 8 - tile = 8 * 8 + qmap += [palette.index(color)] + qmap += pad * padding['right'] + qmap += pad * width * padding['bottom'] + + # Graphics are stored in tiles instead of lines + tile_width = 8 + tile_height = 8 + num_columns = width / tile_width + num_rows = height / tile_height image = [] - for row in range(num_rows): - for column in range(num_columns): - for strip in range(tile / 8): - anchor = row*num_columns*tile + column*tile/8 + strip*width - line = map[anchor:anchor+8] - bottom = 0 - top = 0 + + for row in xrange(num_rows): + for column in xrange(num_columns): + + # Split it up into strips to convert to planar data + for strip in xrange(tile_height): + anchor = ( + row * num_columns * tile_width * tile_height + + column * tile_width + + strip * width + ) + line = qmap[anchor : anchor + tile_width] + bottom, top = 0, 0 for bit, quad in enumerate(line): - bottom += (quad & 1) << (7-bit) - top += ((quad & 2) >> 1) << (7-bit) - image.append(bottom) - image.append(top) + bottom += (quad & 1) << (7 - bit) + top += (quad /2 & 1) << (7 - bit) + image += [bottom, top] - to_file(fileout, image) + return image, palette + + +def export_palette(palette, filename): + if os.path.exists(filename): + output = [] + for color in palette: + word = rgb_to_dmg(color) + output += [word & 0xff] + output += [word >> 8] + to_file(filename, output) def png_to_lz(filein): name = os.path.splitext(filein)[0] - to_2bpp(filein) + export_png_to_2bpp(filein) image = open(name+'.2bpp', 'rb').read() to_file(name+'.lz', Compressed(image).output) @@ -1639,21 +1655,21 @@ if __name__ == "__main__": # front.2bpp and tiles.2bpp are combined before compression, # so we have to pass in the anim file and pic size name = os.path.splitext(argv[4])[0] - to_2bpp(name+'.png', name+'.2bpp') + export_png_to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() anim = open(argv[3], 'rb').read() size = int(sqrt(len(pic)/16)) # assume square pic to_file(name+'.lz', Compressed(pic + anim, 'vert', size).output) elif argv[2] == '--vert': name = os.path.splitext(argv[3])[0] - to_2bpp(name+'.png', name+'.2bpp') + export_png_to_2bpp(name+'.png', name+'.2bpp') pic = open(name+'.2bpp', 'rb').read() to_file(name+'.lz', Compressed(pic, 'vert').output) else: png_to_lz(argv[2]) elif argv[1] == 'png-to-2bpp': - to_2bpp(argv[2]) + export_png_to_2bpp(argv[2]) elif argv[1] == '2bpp-to-lz': if argv[2] == '--vert': -- cgit v1.2.3 From 9e3105e5c043c87d22a934a89df9e7da4621220f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 17:36:59 -0600 Subject: tabs to spaces in map_editor.py This is to maintain consistency in python source code formatting in the project. --- pokemontools/map_editor.py | 1128 ++++++++++++++++++++++---------------------- 1 file changed, 564 insertions(+), 564 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 566d422..362a67f 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -14,636 +14,636 @@ conf = configuration.Config() version = 'red' if version == 'crystal': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2d' % x - block_dir = os.path.join(conf.path, 'tilesets/') - block_ext = '_metatiles.bin' + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2d' % x + block_dir = os.path.join(conf.path, 'tilesets/') + block_ext = '_metatiles.bin' - palettes_on = True - palmap_dir = os.path.join(conf.path, 'tilesets/') - palette_dir = os.path.join(conf.path, 'tilesets/') + palettes_on = True + palmap_dir = os.path.join(conf.path, 'tilesets/') + palette_dir = os.path.join(conf.path, 'tilesets/') - asm_dir = os.path.join(conf.path, 'maps/') + asm_dir = os.path.join(conf.path, 'maps/') - constants_dir = os.path.join(conf.path, 'constants/') - constants_filename = os.path.join(constants_dir, 'map_constants.asm') + constants_dir = os.path.join(conf.path, 'constants/') + constants_filename = os.path.join(constants_dir, 'map_constants.asm') - header_dir = os.path.join(conf.path, 'maps/') + header_dir = os.path.join(conf.path, 'maps/') elif version == 'red': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2x' % x - block_dir = os.path.join(conf.path, 'gfx/blocksets/') - block_ext = '.bst' + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2x' % x + block_dir = os.path.join(conf.path, 'gfx/blocksets/') + block_ext = '.bst' - palettes_on = False + palettes_on = False - asm_path = os.path.join(conf.path, 'main.asm') + asm_path = os.path.join(conf.path, 'main.asm') - constants_filename = os.path.join(conf.path, 'constants.asm') + constants_filename = os.path.join(conf.path, 'constants.asm') - header_path = os.path.join(conf.path, 'main.asm') + header_path = os.path.join(conf.path, 'main.asm') else: - raise Exception, 'version must be "crystal" or "red"' + raise Exception, 'version must be "crystal" or "red"' def get_constants(): - lines = open(constants_filename, 'r').readlines() - for line in lines: - if ' EQU ' in line: - name, value = [s.strip() for s in line.split(' EQU ')] - globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) + lines = open(constants_filename, 'r').readlines() + for line in lines: + if ' EQU ' in line: + name, value = [s.strip() for s in line.split(' EQU ')] + globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) get_constants() class Application(Frame): - def __init__(self, master=None): - self.display_connections = False - Frame.__init__(self, master) - self.grid() - Style().configure("TFrame", background="#444") - self.paint_tile = 1 - self.init_ui() - - def init_ui(self): - self.connections = {} - self.button_frame = Frame(self) - self.button_frame.grid(row=0, column=0, columnspan=2) - self.map_frame = Frame(self) - self.map_frame.grid(row=1, column=0, padx=5, pady=5) - self.picker_frame = Frame(self) - self.picker_frame.grid(row=1, column=1) - - self.new = Button(self.button_frame) - self.new["text"] = "New" - self.new["command"] = self.new_map - self.new.grid(row=0, column=0, padx=2) - - self.open = Button(self.button_frame) - self.open["text"] = "Open" - self.open["command"] = self.open_map - self.open.grid(row=0, column=1, padx=2) - - self.save = Button(self.button_frame) - self.save["text"] = "Save" - self.save["command"] = self.save_map - self.save.grid(row=0, column=2, padx=2) - - self.get_map_list() - self.map_list.grid(row=0, column=3, padx=2) - - - def get_map_list(self): - self.available_maps = sorted(m for m in get_available_maps()) - self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) - if len(self.available_maps): - self.map_list.set(self.available_maps[0]) - - def new_map(self): - self.map_name = None - self.init_map() - self.map.blockdata = [self.paint_tile] * 20 * 20 - self.map.width = 20 - self.map.height = 20 - self.draw_map() - self.init_picker() - - def open_map(self): - self.map_name = self.map_list.get() - self.init_map() - self.draw_map() - self.init_picker() - - def save_map(self): - if hasattr(self, 'map'): - if self.map.blockdata_filename: - with open(self.map.blockdata_filename, 'wb') as save: - save.write(self.map.blockdata) - print 'blockdata saved as %s' % self.map.blockdata_filename - else: - print 'dunno how to save this' - else: - print 'nothing to save' - - def init_map(self): - if hasattr(self, 'map'): - self.map.kill_canvas() - self.map = Map(self.map_frame, self.map_name) - self.init_map_connections() - - def draw_map(self): - self.map.init_canvas(self.map_frame) - self.map.canvas.pack() #.grid(row=1,column=1) - self.map.draw() - self.map.canvas.bind('', self.paint) - self.map.canvas.bind('', self.paint) - - def init_picker(self): - - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.width = 1 - self.current_tile.height = 1 - self.current_tile.init_canvas() - self.current_tile.draw() - self.current_tile.canvas.grid(row=0, column=4, padx=4) - - if hasattr(self, 'picker'): - self.picker.kill_canvas() - self.picker = Map(self, tileset_id=self.map.tileset_id) - self.picker.blockdata = range(len(self.picker.tileset.blocks)) - self.picker.width = 4 - self.picker.height = len(self.picker.blockdata) / self.picker.width - self.picker.init_canvas(self.picker_frame) - - if hasattr(self.picker_frame, 'vbar'): - self.picker_frame.vbar.destroy() - self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) - self.picker_frame.vbar.pack(side=RIGHT, fill=Y) - self.picker_frame.vbar.config(command=self.picker.canvas.yview) - - - self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) - self.map_frame.update() - self.picker.canvas.config(height=self.map_frame.winfo_height()) - self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) - self.picker.canvas.pack(side=LEFT, expand=True) - - self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) - self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) - self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) - self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) - - self.picker.draw() - self.picker.canvas.bind('', self.pick_block) - - def scroll_picker(self, event): - if event.num == 4: - self.picker.canvas.yview('scroll', -1, 'units') - elif event.num == 5: - self.picker.canvas.yview('scroll', 1, 'units') - - - def pick_block(self, event): - block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) - block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) - i = block_y * self.picker.width + block_x - self.paint_tile = self.picker.blockdata[i] - - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.draw() - - def paint(self, event): - block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) - block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) - i = block_y * self.map.width + block_x - if 0 <= i < len(self.map.blockdata): - self.map.blockdata[i] = self.paint_tile - self.map.draw_block(block_x, block_y) - - def init_map_connections(self): - if not display_connections: - return - for direction in self.map.connections.keys(): - if direction in self.connections.keys(): - if hasattr(self.connections[direction], 'canvas'): - self.connections[direction].kill_canvas() - if self.map.connections[direction] == {}: - self.connections[direction] = {} - continue - self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) - - if direction in ['north', 'south']: - x1 = 0 - y1 = 0 - x2 = x1 + eval(self.map.connections[direction]['strip_length']) - y2 = y1 + 3 - else: # east, west - x1 = 0 - y1 = 0 - x2 = x1 + 3 - y2 = y1 + eval(self.map.connections[direction]['strip_length']) - - self.connections[direction].crop(x1, y1, x2, y2) - self.connections[direction].init_canvas(self.map_frame) - self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) - self.connections[direction].draw() + def __init__(self, master=None): + self.display_connections = False + Frame.__init__(self, master) + self.grid() + Style().configure("TFrame", background="#444") + self.paint_tile = 1 + self.init_ui() + + def init_ui(self): + self.connections = {} + self.button_frame = Frame(self) + self.button_frame.grid(row=0, column=0, columnspan=2) + self.map_frame = Frame(self) + self.map_frame.grid(row=1, column=0, padx=5, pady=5) + self.picker_frame = Frame(self) + self.picker_frame.grid(row=1, column=1) + + self.new = Button(self.button_frame) + self.new["text"] = "New" + self.new["command"] = self.new_map + self.new.grid(row=0, column=0, padx=2) + + self.open = Button(self.button_frame) + self.open["text"] = "Open" + self.open["command"] = self.open_map + self.open.grid(row=0, column=1, padx=2) + + self.save = Button(self.button_frame) + self.save["text"] = "Save" + self.save["command"] = self.save_map + self.save.grid(row=0, column=2, padx=2) + + self.get_map_list() + self.map_list.grid(row=0, column=3, padx=2) + + + def get_map_list(self): + self.available_maps = sorted(m for m in get_available_maps()) + self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) + if len(self.available_maps): + self.map_list.set(self.available_maps[0]) + + def new_map(self): + self.map_name = None + self.init_map() + self.map.blockdata = [self.paint_tile] * 20 * 20 + self.map.width = 20 + self.map.height = 20 + self.draw_map() + self.init_picker() + + def open_map(self): + self.map_name = self.map_list.get() + self.init_map() + self.draw_map() + self.init_picker() + + def save_map(self): + if hasattr(self, 'map'): + if self.map.blockdata_filename: + with open(self.map.blockdata_filename, 'wb') as save: + save.write(self.map.blockdata) + print 'blockdata saved as %s' % self.map.blockdata_filename + else: + print 'dunno how to save this' + else: + print 'nothing to save' + + def init_map(self): + if hasattr(self, 'map'): + self.map.kill_canvas() + self.map = Map(self.map_frame, self.map_name) + self.init_map_connections() + + def draw_map(self): + self.map.init_canvas(self.map_frame) + self.map.canvas.pack() #.grid(row=1,column=1) + self.map.draw() + self.map.canvas.bind('', self.paint) + self.map.canvas.bind('', self.paint) + + def init_picker(self): + + self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.width = 1 + self.current_tile.height = 1 + self.current_tile.init_canvas() + self.current_tile.draw() + self.current_tile.canvas.grid(row=0, column=4, padx=4) + + if hasattr(self, 'picker'): + self.picker.kill_canvas() + self.picker = Map(self, tileset_id=self.map.tileset_id) + self.picker.blockdata = range(len(self.picker.tileset.blocks)) + self.picker.width = 4 + self.picker.height = len(self.picker.blockdata) / self.picker.width + self.picker.init_canvas(self.picker_frame) + + if hasattr(self.picker_frame, 'vbar'): + self.picker_frame.vbar.destroy() + self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) + self.picker_frame.vbar.pack(side=RIGHT, fill=Y) + self.picker_frame.vbar.config(command=self.picker.canvas.yview) + + + self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) + self.map_frame.update() + self.picker.canvas.config(height=self.map_frame.winfo_height()) + self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) + self.picker.canvas.pack(side=LEFT, expand=True) + + self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) + + self.picker.draw() + self.picker.canvas.bind('', self.pick_block) + + def scroll_picker(self, event): + if event.num == 4: + self.picker.canvas.yview('scroll', -1, 'units') + elif event.num == 5: + self.picker.canvas.yview('scroll', 1, 'units') + + + def pick_block(self, event): + block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) + block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) + i = block_y * self.picker.width + block_x + self.paint_tile = self.picker.blockdata[i] + + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.draw() + + def paint(self, event): + block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) + block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) + i = block_y * self.map.width + block_x + if 0 <= i < len(self.map.blockdata): + self.map.blockdata[i] = self.paint_tile + self.map.draw_block(block_x, block_y) + + def init_map_connections(self): + if not display_connections: + return + for direction in self.map.connections.keys(): + if direction in self.connections.keys(): + if hasattr(self.connections[direction], 'canvas'): + self.connections[direction].kill_canvas() + if self.map.connections[direction] == {}: + self.connections[direction] = {} + continue + self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) + + if direction in ['north', 'south']: + x1 = 0 + y1 = 0 + x2 = x1 + eval(self.map.connections[direction]['strip_length']) + y2 = y1 + 3 + else: # east, west + x1 = 0 + y1 = 0 + x2 = x1 + 3 + y2 = y1 + eval(self.map.connections[direction]['strip_length']) + + self.connections[direction].crop(x1, y1, x2, y2) + self.connections[direction].init_canvas(self.map_frame) + self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) + self.connections[direction].draw() class Map: - def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): - self.parent = parent - - self.name = name - - self.blockdata_filename = blockdata_filename - if not self.blockdata_filename and self.name: - self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') - elif not self.blockdata_filename: - self.blockdata_filename = '' - - asm_filename = '' - if self.name: - if 'asm_dir' in globals().keys(): - asm_filename = os.path.join(asm_dir, self.name + '.asm') - elif 'asm_path' in globals().keys(): - asm_filename = asm_path - - if os.path.exists(asm_filename): - for props in [map_header(self.name), second_map_header(self.name)]: - self.__dict__.update(props) - self.asm = open(asm_filename, 'r').read() - self.events = event_header(self.asm, self.name) - self.scripts = script_header(self.asm, self.name) - - self.tileset_id = eval(self.tileset_id) - - self.width = eval(self.width) - self.height = eval(self.height) - - else: - self.width = width - self.height = height - self.tileset_id = tileset_id - - if self.blockdata_filename: - self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) - else: - self.blockdata = [] - - self.tileset = Tileset(self.tileset_id) - - def init_canvas(self, parent=None): - if parent == None: - parent = self.parent - if not hasattr(self, 'canvas'): - self.canvas_width = self.width * 32 - self.canvas_height = self.height * 32 - self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) - self.canvas.xview_moveto(0) - self.canvas.yview_moveto(0) - - def kill_canvas(self): - if hasattr(self, 'canvas'): - self.canvas.destroy() - - def crop(self, x1, y1, x2, y2): - blockdata = self.blockdata - start = y1 * self.width + x1 - width = x2 - x1 - height = y2 - y1 - self.blockdata = [] - for y in xrange(height): - for x in xrange(width): - self.blockdata += [blockdata[start + y * self.width + x]] - self.blockdata = bytearray(self.blockdata) - self.width = width - self.height = height - - def draw(self): - for i in xrange(len(self.blockdata)): - block_x = i % self.width - block_y = i / self.width - self.draw_block(block_x, block_y) - - def draw_block(self, block_x, block_y): - # the canvas starts at 4, 4 for some reason - # probably something to do with a border - index, indey = 4, 4 - - # Draw one block (4x4 tiles) - block = self.blockdata[block_y * self.width + block_x] - for j, tile in enumerate(self.tileset.blocks[block]): - try: - # Tile gfx are split in half to make vram mapping easier - if tile >= 0x80: - tile -= 0x20 - tile_x = block_x * 32 + (j % 4) * 8 - tile_y = block_y * 32 + (j / 4) * 8 - self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) - except: - pass + def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): + self.parent = parent + + self.name = name + + self.blockdata_filename = blockdata_filename + if not self.blockdata_filename and self.name: + self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') + elif not self.blockdata_filename: + self.blockdata_filename = '' + + asm_filename = '' + if self.name: + if 'asm_dir' in globals().keys(): + asm_filename = os.path.join(asm_dir, self.name + '.asm') + elif 'asm_path' in globals().keys(): + asm_filename = asm_path + + if os.path.exists(asm_filename): + for props in [map_header(self.name), second_map_header(self.name)]: + self.__dict__.update(props) + self.asm = open(asm_filename, 'r').read() + self.events = event_header(self.asm, self.name) + self.scripts = script_header(self.asm, self.name) + + self.tileset_id = eval(self.tileset_id) + + self.width = eval(self.width) + self.height = eval(self.height) + + else: + self.width = width + self.height = height + self.tileset_id = tileset_id + + if self.blockdata_filename: + self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) + else: + self.blockdata = [] + + self.tileset = Tileset(self.tileset_id) + + def init_canvas(self, parent=None): + if parent == None: + parent = self.parent + if not hasattr(self, 'canvas'): + self.canvas_width = self.width * 32 + self.canvas_height = self.height * 32 + self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) + self.canvas.xview_moveto(0) + self.canvas.yview_moveto(0) + + def kill_canvas(self): + if hasattr(self, 'canvas'): + self.canvas.destroy() + + def crop(self, x1, y1, x2, y2): + blockdata = self.blockdata + start = y1 * self.width + x1 + width = x2 - x1 + height = y2 - y1 + self.blockdata = [] + for y in xrange(height): + for x in xrange(width): + self.blockdata += [blockdata[start + y * self.width + x]] + self.blockdata = bytearray(self.blockdata) + self.width = width + self.height = height + + def draw(self): + for i in xrange(len(self.blockdata)): + block_x = i % self.width + block_y = i / self.width + self.draw_block(block_x, block_y) + + def draw_block(self, block_x, block_y): + # the canvas starts at 4, 4 for some reason + # probably something to do with a border + index, indey = 4, 4 + + # Draw one block (4x4 tiles) + block = self.blockdata[block_y * self.width + block_x] + for j, tile in enumerate(self.tileset.blocks[block]): + try: + # Tile gfx are split in half to make vram mapping easier + if tile >= 0x80: + tile -= 0x20 + tile_x = block_x * 32 + (j % 4) * 8 + tile_y = block_y * 32 + (j / 4) * 8 + self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + except: + pass class Tileset: - def __init__(self, tileset_id=0): - self.id = tileset_id - - self.tile_width = 8 - self.tile_height = 8 - self.block_width = 4 - self.block_height = 4 - - self.alpha = 255 - - if palettes_on: - self.get_palettes() - self.get_palette_map() - - self.get_blocks() - self.get_tiles() - - def get_tileset_gfx_filename(self): - filename = None - - if version == 'red': - tileset_defs = open(os.path.join(conf.path, 'main.asm'), 'r').read() - incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id) - print incbin - filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png') - filename = os.path.join(conf.path, filename) - print filename - - if not filename: - filename = os.path.join( - gfx_dir, - to_gfx_name(self.id) + '.png' - ) - - return filename - - def get_tiles(self): - filename = self.get_tileset_gfx_filename() - if not os.path.exists(filename): - import gfx - gfx.to_png(filename.replace('.png','.2bpp'), filename) - self.img = Image.open(filename) - self.img.width, self.img.height = self.img.size - self.tiles = [] - cur_tile = 0 - for y in xrange(0, self.img.height, self.tile_height): - for x in xrange(0, self.img.width, self.tile_width): - tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) - - if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): - # Palette maps are padded to make vram mapping easier. - pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 - tile = self.colorize_tile(tile, self.palettes[pal]) - - self.tiles += [ImageTk.PhotoImage(tile)] - cur_tile += 1 - - def colorize_tile(self, tile, palette): - width, height = tile.size - tile = tile.convert("RGB") - px = tile.load() - for y in xrange(height): - for x in xrange(width): - # assume greyscale - which_color = 3 - (px[x, y][0] / 0x55) - r, g, b = [v * 8 for v in palette[which_color]] - px[x, y] = (r, g, b) - return tile - - def get_blocks(self): - filename = os.path.join( - block_dir, - to_gfx_name(self.id) + block_ext - ) - self.blocks = [] - block_length = self.block_width * self.block_height - blocks = bytearray(open(filename, 'rb').read()) - for block in xrange(len(blocks) / (block_length)): - i = block * block_length - self.blocks += [blocks[i : i + block_length]] - - def get_palette_map(self): - filename = os.path.join( - palmap_dir, - str(self.id).zfill(2) + '_palette_map.bin' - ) - self.palette_map = [] - palmap = bytearray(open(filename, 'rb').read()) - for i in xrange(len(palmap)): - self.palette_map += [palmap[i] & 0xf] - self.palette_map += [(palmap[i] >> 4) & 0xf] - - def get_palettes(self): - filename = os.path.join( - palette_dir, - ['morn', 'day', 'nite'][time_of_day] + '.pal' - ) - self.palettes = get_palettes(filename) + def __init__(self, tileset_id=0): + self.id = tileset_id + + self.tile_width = 8 + self.tile_height = 8 + self.block_width = 4 + self.block_height = 4 + + self.alpha = 255 + + if palettes_on: + self.get_palettes() + self.get_palette_map() + + self.get_blocks() + self.get_tiles() + + def get_tileset_gfx_filename(self): + filename = None + + if version == 'red': + tileset_defs = open(os.path.join(conf.path, 'main.asm'), 'r').read() + incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id) + print incbin + filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png') + filename = os.path.join(conf.path, filename) + print filename + + if not filename: + filename = os.path.join( + gfx_dir, + to_gfx_name(self.id) + '.png' + ) + + return filename + + def get_tiles(self): + filename = self.get_tileset_gfx_filename() + if not os.path.exists(filename): + import gfx + gfx.to_png(filename.replace('.png','.2bpp'), filename) + self.img = Image.open(filename) + self.img.width, self.img.height = self.img.size + self.tiles = [] + cur_tile = 0 + for y in xrange(0, self.img.height, self.tile_height): + for x in xrange(0, self.img.width, self.tile_width): + tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) + + if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): + # Palette maps are padded to make vram mapping easier. + pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 + tile = self.colorize_tile(tile, self.palettes[pal]) + + self.tiles += [ImageTk.PhotoImage(tile)] + cur_tile += 1 + + def colorize_tile(self, tile, palette): + width, height = tile.size + tile = tile.convert("RGB") + px = tile.load() + for y in xrange(height): + for x in xrange(width): + # assume greyscale + which_color = 3 - (px[x, y][0] / 0x55) + r, g, b = [v * 8 for v in palette[which_color]] + px[x, y] = (r, g, b) + return tile + + def get_blocks(self): + filename = os.path.join( + block_dir, + to_gfx_name(self.id) + block_ext + ) + self.blocks = [] + block_length = self.block_width * self.block_height + blocks = bytearray(open(filename, 'rb').read()) + for block in xrange(len(blocks) / (block_length)): + i = block * block_length + self.blocks += [blocks[i : i + block_length]] + + def get_palette_map(self): + filename = os.path.join( + palmap_dir, + str(self.id).zfill(2) + '_palette_map.bin' + ) + self.palette_map = [] + palmap = bytearray(open(filename, 'rb').read()) + for i in xrange(len(palmap)): + self.palette_map += [palmap[i] & 0xf] + self.palette_map += [(palmap[i] >> 4) & 0xf] + + def get_palettes(self): + filename = os.path.join( + palette_dir, + ['morn', 'day', 'nite'][time_of_day] + '.pal' + ) + self.palettes = get_palettes(filename) time_of_day = 1 def get_palettes(filename): - pals = bytearray(open(filename, 'rb').read()) + pals = bytearray(open(filename, 'rb').read()) - num_colors = 4 - color_length = 2 + num_colors = 4 + color_length = 2 - palette_length = num_colors * color_length + palette_length = num_colors * color_length - num_pals = len(pals) / palette_length + num_pals = len(pals) / palette_length - palettes = [] - for pal in xrange(num_pals): - palettes += [[]] + palettes = [] + for pal in xrange(num_pals): + palettes += [[]] - for color in xrange(num_colors): - i = pal * palette_length - i += color * color_length - word = pals[i] + pals[i+1] * 0x100 - palettes[pal] += [[ - c & 0x1f for c in [ - word >> 0, - word >> 5, - word >> 10, - ] - ]] - return palettes + for color in xrange(num_colors): + i = pal * palette_length + i += color * color_length + word = pals[i] + pals[i+1] * 0x100 + palettes[pal] += [[ + c & 0x1f for c in [ + word >> 0, + word >> 5, + word >> 10, + ] + ]] + return palettes def get_available_maps(): - for root, dirs, files in os.walk(map_dir): - for filename in files: - base_name, ext = os.path.splitext(filename) - if ext == '.blk': - yield base_name + for root, dirs, files in os.walk(map_dir): + for filename in files: + base_name, ext = os.path.splitext(filename) + if ext == '.blk': + yield base_name def map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() - label = name + '_MapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'db', 'dw', 'db', 'db', 'db', 'db' ] - attributes = [ - 'bank', - 'tileset_id', - 'permission', - 'second_map_header', - 'world_map_location', - 'music', - 'time_of_day', - 'fishing_group', - ] - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - return attrs - - elif version == 'red': - headers = open(header_path, 'r').read() - - # there has to be a better way to do this - lower_label = name + '_h' - i = headers.lower().find(lower_label) - if i == -1: - return {} - label = headers[i:i+len(lower_label)] - - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'db', 'dw', 'dw', 'dw', 'db' ] - attributes = [ - 'tileset_id', - 'height', - 'width', - 'blockdata_label', - 'text_label', - 'script_label', - 'which_connections', - ] - values, l = read_header_macros(header, attributes, macros) - - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) - - macros = [ 'dw' ] - attributes = [ - 'object_label', - ] - values, l = read_header_macros(header[l:], attributes, macros) - attrs.update(dict(zip(attributes, values))) - - return attrs - - return {} + if version == 'crystal': + headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() + label = name + '_MapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'db', 'dw', 'db', 'db', 'db', 'db' ] + attributes = [ + 'bank', + 'tileset_id', + 'permission', + 'second_map_header', + 'world_map_location', + 'music', + 'time_of_day', + 'fishing_group', + ] + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + return attrs + + elif version == 'red': + headers = open(header_path, 'r').read() + + # there has to be a better way to do this + lower_label = name + '_h' + i = headers.lower().find(lower_label) + if i == -1: + return {} + label = headers[i:i+len(lower_label)] + + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'db', 'dw', 'dw', 'dw', 'db' ] + attributes = [ + 'tileset_id', + 'height', + 'width', + 'blockdata_label', + 'text_label', + 'script_label', + 'which_connections', + ] + values, l = read_header_macros(header, attributes, macros) + + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + + macros = [ 'dw' ] + attributes = [ + 'object_label', + ] + values, l = read_header_macros(header[l:], attributes, macros) + attrs.update(dict(zip(attributes, values))) + + return attrs + + return {} def second_map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() - label = name + '_SecondMapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'db', 'db', 'dw', 'db', 'dw', 'dw', 'db' ] - attributes = [ - 'border_block', - 'height', - 'width', - 'blockdata_bank', - 'blockdata_label', - 'script_header_bank', - 'script_header_label', - 'map_event_header_label', - 'which_connections', - ] - - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) - return attrs - - return {} + if version == 'crystal': + headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() + label = name + '_SecondMapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'db', 'db', 'dw', 'db', 'dw', 'dw', 'db' ] + attributes = [ + 'border_block', + 'height', + 'width', + 'blockdata_bank', + 'blockdata_label', + 'script_header_bank', + 'script_header_label', + 'map_event_header_label', + 'which_connections', + ] + + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + return attrs + + return {} def connections(which_connections, header, l=0): - directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } - - if version == 'crystal': - macros = [ 'db', 'db' ] - attributes = [ - 'map_group', - 'map_no', - ] - - elif version == 'red': - macros = [ 'db' ] - attributes = [ - 'map_id', - ] - - macros += [ 'dw', 'dw', 'db', 'db', 'db', 'db', 'dw' ] - attributes += [ - 'strip_pointer', - 'strip_destination', - 'strip_length', - 'map_width', - 'y_offset', - 'x_offset', - 'window', - ] - for d in directions.keys(): - if d.upper() in which_connections: - values, l = read_header_macros(header, attributes, macros) - header = header[l:] - directions[d] = dict(zip(attributes, values)) - if version == 'crystal': - directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') - elif version == 'red': - directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') - return directions, l + directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } + + if version == 'crystal': + macros = [ 'db', 'db' ] + attributes = [ + 'map_group', + 'map_no', + ] + + elif version == 'red': + macros = [ 'db' ] + attributes = [ + 'map_id', + ] + + macros += [ 'dw', 'dw', 'db', 'db', 'db', 'db', 'dw' ] + attributes += [ + 'strip_pointer', + 'strip_destination', + 'strip_length', + 'map_width', + 'y_offset', + 'x_offset', + 'window', + ] + for d in directions.keys(): + if d.upper() in which_connections: + values, l = read_header_macros(header, attributes, macros) + header = header[l:] + directions[d] = dict(zip(attributes, values)) + if version == 'crystal': + directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') + elif version == 'red': + directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') + return directions, l def read_header_macros(header, attributes, macros): - values = [] - i = 0 - l = 0 - for l, (asm, comment) in enumerate(header): - if asm.strip() != '': - mvalues = macro_values(asm, macros[i]) - values += mvalues - i += len(mvalues) - if len(values) >= len(attributes): - l += 1 - break - return values, l + values = [] + i = 0 + l = 0 + for l, (asm, comment) in enumerate(header): + if asm.strip() != '': + mvalues = macro_values(asm, macros[i]) + values += mvalues + i += len(mvalues) + if len(values) >= len(attributes): + l += 1 + break + return values, l def event_header(asm, name): - return {} + return {} def script_header(asm, name): - return {} + return {} def macro_values(line, macro): - values = line[line.find(macro) + len(macro):].split(',') - values = [v.replace('$','0x').strip() for v in values] - if values[0] == 'w': # dbw - values = values[1:] - return values + values = line[line.find(macro) + len(macro):].split(',') + values = [v.replace('$','0x').strip() for v in values] + if values[0] == 'w': # dbw + values = values[1:] + return values def db_value(line): - macro = 'db' - return macro_values(line, macro) + macro = 'db' + return macro_values(line, macro) def db_values(line): - macro = 'db' - return macro_values(line, macro) + macro = 'db' + return macro_values(line, macro) from preprocessor import separate_comment def asm_at_label(asm, label): - label_def = label + ':' - lines = asm.split('\n') - for line in lines: - if line.startswith(label_def): - lines = lines[lines.index(line):] - lines[0] = lines[0][len(label_def):] - break - # go until the next label - content = [] - for line in lines: - l, comment = separate_comment(line + '\n') - if ':' in l: - break - content += [[l, comment]] - return content + label_def = label + ':' + lines = asm.split('\n') + for line in lines: + if line.startswith(label_def): + lines = lines[lines.index(line):] + lines[0] = lines[0][len(label_def):] + break + # go until the next label + content = [] + for line in lines: + l, comment = separate_comment(line + '\n') + if ':' in l: + break + content += [[l, comment]] + return content def main(): """ -- cgit v1.2.3 From 2d59e4d3e07d3fe92c6463ce212a2c537f62160b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 17:39:02 -0600 Subject: move import statements to top of map_editor.py --- pokemontools/map_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 362a67f..52da243 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -9,6 +9,8 @@ from PIL import Image, ImageTk import configuration conf = configuration.Config() +from preprocessor import separate_comment +import gfx #version = 'crystal' version = 'red' @@ -368,7 +370,6 @@ class Tileset: def get_tiles(self): filename = self.get_tileset_gfx_filename() if not os.path.exists(filename): - import gfx gfx.to_png(filename.replace('.png','.2bpp'), filename) self.img = Image.open(filename) self.img.width, self.img.height = self.img.size @@ -626,7 +627,6 @@ def db_values(line): return macro_values(line, macro) -from preprocessor import separate_comment def asm_at_label(asm, label): label_def = label + ':' -- cgit v1.2.3 From 76d47a68b345695d676879c723c0a833bf2c4396 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 18:30:50 -0600 Subject: get PIL from pypi to run map editor --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index ab6f202..edaedaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ -e git://github.com/drj11/pypng.git@master#egg=pypng +# for the map editor +PIL + # testing mock -- cgit v1.2.3 From 8ebf9b05b5ed53cd8f635dd86dbfea8bf736fd10 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 18:34:57 -0600 Subject: make ConfigException accessible Apparently the "exceptions" name is already used for another module. --- pokemontools/configuration.py | 9 ++++++--- pokemontools/exceptions.py | 5 ----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pokemontools/configuration.py b/pokemontools/configuration.py index cbf230c..1592fe6 100644 --- a/pokemontools/configuration.py +++ b/pokemontools/configuration.py @@ -4,7 +4,10 @@ Configuration import os -import exceptions +class ConfigException(Exception): + """ + Configuration error. Maybe a missing config variable. + """ class Config(object): """ @@ -23,7 +26,7 @@ class Config(object): if key not in self.__dict__: self._config[key] = value else: - raise exceptions.ConfigException( + raise ConfigException( "Can't store \"{0}\" in configuration because the key conflicts with an existing property." .format(key) ) @@ -49,6 +52,6 @@ class Config(object): elif key in self._config: return self._config[key] else: - raise exceptions.ConfigException( + raise ConfigException( "no config found for \"{0}\"".format(key) ) diff --git a/pokemontools/exceptions.py b/pokemontools/exceptions.py index 4de62eb..e583b17 100644 --- a/pokemontools/exceptions.py +++ b/pokemontools/exceptions.py @@ -12,11 +12,6 @@ class TextScriptException(Exception): TextScript encountered an inconsistency or problem. """ -class ConfigException(Exception): - """ - Configuration error. Maybe a missing config variable. - """ - class PreprocessorException(Exception): """ There was a problem in the preprocessor. -- cgit v1.2.3 From 0dac29a91d52ca490848864b6b6763c809f8a954 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 18:36:01 -0600 Subject: change RomStr usage in gfx.py to use RomStr.load --- pokemontools/gfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index d830259..051d927 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -13,7 +13,7 @@ import trainers import romstr if __name__ != "__main__": - rom = romstr.RomStr(filename=config.rom_path) + rom = romstr.RomStr.load(filename=config.rom_path) def hex_dump(input, debug=True): """ -- cgit v1.2.3 From 6920881598e61eb369a2fdb78922199f637f6ef3 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 18:56:31 -0600 Subject: factor out global configs in map_editor.py --- pokemontools/map_editor.py | 195 +++++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 78 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 52da243..cb7d244 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -7,62 +7,96 @@ import PIL from PIL import Image, ImageTk import configuration -conf = configuration.Config() +config = configuration.Config() from preprocessor import separate_comment import gfx -#version = 'crystal' -version = 'red' +def configure_for_pokered(config=config): + """ + Sets default configuration values for pokered. These should eventually be + moved into the configuration module. + """ + attrs = { + "version": "red", + + "map_dir": os.path.join(config.path, 'maps/'), + "gfx_dir": os.path.join(config.path, 'gfx/tilesets/'), + "to_gfx_name": lambda x : '%.2x' % x, + "block_dir": os.path.join(config.path, 'gfx/blocksets/'), + "block_ext": '.bst', + + "palettes_on": False, -if version == 'crystal': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2d' % x - block_dir = os.path.join(conf.path, 'tilesets/') - block_ext = '_metatiles.bin' + "asm_path": os.path.join(config.path, 'main.asm'), - palettes_on = True - palmap_dir = os.path.join(conf.path, 'tilesets/') - palette_dir = os.path.join(conf.path, 'tilesets/') + "constants_filename": os.path.join(config.path, 'constants.asm'), - asm_dir = os.path.join(conf.path, 'maps/') + "header_path": os.path.join(config.path, 'main.asm'), + + "time_of_day": 1, + } + return attrs + +def configure_for_pokecrystal(config=config): + """ + Sets default configuration values for pokecrystal. These should eventually + be moved into the configuration module. + """ + attrs = { + "version": "crystal", - constants_dir = os.path.join(conf.path, 'constants/') - constants_filename = os.path.join(constants_dir, 'map_constants.asm') + "map_dir": os.path.join(config.path, 'maps/'), + "gfx_dir": os.path.join(config.path, 'gfx/tilesets/'), + "to_gfx_name": lambda x : '%.2d' % x, + "block_dir": os.path.join(config.path, 'tilesets/'), + "block_ext": '_metatiles.bin', - header_dir = os.path.join(conf.path, 'maps/') + "palettes_on": True, + "palmap_dir": os.path.join(config.path, 'tilesets/'), + "palette_dir": os.path.join(config.path, 'tilesets/'), -elif version == 'red': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2x' % x - block_dir = os.path.join(conf.path, 'gfx/blocksets/') - block_ext = '.bst' + "asm_dir": os.path.join(config.path, 'maps/'), - palettes_on = False + "constants_filename": os.path.join(os.path.join(config.path, "constants/"), 'map_constants.asm'), - asm_path = os.path.join(conf.path, 'main.asm') + "header_dir": os.path.join(config.path, 'maps/'), - constants_filename = os.path.join(conf.path, 'constants.asm') + "time_of_day": 1, + } + return attrs - header_path = os.path.join(conf.path, 'main.asm') +def configure_for_version(version, config=config): + """ + Overrides default values from the configuration with additional attributes. + """ + if version == "red": + attrs = configure_for_pokered(config) + elif version == "crystal": + attrs = configure_for_pokecrystal(config) + else: + # TODO: pick a better exception + raise Exception( + "Can't configure for this version." + ) -else: - raise Exception, 'version must be "crystal" or "red"' + for (key, value) in attrs.iteritems(): + setattr(config, key, value) + # not really needed since it's modifying the same object + return config -def get_constants(): - lines = open(constants_filename, 'r').readlines() +def get_constants(config=config): + lines = open(config.constants_filename, 'r').readlines() for line in lines: if ' EQU ' in line: name, value = [s.strip() for s in line.split(' EQU ')] globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) -get_constants() class Application(Frame): - def __init__(self, master=None): + def __init__(self, master=None, config=config): + self.config = config self.display_connections = False Frame.__init__(self, master) self.grid() @@ -99,7 +133,7 @@ class Application(Frame): def get_map_list(self): - self.available_maps = sorted(m for m in get_available_maps()) + self.available_maps = sorted(m for m in get_available_maps(config=self.config)) self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) if len(self.available_maps): self.map_list.set(self.available_maps[0]) @@ -133,7 +167,7 @@ class Application(Frame): def init_map(self): if hasattr(self, 'map'): self.map.kill_canvas() - self.map = Map(self.map_frame, self.map_name) + self.map = Map(self.map_frame, self.map_name, config=self.config) self.init_map_connections() def draw_map(self): @@ -145,7 +179,7 @@ class Application(Frame): def init_picker(self): - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) + self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id, config=self.config) self.current_tile.blockdata = [self.paint_tile] self.current_tile.width = 1 self.current_tile.height = 1 @@ -155,7 +189,7 @@ class Application(Frame): if hasattr(self, 'picker'): self.picker.kill_canvas() - self.picker = Map(self, tileset_id=self.map.tileset_id) + self.picker = Map(self, tileset_id=self.map.tileset_id, config=self.config) self.picker.blockdata = range(len(self.picker.tileset.blocks)) self.picker.width = 4 self.picker.height = len(self.picker.blockdata) / self.picker.width @@ -207,7 +241,7 @@ class Application(Frame): self.map.draw_block(block_x, block_y) def init_map_connections(self): - if not display_connections: + if not self.display_connections: return for direction in self.map.connections.keys(): if direction in self.connections.keys(): @@ -216,7 +250,7 @@ class Application(Frame): if self.map.connections[direction] == {}: self.connections[direction] = {} continue - self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) + self.connections[direction] = Map(self, self.map.connections[direction]['map_name'], config=self.config) if direction in ['north', 'south']: x1 = 0 @@ -236,26 +270,28 @@ class Application(Frame): class Map: - def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): + def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None, config=config): self.parent = parent self.name = name + self.config = config + self.blockdata_filename = blockdata_filename if not self.blockdata_filename and self.name: - self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') + self.blockdata_filename = os.path.join(self.config.map_dir, self.name + '.blk') elif not self.blockdata_filename: self.blockdata_filename = '' asm_filename = '' if self.name: - if 'asm_dir' in globals().keys(): - asm_filename = os.path.join(asm_dir, self.name + '.asm') - elif 'asm_path' in globals().keys(): - asm_filename = asm_path + if self.config.asm_dir is not None: + asm_filename = os.path.join(self.config.asm_dir, self.name + '.asm') + elif self.config.asm_path is not None: + asm_filename = self.config.asm_path if os.path.exists(asm_filename): - for props in [map_header(self.name), second_map_header(self.name)]: + for props in [map_header(self.name, config=self.config), second_map_header(self.name, config=self.config)]: self.__dict__.update(props) self.asm = open(asm_filename, 'r').read() self.events = event_header(self.asm, self.name) @@ -276,7 +312,7 @@ class Map: else: self.blockdata = [] - self.tileset = Tileset(self.tileset_id) + self.tileset = Tileset(self.tileset_id, config=self.config) def init_canvas(self, parent=None): if parent == None: @@ -331,7 +367,9 @@ class Map: class Tileset: - def __init__(self, tileset_id=0): + def __init__(self, tileset_id=0, config=config): + self.config = config + self.id = tileset_id self.tile_width = 8 @@ -341,7 +379,7 @@ class Tileset: self.alpha = 255 - if palettes_on: + if self.config.palettes_on: self.get_palettes() self.get_palette_map() @@ -351,18 +389,18 @@ class Tileset: def get_tileset_gfx_filename(self): filename = None - if version == 'red': - tileset_defs = open(os.path.join(conf.path, 'main.asm'), 'r').read() + if self.config.version == 'red': + tileset_defs = open(os.path.join(self.config.path, 'main.asm'), 'r').read() incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id) print incbin filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png') - filename = os.path.join(conf.path, filename) + filename = os.path.join(self.config.path, filename) print filename if not filename: filename = os.path.join( - gfx_dir, - to_gfx_name(self.id) + '.png' + self.config.gfx_dir, + self.config.to_gfx_name(self.id) + '.png' ) return filename @@ -401,8 +439,8 @@ class Tileset: def get_blocks(self): filename = os.path.join( - block_dir, - to_gfx_name(self.id) + block_ext + self.config.block_dir, + self.config.to_gfx_name(self.id) + self.config.block_ext ) self.blocks = [] block_length = self.block_width * self.block_height @@ -413,7 +451,7 @@ class Tileset: def get_palette_map(self): filename = os.path.join( - palmap_dir, + self.config.palmap_dir, str(self.id).zfill(2) + '_palette_map.bin' ) self.palette_map = [] @@ -424,13 +462,12 @@ class Tileset: def get_palettes(self): filename = os.path.join( - palette_dir, - ['morn', 'day', 'nite'][time_of_day] + '.pal' + self.config.palette_dir, + ['morn', 'day', 'nite'][self.config.time_of_day] + '.pal' ) self.palettes = get_palettes(filename) -time_of_day = 1 def get_palettes(filename): @@ -462,17 +499,17 @@ def get_palettes(filename): -def get_available_maps(): - for root, dirs, files in os.walk(map_dir): +def get_available_maps(config=config): + for root, dirs, files in os.walk(config.map_dir): for filename in files: base_name, ext = os.path.splitext(filename) if ext == '.blk': yield base_name -def map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() +def map_header(name, config=config): + if config.version == 'crystal': + headers = open(os.path.join(config.header_dir, 'map_headers.asm'), 'r').read() label = name + '_MapHeader' header = asm_at_label(headers, label) macros = [ 'db', 'db', 'db', 'dw', 'db', 'db', 'db', 'db' ] @@ -490,8 +527,8 @@ def map_header(name): attrs = dict(zip(attributes, values)) return attrs - elif version == 'red': - headers = open(header_path, 'r').read() + elif config.version == 'red': + headers = open(config.header_path, 'r').read() # there has to be a better way to do this lower_label = name + '_h' @@ -514,7 +551,7 @@ def map_header(name): values, l = read_header_macros(header, attributes, macros) attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) + attrs['connections'], l = connections(attrs['which_connections'], header, l, config=config) macros = [ 'dw' ] attributes = [ @@ -527,9 +564,9 @@ def map_header(name): return {} -def second_map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() +def second_map_header(name, config=config): + if config.version == 'crystal': + headers = open(os.path.join(config.header_dir, 'second_map_headers.asm'), 'r').read() label = name + '_SecondMapHeader' header = asm_at_label(headers, label) macros = [ 'db', 'db', 'db', 'db', 'dw', 'db', 'dw', 'dw', 'db' ] @@ -552,17 +589,17 @@ def second_map_header(name): return {} -def connections(which_connections, header, l=0): +def connections(which_connections, header, l=0, config=config): directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } - if version == 'crystal': + if config.version == 'crystal': macros = [ 'db', 'db' ] attributes = [ 'map_group', 'map_no', ] - elif version == 'red': + elif config.version == 'red': macros = [ 'db' ] attributes = [ 'map_id', @@ -583,9 +620,9 @@ def connections(which_connections, header, l=0): values, l = read_header_macros(header, attributes, macros) header = header[l:] directions[d] = dict(zip(attributes, values)) - if version == 'crystal': + if config.version == 'crystal': directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') - elif version == 'red': + elif config.version == 'red': directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') return directions, l @@ -645,13 +682,13 @@ def asm_at_label(asm, label): content += [[l, comment]] return content -def main(): +def main(config=config): """ Launches the map editor. """ root = Tk() root.wm_title("MAP EDITOR") - app = Application(master=root) + app = Application(master=root, config=config) try: app.mainloop() @@ -664,4 +701,6 @@ def main(): pass if __name__ == "__main__": - main() + config = configure_for_version("crystal", config) + get_constants(config=config) + main(config=config) -- cgit v1.2.3 From f414cf38f6377d6956308cd78506ed21839bb7ef Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:05:09 -0600 Subject: only set global constants for eval Although I am not sure that using eval is a good idea in the first place... it should go away. --- pokemontools/map_editor.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index cb7d244..cd6e557 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -87,12 +87,13 @@ def configure_for_version(version, config=config): return config def get_constants(config=config): + constants = {} lines = open(config.constants_filename, 'r').readlines() for line in lines: if ' EQU ' in line: name, value = [s.strip() for s in line.split(' EQU ')] - globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) - + constants[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) + config.constants = constants class Application(Frame): def __init__(self, master=None, config=config): @@ -255,13 +256,13 @@ class Application(Frame): if direction in ['north', 'south']: x1 = 0 y1 = 0 - x2 = x1 + eval(self.map.connections[direction]['strip_length']) + x2 = x1 + eval(self.map.connections[direction]['strip_length'], self.config.constants) y2 = y1 + 3 else: # east, west x1 = 0 y1 = 0 x2 = x1 + 3 - y2 = y1 + eval(self.map.connections[direction]['strip_length']) + y2 = y1 + eval(self.map.connections[direction]['strip_length'], self.config.constants) self.connections[direction].crop(x1, y1, x2, y2) self.connections[direction].init_canvas(self.map_frame) @@ -297,10 +298,10 @@ class Map: self.events = event_header(self.asm, self.name) self.scripts = script_header(self.asm, self.name) - self.tileset_id = eval(self.tileset_id) + self.tileset_id = eval(self.tileset_id, self.config.constants) - self.width = eval(self.width) - self.height = eval(self.height) + self.width = eval(self.width, self.config.constants) + self.height = eval(self.height, self.config.constants) else: self.width = width -- cgit v1.2.3 From 98d335438a37772fef673d1f27f41b10e1bbd9cf Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:11:54 -0600 Subject: fix up map_editor.py extra newlines --- pokemontools/map_editor.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index cd6e557..44b2d98 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -179,7 +179,6 @@ class Application(Frame): self.map.canvas.bind('', self.paint) def init_picker(self): - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id, config=self.config) self.current_tile.blockdata = [self.paint_tile] self.current_tile.width = 1 @@ -202,7 +201,6 @@ class Application(Frame): self.picker_frame.vbar.pack(side=RIGHT, fill=Y) self.picker_frame.vbar.config(command=self.picker.canvas.yview) - self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) self.map_frame.update() self.picker.canvas.config(height=self.map_frame.winfo_height()) @@ -468,9 +466,6 @@ class Tileset: ) self.palettes = get_palettes(filename) - - - def get_palettes(filename): pals = bytearray(open(filename, 'rb').read()) @@ -498,8 +493,6 @@ def get_palettes(filename): ]] return palettes - - def get_available_maps(config=config): for root, dirs, files in os.walk(config.map_dir): for filename in files: @@ -507,7 +500,6 @@ def get_available_maps(config=config): if ext == '.blk': yield base_name - def map_header(name, config=config): if config.version == 'crystal': headers = open(os.path.join(config.header_dir, 'map_headers.asm'), 'r').read() @@ -641,14 +633,12 @@ def read_header_macros(header, attributes, macros): break return values, l - def event_header(asm, name): return {} def script_header(asm, name): return {} - def macro_values(line, macro): values = line[line.find(macro) + len(macro):].split(',') values = [v.replace('$','0x').strip() for v in values] @@ -664,8 +654,6 @@ def db_values(line): macro = 'db' return macro_values(line, macro) - - def asm_at_label(asm, label): label_def = label + ':' lines = asm.split('\n') -- cgit v1.2.3 From 712f4ce53bbf03807051736c4d08042ff46a0a3d Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:15:33 -0600 Subject: use explicit import of Tkinter components --- pokemontools/map_editor.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 44b2d98..77ecd85 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -1,6 +1,18 @@ import os -from Tkinter import * +from Tkinter import ( + Tk, + Button, + Canvas, + Scrollbar, + VERTICAL, + HORIZONTAL, + RIGHT, + LEFT, + Y, + X, +) + import ttk from ttk import Frame, Style import PIL -- cgit v1.2.3 From 49072b9917a72e9720860a7ee0ca28bda4c425e1 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:41:21 -0600 Subject: replace print with python logging --- pokemontools/map_editor.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 77ecd85..221be2b 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -1,4 +1,6 @@ import os +import sys +import logging from Tkinter import ( Tk, @@ -24,6 +26,18 @@ config = configuration.Config() from preprocessor import separate_comment import gfx +def setup_logging(): + """ + Temporary function that configures logging to go straight to console. + """ + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + console = logging.StreamHandler(sys.stdout) + console.setLevel(logging.DEBUG) + console.setFormatter(formatter) + root = logging.getLogger() + root.addHandler(console) + root.setLevel(logging.DEBUG) + def configure_for_pokered(config=config): """ Sets default configuration values for pokered. These should eventually be @@ -110,6 +124,7 @@ def get_constants(config=config): class Application(Frame): def __init__(self, master=None, config=config): self.config = config + self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) self.display_connections = False Frame.__init__(self, master) self.grid() @@ -171,11 +186,11 @@ class Application(Frame): if self.map.blockdata_filename: with open(self.map.blockdata_filename, 'wb') as save: save.write(self.map.blockdata) - print 'blockdata saved as %s' % self.map.blockdata_filename + self.log.info('blockdata saved as {}'.format(self.map.blockdata_filename)) else: - print 'dunno how to save this' + self.log.info('dunno how to save this') else: - print 'nothing to save' + self.log.info('nothing to save') def init_map(self): if hasattr(self, 'map'): @@ -287,6 +302,7 @@ class Map: self.name = name self.config = config + self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) self.blockdata_filename = blockdata_filename if not self.blockdata_filename and self.name: @@ -380,6 +396,7 @@ class Map: class Tileset: def __init__(self, tileset_id=0, config=config): self.config = config + self.log = logging.getLogger("{0}.{1}".format(self.__class__.__name__, id(self))) self.id = tileset_id @@ -403,10 +420,10 @@ class Tileset: if self.config.version == 'red': tileset_defs = open(os.path.join(self.config.path, 'main.asm'), 'r').read() incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id) - print incbin + self.log.debug(incbin) filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png') filename = os.path.join(self.config.path, filename) - print filename + self.log.debug(filename) if not filename: filename = os.path.join( @@ -702,6 +719,7 @@ def main(config=config): pass if __name__ == "__main__": + setup_logging() config = configure_for_version("crystal", config) get_constants(config=config) main(config=config) -- cgit v1.2.3 From 444d1ecfc721c2a472c7d2a81a8fb68d617a72c2 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:41:37 -0600 Subject: also import TclError into map_editor.py --- pokemontools/map_editor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 221be2b..1ea0fad 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -13,6 +13,7 @@ from Tkinter import ( LEFT, Y, X, + TclError, ) import ttk -- cgit v1.2.3 From 8dcde9ae280fb0664b464bb6e154c2f1f270431a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:44:49 -0600 Subject: use pillow instead of PIL in requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index edaedaa..c3d403f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -e git://github.com/drj11/pypng.git@master#egg=pypng -# for the map editor -PIL +# for the map editor, pillow instead of PIL +pillow # testing mock -- cgit v1.2.3 From 185a29698e5d3175cecbd779103e1a9838ff0ecc Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sun, 17 Nov 2013 19:51:04 -0600 Subject: clean up more imports in map_editor.py --- pokemontools/map_editor.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 1ea0fad..c30fcd8 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -16,16 +16,23 @@ from Tkinter import ( TclError, ) -import ttk -from ttk import Frame, Style -import PIL -from PIL import Image, ImageTk +from ttk import ( + Frame, + Style, + Combobox, +) -import configuration -config = configuration.Config() +# This is why requirements.txt says to install pillow instead of the original +# PIL. +from PIL import ( + Image, + ImageTk, +) -from preprocessor import separate_comment import gfx +import preprocessor +import configuration +config = configuration.Config() def setup_logging(): """ @@ -163,7 +170,7 @@ class Application(Frame): def get_map_list(self): self.available_maps = sorted(m for m in get_available_maps(config=self.config)) - self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) + self.map_list = Combobox(self.button_frame, height=24, width=24, values=self.available_maps) if len(self.available_maps): self.map_list.set(self.available_maps[0]) @@ -695,7 +702,7 @@ def asm_at_label(asm, label): # go until the next label content = [] for line in lines: - l, comment = separate_comment(line + '\n') + l, comment = preprocessor.separate_comment(line + '\n') if ':' in l: break content += [[l, comment]] -- cgit v1.2.3 From 2ffe762d173522095d08b2302bef2dd4769e16e7 Mon Sep 17 00:00:00 2001 From: yenatch Date: Sun, 17 Nov 2013 18:23:49 -0500 Subject: gfx: rewrite lots --- pokemontools/gfx.py | 303 ++++++++++++++++++++-------------------------------- 1 file changed, 116 insertions(+), 187 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 2bc613a..30a9fb1 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -12,114 +12,60 @@ import trainers if __name__ != "__main__": rom = crystal.load_rom() -def hex_dump(input, debug=True): + +def split(list_, interval): """ - Display hex dump in rows of 16 bytes. + Split a list by length. """ + for i in xrange(0, len(list_), interval): + j = min(i + interval, len(list_)) + yield list_[i:j] - dump = '' - output = '' - stream = '' - address = 0x00 - margin = 2 + len(hex(len(input))[2:]) - - # dump - for byte in input: - cool = hex(byte)[2:].zfill(2) - dump += cool + ' ' - if debug: stream += cool - - # convenient for testing quick edits in bgb - if debug: output += stream + '\n' - - # get dump info - bytes_per_line = 16 - chars_per_byte = 3 # '__ ' - chars_per_line = bytes_per_line * chars_per_byte - num_lines = int(ceil(float(len(dump)) / float(chars_per_line))) - - # top - # margin - for char in range(margin): - output += ' ' - - for byte in range(bytes_per_line): - output += hex(byte)[2:].zfill(2) + ' ' - output = output[:-1] # last space - - # print hex - for line in range(num_lines): - # address - output += '\n' + hex(address)[2:].zfill(margin - 2) + ': ' - # contents - start = line * chars_per_line - end = chars_per_line + start - 1 # ignore last space - output += dump[start:end] - address += 0x10 - return output +def hex_dump(data, length=0x10): + """ + just use hexdump -C + """ + margin = len('%x' % len(data)) + output = [] + address = 0 + for line in split(data, length): + output += [ + hex(address)[2:].zfill(margin) + + ' | ' + + ' '.join('%.2x' % byte for byte in line) + ] + address += length + return '\n'.join(output) def get_tiles(image): """ Split a 2bpp image into 8x8 tiles. """ - tiles = [] - tile = [] - bytes_per_tile = 16 - - cur_byte = 0 - for byte in image: - # build tile - tile.append(byte) - cur_byte += 1 - # done building? - if cur_byte >= bytes_per_tile: - # push completed tile - tiles.append(tile) - tile = [] - cur_byte = 0 - return tiles - + return list(split(image, 0x10)) def connect(tiles): """ Combine 8x8 tiles into a 2bpp image. """ - out = [] - for tile in tiles: - for byte in tile: - out.append(byte) - return out - + return [byte for tile in tiles for byte in tile] -def transpose(tiles): +def transpose(tiles, width=None): """ - Transpose a tile arrangement along line y=x. + Transpose a tile arrangement along line y=-x. + + 00 01 02 03 04 05 00 06 0c 12 18 1e + 06 07 08 09 0a 0b 01 07 0d 13 19 1f + 0c 0d 0e 0f 10 11 <-> 02 08 0e 14 1a 20 + 12 13 14 15 16 17 03 09 0f 15 1b 21 + 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22 + 1e 1f 20 21 22 23 05 0b 11 17 1d 23 """ - - # horizontal <-> vertical - # 00 01 02 03 04 05 00 06 0c 12 18 1e - # 06 07 08 09 0a 0b 01 07 0d 13 19 1f - # 0c 0d 0e 0f 10 11 <-> 02 08 0e 14 1a 20 - # 12 13 14 15 16 17 <-> 03 09 0f 15 1b 21 - # 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22 - # 1e 1f 20 21 22 23 05 0b 11 17 1d 23 - # etc - - flipped = [] - t = 0 # which tile we're on - w = int(sqrt(len(tiles))) # assume square image - for tile in tiles: - flipped.append(tiles[t]) - t += w - # end of row? - if t >= w*w: - # wrap around - t -= w*w - # next row - t += 1 - return flipped + if width == None: + width = int(sqrt(len(tiles))) # assume square image + tiles = sorted(enumerate(tiles), key= lambda (i, tile): i % width) + return [tile for i, tile in tiles] def to_file(filename, data): @@ -1171,13 +1117,16 @@ def flatten(planar): Flatten planar 2bpp image data into a quaternary pixel map. """ strips = [] - for pair in range(len(planar)/2): - bottom = ord(planar[(pair*2) ]) - top = ord(planar[(pair*2)+1]) - strip = [] - for i in range(7,-1,-1): - color = ((bottom >> i) & 1) + (((top >> i-1) if i > 0 else (top << 1-i)) & 2) - strip.append(color) + for bottom, top in split(planar, 2): + bottom = ord(bottom) + top = ord(top) + strip = [] + for i in xrange(7,-1,-1): + color = ( + (bottom >> i & 1) + + (top *2 >> i & 2) + ) + strip += [color] strips += strip return strips @@ -1186,47 +1135,52 @@ def to_lines(image, width): """ Convert a tiled quaternary pixel map to lines of quaternary pixels. """ - - tile = 8 * 8 - - # so we know how many strips of 8px we're putting into a line - num_columns = width / 8 - # number of lines + tile_width = 8 + tile_height = 8 + num_columns = width / tile_width height = len(image) / width lines = [] - for cur_line in range(height): - tile_row = int(cur_line / 8) + for cur_line in xrange(height): + tile_row = cur_line / tile_height line = [] - for column in range(num_columns): - anchor = num_columns*tile_row*tile + column*tile + (cur_line%8)*8 - line += image[anchor:anchor+8] - lines.append(line) + for column in xrange(num_columns): + anchor = ( + num_columns * tile_row * tile_width * tile_height + + column * tile_width * tile_height + + cur_line % tile_height * tile_width + ) + line += image[anchor : anchor + tile_width] + lines += [line] return lines + def dmg2rgb(word): - red = word & 0b11111 - word >>= 5 - green = word & 0b11111 - word >>= 5 - blue = word & 0b11111 + def shift(value): + while True: + yield value & (2**5 - 1) + value >>= 5 + word = shift(word) + # distribution is less even w/ << 3 + red, green, blue = [int(color * 8.25) for color in [word.next() for _ in xrange(3)]] alpha = 255 - return ((red<<3)+0b100, (green<<3)+0b100, (blue<<3)+0b100, alpha) + return (red, green, blue, alpha) + def rgb_to_dmg(color): word = (color['r'] / 8) - word += (color['g'] / 8) << 5 + word += (color['g'] / 8) << 5 word += (color['b'] / 8) << 10 return word def png_pal(filename): - palette = [] with open(filename, 'rb') as pal_data: words = pal_data.read() - dmg_pals = [] - for word in range(len(words)/2): - dmg_pals.append(ord(words[word*2]) + ord(words[word*2+1])*0x100) + dmg_pals = [] + for word in range(len(words)/2): + dmg_pals.append(ord(words[word*2]) + ord(words[word*2+1])*0x100) + palette = [] white = (255,255,255,255) black = (000,000,000,255) for word in dmg_pals: palette += [dmg2rgb(word)] @@ -1235,90 +1189,65 @@ def png_pal(filename): return palette -def to_png(filein, fileout=None, pal_file=None, height=None, width=None): - """ - Take a planar 2bpp graphics file and converts it to png. - """ - - if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.png' - +def export_2bpp_to_png(filein, fileout=None, pal_file=None, height=0, width=0): + if fileout == None: + fileout = os.path.splitext(filein)[0] + '.png' image = open(filein, 'rb').read() - num_pixels = len(image) * 4 + if pal_file == None: + if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): + pal_file = os.path.splitext(fileout)[0]+'.pal' - if num_pixels == 0: return 'empty image!' + width, height, palette, greyscale, bitdepth, px_map = convert_2bpp_to_png(image, width=width, height=height, pal_file=pal_file) + w = png.Writer(width, height, palette=palette, compression=9, greyscale=greyscale, bitdepth=bitdepth) + with open(fileout, 'wb') as f: + w.write(f, px_map) - # unless the pic is square, at least one dimension should be given - if width == None and height == None: - width = int(sqrt(num_pixels)) - height = width +def convert_2bpp_to_png(image, width=0, height=0, pal_file=None): + """ + Convert a planar 2bpp graphic to png. + """ + num_pixels = len(image) * 4 + assert num_pixels > 0, 'empty image!' - elif height == None: + # at least one dimension should be given + if height == 0 and width != 0: height = num_pixels / width - - elif width == None: - width = num_pixels / height - - - # but try to see if it can be made rectangular + elif width == 0 and height != 0: + width = num_pixels / height if width * height != num_pixels: - # look for possible combos of width/height that would form a rectangle matches = [] - - # this is pretty inefficient, and there is probably a simpler way - for width in range(8,256+1,8): # we only want dimensions that fit in tiles - height = num_pixels / width - if height % 8 == 0: - matches.append((width, height)) - + for w in range(8, num_pixels / 2 + 1, 8): + h = num_pixels / w + if w * h == num_pixels: + matches += [(w, h)] # go for the most square image - width, height = sorted(matches, key=lambda (x,y): x+y)[0] # favors height - - - # if it can't, the only option is a width of 1 tile + width, height = sorted(matches, key= lambda (w, h): w + h)[0] # favor height + # if it still isn't rectangular then the image isn't made of tiles if width * height != num_pixels: - width = 8 - height = num_pixels / width - - - # if this still isn't rectangular, then the image isn't made of tiles - - # for now we'll just spit out a warning - if width * height != num_pixels: - print 'Warning! ' + fileout + ' is ' + width + 'x' + height + '(' + width*height + ' pixels),\n' +\ - 'but ' + filein + ' is ' + num_pixels + ' pixels!' - - - # map it out + raise Exception, 'Image can\'t be divided into tiles (%d px)!' % (num_pixels) + # convert tiles to lines lines = to_lines(flatten(image), width) - if pal_file == None: - if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): - pal_file = os.path.splitext(fileout)[0]+'.pal' - if pal_file == None: palette = None greyscale = True bitdepth = 2 - inverse = { 0:3, 1:2, 2:1, 3:0 } - map = [[inverse[pixel] for pixel in line] for line in lines] + px_map = [[3 - pixel for pixel in line] for line in lines] else: # gbc color palette = png_pal(pal_file) greyscale = False bitdepth = 8 - map = [[pixel for pixel in line] for line in lines] - + px_map = [[pixel for pixel in line] for line in lines] - w = png.Writer(width, height, palette=palette, compression = 9, greyscale = greyscale, bitdepth = bitdepth) - with open(fileout, 'wb') as file: - w.write(file, map) + return width, height, palette, greyscale, bitdepth, px_map def export_png_to_2bpp(filein, fileout=None, palout=None): @@ -1489,7 +1418,7 @@ def mass_to_png(debug=False): for name in files: if debug: print os.path.splitext(name), os.path.join(root, name) if os.path.splitext(name)[1] == '.2bpp': - to_png(os.path.join(root, name)) + export_2bpp_to_png(os.path.join(root, name)) def mass_to_colored_png(debug=False): # greyscale, unless a palette is detected @@ -1498,7 +1427,7 @@ def mass_to_colored_png(debug=False): for name in files: if debug: print os.path.splitext(name), os.path.join(root, name) if os.path.splitext(name)[1] == '.2bpp': - to_png(os.path.join(root, name)) + export_2bpp_to_png(os.path.join(root, name)) os.utime(os.path.join(root, name), None) # only monster and trainer pics for now @@ -1507,16 +1436,16 @@ def mass_to_colored_png(debug=False): if debug: print os.path.splitext(name), os.path.join(root, name) if os.path.splitext(name)[1] == '.2bpp': if 'normal.pal' in files: - to_png(os.path.join(root, name), None, os.path.join(root, 'normal.pal')) + export_2bpp_to_png(os.path.join(root, name), None, os.path.join(root, 'normal.pal')) else: - to_png(os.path.join(root, name)) + export_2bpp_to_png(os.path.join(root, name)) os.utime(os.path.join(root, name), None) for root, dirs, files in os.walk('./gfx/trainers/'): for name in files: if debug: print os.path.splitext(name), os.path.join(root, name) if os.path.splitext(name)[1] == '.2bpp': - to_png(os.path.join(root, name)) + export_2bpp_to_png(os.path.join(root, name)) os.utime(os.path.join(root, name), None) @@ -1556,7 +1485,7 @@ def append_terminator_to_lzs(directory): new.write(data) new.close() -def lz_to_png_by_file(filename): +def export_lz_to_png(filename): """ Convert a lz file to png. Dump a 2bpp file too. """ @@ -1565,7 +1494,7 @@ def lz_to_png_by_file(filename): bpp = Decompressed(lz_data).output bpp_filename = filename.replace(".lz", ".2bpp") to_file(bpp_filename, bpp) - to_png(bpp_filename) + export_2bpp_to_png(bpp_filename) def dump_tileset_pngs(): """ @@ -1575,7 +1504,7 @@ def dump_tileset_pngs(): """ for tileset_id in range(37): tileset_filename = "./gfx/tilesets/" + str(tileset_id).zfill(2) + ".lz" - lz_to_png_by_file(tileset_filename) + export_lz_to_png(tileset_filename) def decompress_frontpic(lz_file): """ @@ -1645,9 +1574,9 @@ if __name__ == "__main__": lz = open(name+'.lz', 'rb').read() to_file(name+'.2bpp', Decompressed(lz, 'vert').output) pic = open(name+'.2bpp', 'rb').read() - to_file(name+'.png', to_png(pic)) + to_file(name+'.png', export_2bpp_to_png(pic)) else: - lz_to_png_by_file(argv[2]) + export_lz_to_png(argv[2]) elif argv[1] == 'png-to-lz': # python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png] @@ -1682,4 +1611,4 @@ if __name__ == "__main__": compress_file(filein, fileout) elif argv[1] == '2bpp-to-png': - to_png(argv[2]) + export_2bpp_to_png(argv[2]) -- cgit v1.2.3 From a91b3498232cd409f61b1f5f83abca684abf49a6 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 18 Nov 2013 05:08:38 -0500 Subject: gfx: 1bpp-to-png also dont assume any dimension matches in png conversion --- pokemontools/gfx.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 30a9fb1..fc39ea2 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1226,7 +1226,8 @@ def convert_2bpp_to_png(image, width=0, height=0, pal_file=None): if w * h == num_pixels: matches += [(w, h)] # go for the most square image - width, height = sorted(matches, key= lambda (w, h): w + h)[0] # favor height + if len(matches): + width, height = sorted(matches, key= lambda (w, h): w + h)[0] # favor height # if it still isn't rectangular then the image isn't made of tiles if width * height != num_pixels: @@ -1408,8 +1409,25 @@ def convert_1bpp_to_2bpp(data): """ Convert 1bpp image data to planar 2bpp (black/white). """ - return type(data)(byte for byte in data for _ in (0, 1)) + output = [] + for i in data: + output += [i, i] + return output + + +def export_1bpp_to_png(filename, fileout=None): + + if fileout == None: + fileout = os.path.splitext(filename)[0] + '.png' + image = open(filename, 'rb').read() + image = convert_1bpp_to_2bpp(image) + + width, height, palette, greyscale, bitdepth, px_map = convert_2bpp_to_png(image) + + w = png.Writer(width, height, palette=palette, compression=9, greyscale=greyscale, bitdepth=bitdepth) + with open(fileout, 'wb') as f: + w.write(f, px_map) def mass_to_png(debug=False): @@ -1429,6 +1447,9 @@ def mass_to_colored_png(debug=False): if os.path.splitext(name)[1] == '.2bpp': export_2bpp_to_png(os.path.join(root, name)) os.utime(os.path.join(root, name), None) + elif os.path.splitext(name)[1] == '.1bpp': + export_1bpp_to_png(os.path.join(root, name)) + os.utime(os.path.join(root, name), None) # only monster and trainer pics for now for root, dirs, files in os.walk('./gfx/pics/'): -- cgit v1.2.3 From 125a58fb9ae4ff9b5ac3329c8716d52685272532 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 18 Nov 2013 17:41:55 -0500 Subject: gfx: rewrite lz commands and comments --- pokemontools/gfx.py | 124 ++++++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 68 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index fc39ea2..ca14d58 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -76,54 +76,45 @@ def to_file(filename, data): - -# basic rundown of crystal's compression scheme: - -# a control command consists of -# the command (bits 5-7) -# and the count (bits 0-4) -# followed by additional params - -lz_lit = 0 -# print literal for [count] bytes - -lz_iter = 1 -# print one byte [count] times - -lz_alt = 2 -# print alternating bytes (2 params) for [count] bytes - -lz_zeros = 3 -# print 00 for [count] bytes - -# repeater control commands have a signed parameter used to determine the start point -# wraparound is simulated -# positive values are added to the start address of the decompressed data -# and negative values are subtracted from the current position - -lz_repeat = 4 -# print [count] bytes from decompressed data - -lz_flip = 5 -# print [count] bytes from decompressed data in bit order 01234567 - -lz_reverse = 6 -# print [count] bytes from decompressed data backwards - -lz_hi = 7 -# -used when the count exceeds 5 bits. uses a 10-bit count instead -# -bits 2-4 now contain the control code, bits 0-1 are bits 8-9 of the count -# -the following byte contains bits 0-7 of the count - -lz_end = 0xff -# if 0xff is encountered the decompression ends - -# since frontpics have animation tiles lumped onto them, -# sizes must be grabbed from base stats to know when to stop reading them - +""" +A rundown of Pokemon Crystal's compression scheme: + +Control commands occupy bits 5-7. +Bits 0-4 serve as the first parameter for each command. +""" +lz_commands = { + 'literal': 0, # n values for n bytes + 'iterate': 1, # one value for n bytes + 'alternate': 2, # alternate two values for n bytes + 'blank': 3, # zero for n bytes +} + +""" +Repeater commands repeat any data that was just decompressed. +They take an additional signed parameter to mark a relative starting point. +These wrap around (positive from the start, negative from the current position). +""" +lz_commands.update({ + 'repeat': 4, # n bytes starting from s + 'flip': 5, # n bytes in reverse bit order starting from s + 'reverse': 6, # n bytes backwards starting from s +}) + +""" +The long command is used when 5 bits aren't enough. Bits 2-4 contain a new control code. +Bits 0-1 are appended to a new byte as 8-9, allowing a 10-bit parameter. +""" +lz_commands.update({ + 'long': 7, # n is now 10 bits for a new control code +}) max_length = 1 << 10 # can't go higher than 10 bits lowmax = 1 << 5 # standard 5-bit param +""" +If 0xff is encountered instead of a command, decompression ends. +""" +lz_end = 0xff + class Compressed: @@ -239,10 +230,10 @@ class Compressed: def doLiterals(self): if len(self.literals) > lowmax: - self.output.append( (lz_hi << 5) | (lz_lit << 2) | ((len(self.literals) - 1) >> 8) ) + self.output.append( (lz_commands['long'] << 5) | (lz_commands['literal'] << 2) | ((len(self.literals) - 1) >> 8) ) self.output.append( (len(self.literals) - 1) & 0xff ) elif len(self.literals) > 0: - self.output.append( (lz_lit << 5) | (len(self.literals) - 1) ) + self.output.append( (lz_commands['literal'] << 5) | (len(self.literals) - 1) ) for byte in self.literals: self.output.append(byte) self.literals = [] @@ -257,8 +248,8 @@ class Compressed: """ Works, but doesn't do flipped/reversed streams yet. - This takes up most of the compress time and only saves a few bytes - it might be more feasible to exclude it entirely. + This takes up most of the compress time and only saves a few bytes. + It might be more effective to exclude it entirely. """ self.repeats = [] @@ -363,14 +354,14 @@ class Compressed: # decide which side we're copying from if (self.address - repeat[1]) <= 0x80: self.doLiterals() - self.stream.append( (lz_repeat << 5) | length - 1 ) + self.stream.append( (lz_commands['repeat'] << 5) | length - 1 ) # wrong? self.stream.append( (((self.address - repeat[1])^0xff)+1)&0xff ) else: self.doLiterals() - self.stream.append( (lz_repeat << 5) | length - 1 ) + self.stream.append( (lz_commands['repeat'] << 5) | length - 1 ) # wrong? self.stream.append(repeat[1]>>8) @@ -400,10 +391,10 @@ class Compressed: def doWhitespace(self): if (len(self.zeros) + 1) >= lowmax: - self.stream.append( (lz_hi << 5) | (lz_zeros << 2) | ((len(self.zeros) - 1) >> 8) ) + self.stream.append( (lz_commands['long'] << 5) | (lz_commands['blank'] << 2) | ((len(self.zeros) - 1) >> 8) ) self.stream.append( (len(self.zeros) - 1) & 0xff ) elif len(self.zeros) > 1: - self.stream.append( lz_zeros << 5 | (len(self.zeros) - 1) ) + self.stream.append( lz_commands['blank'] << 5 | (len(self.zeros) - 1) ) else: raise Exception, "checkWhitespace() should prevent this from happening" @@ -456,12 +447,12 @@ class Compressed: num_alts = len(self.iters) + 1 if num_alts > lowmax: - self.stream.append( (lz_hi << 5) | (lz_alt << 2) | ((num_alts - 1) >> 8) ) + self.stream.append( (lz_commands['long'] << 5) | (lz_commands['alternate'] << 2) | ((num_alts - 1) >> 8) ) self.stream.append( num_alts & 0xff ) self.stream.append( self.alts[0] ) self.stream.append( self.alts[1] ) elif num_alts > 2: - self.stream.append( (lz_alt << 5) | (num_alts - 1) ) + self.stream.append( (lz_commands['alternate'] << 5) | (num_alts - 1) ) self.stream.append( self.alts[0] ) self.stream.append( self.alts[1] ) else: @@ -498,22 +489,19 @@ class Compressed: self.next() if (len(self.iters) - 1) >= lowmax: - self.stream.append( (lz_hi << 5) | (lz_iter << 2) | ((len(self.iters)-1) >> 8) ) + self.stream.append( (lz_commands['long'] << 5) | (lz_commands['iterate'] << 2) | ((len(self.iters)-1) >> 8) ) self.stream.append( (len(self.iters) - 1) & 0xff ) self.stream.append( iter ) elif len(self.iters) > 3: # 3 or fewer isn't worth the trouble and actually longer # if part of a larger literal set - self.stream.append( (lz_iter << 5) | (len(self.iters) - 1) ) + self.stream.append( (lz_commands['iterate'] << 5) | (len(self.iters) - 1) ) self.stream.append( iter ) else: self.address = original_address raise Exception, "checkIter() should prevent this from happening" - - - class Decompressed: """ Parse compressed 2bpp data. @@ -579,7 +567,7 @@ class Decompressed: self.cmd = (self.byte & 0b11100000) >> 5 - if self.cmd == lz_hi: # 10-bit param + if self.cmd == lz_commands['long']: # 10-bit param self.cmd = (self.byte & 0b00011100) >> 2 self.length = (self.byte & 0b00000011) << 8 self.next() @@ -588,13 +576,13 @@ class Decompressed: self.length = (self.byte & 0b00011111) + 1 # literals - if self.cmd == lz_lit: + if self.cmd == lz_commands['literal']: self.doLiteral() - elif self.cmd == lz_iter: + elif self.cmd == lz_commands['iterate']: self.doIter() - elif self.cmd == lz_alt: + elif self.cmd == lz_commands['alternate']: self.doAlt() - elif self.cmd == lz_zeros: + elif self.cmd == lz_commands['blank']: self.doZeros() else: # repeaters @@ -607,11 +595,11 @@ class Decompressed: self.next() self.displacement += self.byte - if self.cmd == lz_flip: + if self.cmd == lz_commands['flip']: self.doFlip() - elif self.cmd == lz_reverse: + elif self.cmd == lz_commands['reverse']: self.doReverse() - else: # lz_repeat + else: # lz_commands['repeat'] self.doRepeat() self.address += 1 -- cgit v1.2.3 From 7fa921eb65d71f8ecf2527838090c69aa6d6ac64 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 18 Nov 2013 17:57:07 -0500 Subject: gfx: direct png-to-1bpp --- pokemontools/gfx.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index ca14d58..141f27e 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1418,6 +1418,19 @@ def export_1bpp_to_png(filename, fileout=None): w.write(f, px_map) +def export_png_to_1bpp(filename, fileout=None): + image = png_to_1bpp(filename) + + if fileout == None: + fileout = os.path.splitext(filename)[0] + '.1bpp' + + to_file(fileout, image) + +def png_to_1bpp(filename): + image, palette = png_to_2bpp(filename) + return convert_2bpp_to_1bpp(image) + + def mass_to_png(debug=False): # greyscale for root, dirs, files in os.walk('./gfx/'): @@ -1609,6 +1622,9 @@ if __name__ == "__main__": elif argv[1] == 'png-to-2bpp': export_png_to_2bpp(argv[2]) + elif argv[1] == 'png-to-1bpp': + export_png_to_1bpp(argv[2]) + elif argv[1] == '2bpp-to-lz': if argv[2] == '--vert': filein = argv[3] -- cgit v1.2.3 From 9d01c85d3bac2a6a7b5826dc2139f69731a901ab Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 18 Nov 2013 20:19:18 -0500 Subject: gfx: make sure rectangular images are also divisible into 8x8 tiles --- pokemontools/gfx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 141f27e..2ea6b54 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1211,7 +1211,7 @@ def convert_2bpp_to_png(image, width=0, height=0, pal_file=None): matches = [] for w in range(8, num_pixels / 2 + 1, 8): h = num_pixels / w - if w * h == num_pixels: + if w * h == num_pixels and h % 8 == 0: matches += [(w, h)] # go for the most square image if len(matches): -- cgit v1.2.3 From 3dac619f2d08f22b31e51311e27225c9f97d642e Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 18 Nov 2013 21:05:59 -0500 Subject: gfx: fix misuse of export_2bpp_to_png --- pokemontools/gfx.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 37dd1b6..3c5346e 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1598,8 +1598,7 @@ if __name__ == "__main__": name = os.path.splitext(argv[3])[0] lz = open(name+'.lz', 'rb').read() to_file(name+'.2bpp', Decompressed(lz, 'vert').output) - pic = open(name+'.2bpp', 'rb').read() - to_file(name+'.png', export_2bpp_to_png(pic)) + export_2bpp_to_png(name+'.2bpp') else: export_lz_to_png(argv[2]) -- cgit v1.2.3