From 754b0e60c8f11c217d65b3a553e0bbf03d1930da Mon Sep 17 00:00:00 2001 From: Rob McMullen Date: Sat, 15 Jul 2017 22:41:07 -0700 Subject: [PATCH] Added title screen image conversion --- HiSprite.py | 215 +++++++++++++++++++++++++++++++++++++++++++++++----- Makefile | 18 ++++- 2 files changed, 209 insertions(+), 24 deletions(-) diff --git a/HiSprite.py b/HiSprite.py index 198e630..aaf2873 100755 --- a/HiSprite.py +++ b/HiSprite.py @@ -8,6 +8,7 @@ import re # external packages import png # package name is "pypng" on pypi.python.org +import numpy as np def slugify(s): @@ -206,13 +207,11 @@ class Listing(object): class Sprite(Listing): backing_store_sizes = set() - def __init__(self, pngfile, assembler, screen, xdraw=False, use_mask=False, backing_store=False, clobber=False, double_buffer=False, damage=False, processor="any", name=""): + def __init__(self, slug, pngdata, assembler, screen, xdraw=False, use_mask=False, backing_store=False, clobber=False, double_buffer=False, damage=False, processor="any"): Listing.__init__(self, assembler) + self.slug = slug self.screen = screen - reader = png.Reader(pngfile) - pngdata = reader.asRGB8() - self.xdraw = xdraw self.use_mask = use_mask self.backing_store = backing_store @@ -220,9 +219,6 @@ class Sprite(Listing): self.double_buffer = double_buffer self.damage = damage self.processor = processor - if not name: - name = os.path.splitext(pngfile)[0] - self.slug = slugify(name) self.width = pngdata[0] self.height = pngdata[1] self.pixel_data = list(pngdata[2]) @@ -528,6 +524,17 @@ class HGR(ScreenFormat): return "00" + def bits_for_bw(self, pixel): + if pixel == self.white: + return "1" + else: + return "0" + + def bits_for_bw_mask(self, pixel): + if pixel == self.key: + return "1" + return "0" + def high_bit_for_color(self, pixel): # Note that we prefer high-bit white because blue fringe is less noticeable than magenta. high_bit = "0" @@ -643,15 +650,10 @@ class HGRBW(HGR): bits_per_pixel = 1 def bits_for_color(self, pixel): - if pixel == self.white: - return "1" - else: - return "0" + return self.bits_for_bw(pixel) def bits_for_mask(self, pixel): - if pixel == self.key: - return "1" - return "0" + return self.bits_for_bw_mask(pixel) def pixel_color(self, pixel_data, row, col): r = pixel_data[row][col*3] @@ -980,6 +982,165 @@ class FastFont(Listing): self.byte("$%02x" % ord(data[index]), 16) +class HGRByLine(HGR): + """Either a color line or a BW line, depending on the contents of each + line. + + If the entire line has only BW pixels, look at all the pixel data to + preserve all 280 pixels across. + + Otherwise, look at every other pixel and convert as the normal HGR + """ + bits_per_pixel = 1 + + def __init__(self, color="line"): + HGR.__init__(self) + if color == "color": + self.scan_line = self.scan_line_color + elif color == "bw": + self.scan_line = self.scan_line_bw + else: + self.scan_line = self.scan_line_default + + def is_pixel_on(self, pixel_data, row, col): + r = pixel_data[row][col*3] + g = pixel_data[row][col*3+1] + b = pixel_data[row][col*3+2] + # any pixel that is not super dark is considered on + return r>25 or g>25 or b>25 + + def scan_line_default(self, pixel_data, row, width): + color_count = 0 + bw_count = 0 + for col in range(1, width): + current = self.pixel_color(pixel_data, row, col) + if current == self.white: + bw_count += 1 + elif current != self.black and current != self.key: + color_count += 1 + # print("row: %d, bw=%d, color=%d" % (row, bw_count, color_count)) + if color_count > 1: + return self.color_processor + return self.bw_processor + + def scan_line_color(self, pixel_data, row, width): + return self.color_processor + + def scan_line_bw(self, pixel_data, row, width): + return self.bw_processor + + def color_processor(self, pixel_data, row, width): + bit_stream = "" + high_bits = "" + # Compute raw bitstream for row from PNG pixels, skipping every other + # for color rendering + for byte_index in range(0, width, 2*7): + h = None + # take high bit for each byte using the first non-black pixel + for bit_index in range(0, 2*7, 2): + pixel_index = byte_index + bit_index + pixel = self.pixel_color(pixel_data,row,pixel_index) + if h is None and pixel != self.black and pixel != self.key: + h = self.high_bit_for_color(pixel) + bit_stream += self.bits_for_color(pixel) + if h is None: + h = "0" + high_bits += h * 2*7 + + return self.split_bit_stream(width, bit_stream, high_bits) + + def bw_processor(self, pixel_data, row, width): + bit_stream = "" + high_bits = "" + # Compute raw bitstream for row from PNG pixels, skipping every other + # for color rendering + for pixel_index in range(0, width): + pixel = self.pixel_color(pixel_data,row,pixel_index) + bit_stream += self.bits_for_bw(pixel) + h = self.high_bit_for_color(pixel) + high_bits += h + + return self.split_bit_stream(width, bit_stream, high_bits) + + def split_bit_stream(self, width, bit_stream, high_bits): + # print bit_stream + # print high_bits + + # Split bitstream into bytes + byte_width = width // 7 + bit_pos = 0 + filler_bit = "0" + byte_splits = np.zeros((byte_width), dtype=np.uint8) + + for byte_index in range(byte_width): + remaining_bits = len(bit_stream) - bit_pos + + bit_chunk = "" + + if remaining_bits < 0: + bit_chunk = filler_bit * 7 + else: + if remaining_bits < 7: + bit_chunk = bit_stream[bit_pos:] + bit_chunk += filler_bit * (7-remaining_bits) + else: + bit_chunk = bit_stream[bit_pos:bit_pos+7] + + bit_chunk = bit_chunk[::-1] + byte = high_bits[bit_pos] + bit_chunk + #print("%d: %s" % (byte_index, byte)) + byte_splits[byte_index] = int(byte, 2) + bit_pos += 7 + + return byte_splits + + def lines_from_pixels(self, source): + lines = np.zeros((192, 40), dtype=np.uint8) + + bit_delegate = self.bits_for_color + high_bit_delegate = self.high_bit_for_color + filler_bit = "0" + + for row in range(source.height): + processor = self.scan_line(source.pixel_data, row, source.width) + lines[row] = processor(source.pixel_data, row, source.width) + + return lines + + +class Image(object): + def __init__(self, pngdata, fileroot, color): + self.screen = HGRByLine(color) + + self.width = pngdata[0] + self.height = pngdata[1] + self.pixel_data = list(pngdata[2]) + self.convert(fileroot) + + def convert(self, fileroot): + lines = self.screen.lines_from_pixels(self) + output = "%s.hgr.png" % fileroot + with open(output, "wb") as fh: + # output PNG + w = png.Writer(280, 192, greyscale=True, bitdepth=1) + bits = np.fliplr(np.unpackbits(lines.ravel()).reshape(-1,8)[:,1:8]) + bw = bits.reshape((192, 280)) + w.write(fh, bw) + print("created bw representation of HGR screen: %s" % output) + + offsets = self.screen.generate_row_addresses(0) + # print ["%04x" % i for i in offsets] + screen = np.zeros(8192, dtype=np.uint8) + for row in range(192): + offset = offsets[row] + screen[offset:offset+40] = lines[row] + + output = "%s.hgr" % fileroot + with open(output, "wb") as fh: + fh.write(screen) + print("created HGR screen: %s" % output) + + if __name__ == "__main__": disclaimer = ''' ; This file was generated by HiSprite.py, a sprite compiler by Quinn Dunki. @@ -996,6 +1157,7 @@ if __name__ == "__main__": parser.add_argument("-a", "--assembler", default="cc65", choices=["cc65","mac65"], help="Assembler syntax (default: %(default)s)") parser.add_argument("-p", "--processor", default="any", choices=["any","6502", "65C02"], help="Processor type (default: %(default)s)") parser.add_argument("-s", "--screen", default="hgrcolor", choices=["hgrcolor","hgrbw"], help="Screen format (default: %(default)s)") + parser.add_argument("-i", "--image", default="line", choices=["line", "color","bw"], help="Screen format used for full page image conversion (default: %(default)s)") parser.add_argument("-n", "--name", default="", help="Name for generated assembly function (default: based on image filename)") parser.add_argument("-k", "--clobber", action="store_true", default=False, help="don't save the registers on the stack") parser.add_argument("-d", "--double-buffer", action="store_true", default=False, help="add code blit to either page (default: page 1 only)") @@ -1028,19 +1190,30 @@ if __name__ == "__main__": for pngfile in options.files: try: - sprite_code = Sprite(pngfile, assembler, screen, options.xdraw, options.mask, options.backing_store, options.clobber, options.double_buffer, options.damage, options.processor, options.name) + reader = png.Reader(pngfile) + pngdata = reader.asRGB8() except RuntimeError, e: print "%s: %s" % (pngfile, e) sys.exit(1) except png.Error, e: print "%s: %s" % (pngfile, e) sys.exit(1) - listings.append(sprite_code) - if options.output_prefix: - r = RowLookup(assembler, screen) - luts[r.slug] = r - c = ColLookup(assembler, screen) - luts[c.slug] = c + + name = options.name if options.name else os.path.splitext(pngfile)[0] + slug = slugify(name) + + w, h = pngdata[0:2] + if w == 280 and h == 192: + # Full screen conversion! + Image(pngdata, name, options.image.lower()) + else: + sprite_code = Sprite(slug, pngdata, assembler, screen, options.xdraw, options.mask, options.backing_store, options.clobber, options.double_buffer, options.damage, options.processor) + listings.append(sprite_code) + if options.output_prefix: + r = RowLookup(assembler, screen) + luts[r.slug] = r + c = ColLookup(assembler, screen) + luts[c.slug] = c listings.extend([luts[k] for k in sorted(luts.keys())]) diff --git a/Makefile b/Makefile index c62a817..89aa14b 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ COLORSPRITE = moldy_burger.png #COLORSPRITE = apple.png BWSPRITE = apple-sprite9x11.png -all: cpbg.dsk fonttest.dsk +all: cpbg.dsk fonttest.dsk titles.dsk rowlookup.s: HiSprite.py python HiSprite.py -a mac65 -p 6502 -r > rowlookup.s @@ -48,8 +48,19 @@ cpbg.xex: cpbg.s cpbg-sprite-driver.s cpbg.dsk: HiSprite.py cpbg.xex atrcopy cpbg.dsk boot -b cpbg.xex --brun 6000 -f -titles.dsk: HiSprite.py cpbg.xex - atrcopy titles.dsk boot -d partycrasher-software.hgr@2000 player-missile.hgr@4000 -b cpbg.xex --brun 6000 -f +player-missile.hgr: HiSprite.py player-missile.png + python HiSprite.py player-missile.png + +kansasfest-disclaimer.hgr: HiSprite.py kansasfest-disclaimer.png + python HiSprite.py -i color kansasfest-disclaimer.png + +partycrasher-software.hgr: HiSprite.py partycrasher-software.png + python HiSprite.py -i color partycrasher-software.png + +titles.dsk: HiSprite.py cpbg.xex player-missile.hgr kansasfest-disclaimer.hgr partycrasher-software.hgr + atrcopy titles.dsk boot -d kansasfest-disclaimer.hgr@2000 partycrasher-software.hgr@4000 player-missile.hgr@2000 -b cpbg.xex --brun 6000 -f + #atrcopy titles.dsk boot -d partycrasher-software.bin@2000 kansasfest-disclaimer.bin@4000 player-missile-bg.bin@2000 -b cpbg.xex --brun 6000 -f + #atrcopy titles.dsk boot -d player-missile.hgr@2000 -b cpbg.xex --brun 6000 -f fonttest.dsk: fonttest.s fatfont.s atasm -ofonttest.xex fonttest.s -Lfonttest.var -gfonttest.lst @@ -61,3 +72,4 @@ clean: rm -f colortest.dsk colortest.xex colortest.var colortest.lst rm -f multitest.dsk multitest.xex multitest.var multitest.lst multitest-sprite-driver.s multitest-bwsprite.s multitest-hgrcols-7x1.s multitest-hgrrows.s rm -f cpbg.dsk cpbg.xex cpbg.var cpbg.lst cpbg-sprite-driver.s cpbg-bwsprite.s cpbg-hgrcols-7x1.s cpbg-hgrrows.s + rm -f player-missile.hgr kansasfest-disclaimer.hgr partycrasher-software.hgr