def test_animation_mask1(self): # 2 frames, transparency on frame 2 iw_args = {'options': self.alpha_option} frame1 = Frame([[Udg(49, (64,) * 8)]]) frame2 = Frame([[Udg(184, (240,) * 8, (243,) * 8)]], mask=1) frames = [frame1, frame2] self._test_animated_image(frames, iw_args)
def test_animation(self): # 3 frames, 2 colours, 16x8 frame1 = Frame([[Udg(6, (128,) * 8), Udg(6, (0,) * 8)]], delay=20) frame2 = Frame([[Udg(6, (64,) * 8), Udg(6, (1,) * 8)]], delay=100) frame3 = Frame([[Udg(6, (32,) * 8), Udg(6, (2,) * 8)]], delay=150) frames = [frame1, frame2, frame3] self._test_animated_image(frames)
def test_animation_with_frames_of_different_sizes(self): # First frame 16x8, second frame 8x16, third frame 8x8 frame1 = Frame([[Udg(1, (0,) * 8)] * 2]) frame2 = Frame([[Udg(1, (0,) * 8)]] * 2) frame3 = Frame([[Udg(1, (0,) * 8)]]) frames = [frame1, frame2, frame3] self._test_animated_image(frames)
def _animate_conveyor(self, udgs, direction, x, y, length, scale): mask = 0 delay = 10 frame1 = Frame(udgs, scale, mask, delay=delay) frames = [frame1] if y < 0 or y >= len(udgs) or x + length < 0 or x >= len(udgs[0]): return frames min_x = max(x, 0) max_x = min(x + length, len(udgs[0])) length_t = max_x - min_x base_udg = prev_udg = udgs[y][min_x] while True: next_udg = prev_udg.copy() data = next_udg.data if direction: data[0] = (data[0] >> 2) + (data[0] & 3) * 64 data[2] = ((data[2] << 2) & 255) + (data[2] >> 6) else: data[0] = ((data[0] << 2) & 255) + (data[0] >> 6) data[2] = (data[2] >> 2) + (data[2] & 3) * 64 if next_udg.data == base_udg.data: break next_udgs = [row[:] for row in udgs] next_udgs[y][min_x:max_x] = [next_udg] * length_t frames.append(Frame(next_udgs, scale, mask, delay=delay)) prev_udg = next_udg return frames
def test_animation_with_alternative_transparent_colour_on_first_frame_only(self): # White (PAPER) as transparent colour on first frame iw_args = {'options': self.alpha_option} frame1 = Frame([[Udg(56, (15,) * 8)]], tindex=8) frame2 = Frame([[Udg(1, (15,) * 8)]]) frames = [frame1, frame2] self._test_animated_image(frames, iw_args)
def test_animation_with_alternative_transparent_colour_on_second_frame_only(self): # Yellow (PAPER) as transparent colour on second frame iw_args = {'options': self.alpha_option} frame1 = Frame([[Udg(56, (15,) * 8)]], tindex=7) frame2 = Frame([[Udg(48, (15,) * 8)]]) frames = [frame1, frame2] self._test_animated_image(frames, iw_args)
def test_animation_with_alternative_transparent_colour_overridden_by_mask(self): # White (PAPER) as transparent colour on first frame, overridden by # mask on second frame iw_args = {'options': self.alpha_option} frame1 = Frame([[Udg(56, (15,) * 8)]], tindex=8) frame2 = Frame([[Udg(56, (15,) * 8, (207,) * 8)]], mask=1) frames = [frame1, frame2] self._test_animated_image(frames, iw_args)
def cavern(self, cwd, address, scale=2, fname=None, x=0, y=0, w=32, h=17, guardians=1, animate=0): if fname is None: fname = self.cavern_names[address].lower().replace(' ', '_') cavern_udgs = self._get_cavern_udgs(address, guardians) img_udgs = [ cavern_udgs[i][x:x + w] for i in range(y, y + min(h, 17 - y)) ] if animate: direction = self.snapshot[address + 623] sb_addr = self.snapshot[address + 624] + 256 * self.snapshot[address + 625] conveyor_x = sb_addr % 32 - x conveyor_y = 8 * ( (sb_addr - 28672) // 2048) + (sb_addr % 256) // 32 - y length = self.snapshot[address + 626] frames = self._animate_conveyor(img_udgs, direction, conveyor_x, conveyor_y, length, scale) else: frames = [Frame(img_udgs, scale)] return self.handle_image(frames, fname, cwd, path_id='ScreenshotImagePath')
def run(snafile, imgfname, options): snapshot = get_snapshot(snafile) skool = BackToSkool(snapshot) width = 192 eric = 210 for address in options.mutable: skool.alter_skool_udgs(address) x = y = 0 height = 21 skool.hide_chars() if options.blackboard: bb = BLACKBOARDS[options.blackboard] width, height, x, y = bb['geometry'] eric_x, eric_y = bb['location'] skool.place_char(eric, eric_x, eric_y, 0) elif options.geometry: wh, xy = options.geometry.split('+', 1) width, height = [int(n) for n in wh.split('x')] x, y = [int(n) for n in xy.split('+')] for spec in options.text: board_id, sep, text = spec.partition(':') if sep: bb = BLACKBOARDS[board_id] skool.write(bb['tiles'], text) for spec in options.place_char: values = [] for n in spec.split(','): try: values.append(int(n)) except ValueError: values.append(None) skool.place_char(*values[:4]) for spec in options.pokes: addr, val = spec.split(',', 1) step = 1 if '-' in addr: addr1, addr2 = addr.split('-', 1) addr1 = int(addr1) if '-' in addr2: addr2, step = [int(i) for i in addr2.split('-', 1)] else: addr2 = int(addr2) else: addr1 = int(addr) addr2 = addr1 addr2 += 1 value = int(val) for a in range(addr1, addr2, step): snapshot[a] = value udg_array = skool.get_skool_udgs(x, y, width, height) frame = Frame(udg_array, options.scale) image_writer = ImageWriter() image_format = 'gif' if imgfname.lower()[-4:] == '.gif' else 'png' with open(imgfname, "wb") as f: image_writer.write_image([frame], f, image_format)
def _get_image_data(self, image_writer, udg_array, scale=1, mask=0, tindex=0, alpha=-1, x=0, y=0, width=None, height=None): frame = Frame(udg_array, scale, mask, x, y, width, height, tindex=tindex, alpha=alpha) img_stream = BytesIO() image_writer.write_image([frame], img_stream) img_bytes = [b for b in img_stream.getvalue()] img_stream.close() return img_bytes
def _render_room(self, cwd, roomdata): # room_dims is not comprehendible to me right now .. could use a # worst-size case of the screen size room_width, room_height = 24, 16 # Build a UDG array for the room udg_array = [[self._interior_tile(cwd, 0) for x in range(room_width)] for y in range(room_height)] for obj_index, x, y in roomdata['objects']: interior_object_defs = 0x7095 + obj_index * 2 objdef = self.snapshot[interior_object_defs] + self.snapshot[ interior_object_defs + 1] * 256 width, height, tiles = self._expand_object(cwd, objdef) tiles.reverse() for yy in range(height): for xx in range(width): t = tiles.pop() if t: udg_array[y + yy][x + xx] = self._interior_tile(cwd, t) fname = '{ScreenshotImagePath}/room-%d' % roomdata['room_no'] return self.handle_image(Frame(udg_array, 2), fname, cwd)
def _parse_udg(snapshot, param_str): end, crop_rect, fname, frame, alt, params = skoolmacro.parse_udg(param_str) addr, attr, scale, step, inc, flip, rotate, mask, tindex, alpha, mask_addr, mask_step = params udgs = [[ build_udg(snapshot, addr, attr, step, inc, flip, rotate, mask, mask_addr, mask_step) ]] return Frame(udgs, scale, mask, *crop_rect, tindex=tindex, alpha=alpha)
def as_img(self, cwd, num, scale=2, mask=1, attr=120, udg_page=None, fname_suffix=''): mask_infix = 'm' if mask else 'u' udg_page_infix = udg_page if udg_page is not None else '' snapshot_name = self.get_snapshot_name() snapshot_infix = '_{0}'.format(snapshot_name) if snapshot_name else '' fname = 'as{0:03d}_{1}x{2}{3}{4}{5}{6}'.format(num & 255, attr, scale, mask_infix, udg_page_infix, snapshot_infix, fname_suffix) frame = Frame(lambda: self.build_sprite(num, attr, udg_page), scale, mask) alt = "Animatory state {}".format(num & 255) return self.handle_image([frame], fname, cwd, alt, 'AnimatoryStateImagePath')
def run(snafile, imgfname, options): snapshot = get_snapshot(snafile) game = ContactSamCruise(snapshot) x = y = 0 width, height = 256, 42 game.hide_chars() game.reset_fuses() game.switch_lights_on() game.raise_blinds() game.close_doors() game.adjust_rope(options.rope) if options.geometry: wh, xy = options.geometry.split('+', 1) width, height = [int(n) for n in wh.split('x')] x, y = [int(n) for n in xy.split('+')] for spec in options.place_char: values = [] index = 0 for n in spec.split(','): if not values and '.' in n: n, i = n.split('.', 1) try: index = int(i) except: pass try: values.append(int(n)) except ValueError: values.append(None) game.place_char(*values[:5], index=index) for spec in options.pokes: addr, val = spec.split(',', 1) step = 1 if '-' in addr: addr1, addr2 = addr.split('-', 1) addr1 = int(addr1) if '-' in addr2: addr2, step = [int(i) for i in addr2.split('-', 1)] else: addr2 = int(addr2) else: addr1 = int(addr) addr2 = addr1 addr2 += 1 value = int(val) for a in range(addr1, addr2, step): snapshot[a] = value udg_array = game.get_play_area_udgs(x, y, width, height) frame = Frame(udg_array, options.scale) image_writer = ImageWriter() image_format = 'gif' if imgfname.lower()[-4:] == '.gif' else 'png' with open(imgfname, "wb") as f: image_writer.write_image([frame], f, image_format)
def run(infile, outfile, options): if options.binary or options.org is not None: snapshot = read_bin_file(infile, 49152) if options.org is None: org = 65536 - len(snapshot) else: org = options.org snapshot = [0] * org + list(snapshot) + [0] * (65536 - org - len(snapshot)) elif infile[-4:].lower() == '.scr': scr = read_bin_file(infile, 6912) snapshot = [0] * 65536 snapshot[16384:16384 + len(scr)] = scr elif infile[-4:].lower() in ('.sna', '.szx', '.z80'): snapshot = get_snapshot(infile) else: try: snapshot = BinWriter(infile, fix_mode=options.fix_mode).snapshot except SkoolKitError: raise except: raise SkoolKitError( 'Unable to parse {} as a skool file'.format(infile)) for spec in options.moves: move(snapshot, spec) for spec in options.pokes: poke(snapshot, spec) if options.macro is not None: match = re.match('(#?)(FONT|SCR|UDG|UDGARRAY)([^A-Z]|$)', options.macro) if match: macro = match.group(2) try: frame = MACROS[macro](snapshot, options.macro[match.end(2):]) except skoolmacro.MacroParsingError as e: raise SkoolKitError('Invalid #{} macro: {}'.format( macro, e.args[0])) else: raise SkoolKitError('Macro must be #FONT, #SCR, #UDG or #UDGARRAY') else: (x, y), (w, h) = options.origin, options.size frame = Frame(scr_udgs(snapshot, x, y, w, h), options.scale) if options.invert: for row in frame.udgs: for udg in row: if udg.attr & 128: udg.data = [b ^ 255 for b in udg.data] udg.attr &= 127 flip_udgs(frame.udgs, options.flip) rotate_udgs(frame.udgs, options.rotate) _write_image(frame, outfile, options.animated)
def map(self, cwd, addr, width, height, colour_supertiles, checkerboard): """ Get a UDG game map then save it and return an IMG element. """ map_udgs = self._get_map_as_udgs(cwd, 0xBCEE, width, height, colour_supertiles, checkerboard) fname = '{ScreenshotImagePath}/map-%d-%d' % (colour_supertiles, checkerboard) return self.handle_image(Frame(map_udgs), fname, cwd)
def attribute_crash_img(self, cwd): self.push_snapshot() self.snapshot[59102:59105] = [2, 72, 17] cavern = self._get_cavern_udgs(58368) self.pop_snapshot() cavern[11][17] = cavern[11][18] = Udg(15, cavern[11][15].data) frame = Frame([row[14:22] for row in cavern[8:13]], 2) return self.handle_image([frame], 'attribute_crash', cwd, path_id='ScreenshotImagePath')
def _animate_conveyor(self, udgs, attr, direction, crop_rect, scale): mask = 0 x, y, width, height = crop_rect delay = 10 frame1 = Frame(udgs, scale, mask, x, y, width, height, delay) frames = [frame1] base_udg = None for row in udgs: for udg in row: if udg.attr == attr: base_udg = udg break if base_udg is None: return frames prev_udg = base_udg while True: next_udg = prev_udg.copy() data = next_udg.data if direction: data[0] = (data[0] >> 2) + (data[0] & 3) * 64 data[2] = ((data[2] << 2) & 255) + (data[2] >> 6) else: data[0] = ((data[0] << 2) & 255) + (data[0] >> 6) data[2] = (data[2] >> 2) + (data[2] & 3) * 64 if next_udg.data == base_udg.data: break next_udgs = [] for row in udgs: next_udgs.append([]) for udg in row: if udg.attr == attr: next_udgs[-1].append(next_udg) else: next_udgs[-1].append(udg) frames.append(Frame(next_udgs, scale, mask, x, y, width, height, delay)) prev_udg = next_udg return frames
def expand_willy(self, text, index, cwd): # #WILLYroom,x,y,sprite[,left,top,width,height,scale](fname) names = ('room', 'x', 'y', 'sprite', 'left', 'top', 'width', 'height', 'scale') defaults = (0, 0, 32, 17, 2) end, crop_rect, fname, frame, alt, params = parse_image_macro(text, index, defaults, names) room, x, pixel_y, sprite, left, top, width, height, scale = params room_addr = 49152 + 256 * room room_udgs = self._get_room_udgs(room_addr, 1) willy = self._get_graphic(40192 + 32 * sprite, 7) room_bg = self.snapshot[room_addr + 160] self._place_graphic(room_udgs, willy, x, pixel_y, room_bg) img_udgs = [room_udgs[i][left:left + width] for i in range(top, top + min(height, 17 - top))] frames = [Frame(img_udgs, scale, 0, *crop_rect, name=frame)] return end, self.handle_image(frames, fname, cwd, alt, 'ScreenshotImagePath')
def expand_willy(self, text, index, cwd): # #WILLYcavern,x,y,sprite[,left,top,width,height,scale](fname) names = ('cavern', 'x', 'y', 'sprite', 'left', 'top', 'width', 'height', 'scale') defaults = (0, 0, 32, 17, 2) end, crop_rect, fname, frame, alt, params = parse_image_macro(text, index, defaults, names) cavern, x, pixel_y, sprite, left, top, width, height, scale = params cavern_addr = 45056 + 1024 * cavern cavern_udgs = self._get_cavern_udgs(cavern_addr, 0) willy = self._get_graphic(33280 + 32 * sprite, 7) cavern_bg = self.snapshot[cavern_addr + 544] self._place_graphic(cavern_udgs, willy, x, pixel_y, cavern_bg) img_udgs = [cavern_udgs[i][left:left + width] for i in range(top, top + min(height, 17 - top))] frames = [Frame(img_udgs, scale, 0, *crop_rect, name=frame)] return end, self.handle_image(frames, fname, cwd, alt, 'ScreenshotImagePath')
def bottom_half_twice_img(self, cwd): cavern = self._get_cavern_udgs(64512) swordfish = self._get_graphic(45792, 1) cavern[5][19:21], cavern[6][19:21] = swordfish willy = self._get_graphic(33376, 1) cavern[2][19:21], cavern[3][19:21] = willy cavern[7][19:21] = willy[1] udgs = [row[18:22] for row in cavern[1:9]] for row in udgs: for udg in row: udg.attr = 1 return self.handle_image(Frame(udgs, 2), '{ScreenshotImagePath}/bottom_half_twice', cwd)
def decode_object(self, cwd, addr, index): width, height, tiles = self._expand_object(cwd, addr) tiles.reverse() # Build tile UDG array udg_array = [] for y in range(height): udg_array.append([]) # start new row for _ in range(width): udg_array[-1].append(self._interior_tile(cwd, tiles.pop())) fname = '{ScreenshotImagePath}/object-%d' % index return self.handle_image(Frame(udg_array, 2), fname, cwd)
def expand_room(self, text, index, cwd): # #ROOMaddr[,scale,x,y,w,h,empty,fix,anim][{x,y,width,height}][(fname)] names = ('addr', 'scale', 'x', 'y', 'w', 'h', 'empty', 'fix', 'anim') defaults = (2, 0, 0, 32, 17, 0, 0, 0) end, crop_rect, fname, frame, alt, params = parse_image_macro(text, index, defaults, names) address, scale, x, y, w, h, empty, fix, anim = params if not fname: room_name = self.room_names[address // 256 - 192] fname = room_name.lower().replace(' ', '_') room_udgs = self._get_room_udgs(address, empty, fix) img_udgs = [room_udgs[i][x:x + w] for i in range(y, y + min(h, 17 - y))] if anim: attr = self.snapshot[address + 205] direction = self.snapshot[address + 214] frames = self._animate_conveyor(img_udgs, attr, direction, crop_rect, scale) else: frames = [Frame(img_udgs, scale, 0, *crop_rect, name=frame)] return end, self.handle_image(frames, fname, cwd, alt, 'ScreenshotImagePath')
def _supertile_prime(self, cwd, addr, colour_supertiles, checkerboard): """ Return an image for the supertile at the specified address. """ stile = (addr - 0x5B00) // 16 # Build tile UDG array udg_array = [] for i in range(4 * 4): if i % 4 == 0: udg_array.append([]) # start new row bright = ((i // 4) & 1 ^ i & 1) if checkerboard else False tile = self._tile(cwd, self.snapshot[addr + i], stile, colour_supertiles, bright) udg_array[-1].append(tile) fname = '{ScreenshotImagePath}/supertile-%X-%d-%d' % ( stile, colour_supertiles, checkerboard) return self.handle_image(Frame(udg_array, 2), fname, cwd)
def _parse_udgarray(snapshot, param_str): end, crop_rect, fname, frame, alt, params = skoolmacro.parse_udgarray( param_str, 0, snapshot, False) udg_array, scale, flip, rotate, mask, tindex, alpha = params udgs = adjust_udgs(udg_array, flip, rotate) return Frame(udgs, scale, mask, *crop_rect, tindex=tindex, alpha=alpha)
def _decode_and_save_mask(self, cwd, addr, suggested_width, suggested_height): udg_array = self._decode_mask(cwd, addr, suggested_width, suggested_height) fname = '{ScreenshotImagePath}/mask-%.4X' % addr return self.handle_image(Frame(udg_array, 2), fname, cwd)
def mutable(self, cwd, address, padding=0): fname = 'mutable{0}_{1}'.format(address, padding) if padding else 'mutable{0}'.format(address) frame = Frame(lambda: self.get_mutable_udg_array(address, padding), 2) return self.handle_image([frame], fname, cwd, path_id='MutableImagePath')
def play_area(self, cwd, fname, x, y, w=1, h=1, scale=2, show_chars=0, show_x=0): frame = Frame(lambda: self.get_skool_udgs(x, y, w, h, show_chars, show_x), scale) return self.handle_image([frame], fname, cwd, path_id='PlayAreaImagePath')
def _parse_scr(snapshot, param_str): end, crop_rect, fname, frame, alt, params = skoolmacro.parse_scr(param_str) scale, x, y, w, h, df, af, tindex, alpha = params udgs = scr_udgs(snapshot, x, y, w, h, df, af) return Frame(udgs, scale, 0, *crop_rect, tindex=tindex, alpha=alpha)
def font(self, cwd, address, num_chars=96, scale=2, fname='font'): width = scale * (sum(self.snapshot[address:address + num_chars]) + num_chars + 1) frame = Frame(lambda: self._font_udgs(address, num_chars), scale, width=width) return self.handle_image([frame], fname, cwd, path_id='FontImagePath')