Added title screen image conversion

This commit is contained in:
Rob McMullen 2017-07-15 22:41:07 -07:00
parent 71e57e7433
commit 754b0e60c8
2 changed files with 209 additions and 24 deletions

View File

@ -8,6 +8,7 @@ import re
# external packages # external packages
import png # package name is "pypng" on pypi.python.org import png # package name is "pypng" on pypi.python.org
import numpy as np
def slugify(s): def slugify(s):
@ -206,13 +207,11 @@ class Listing(object):
class Sprite(Listing): class Sprite(Listing):
backing_store_sizes = set() 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) Listing.__init__(self, assembler)
self.slug = slug
self.screen = screen self.screen = screen
reader = png.Reader(pngfile)
pngdata = reader.asRGB8()
self.xdraw = xdraw self.xdraw = xdraw
self.use_mask = use_mask self.use_mask = use_mask
self.backing_store = backing_store self.backing_store = backing_store
@ -220,9 +219,6 @@ class Sprite(Listing):
self.double_buffer = double_buffer self.double_buffer = double_buffer
self.damage = damage self.damage = damage
self.processor = processor self.processor = processor
if not name:
name = os.path.splitext(pngfile)[0]
self.slug = slugify(name)
self.width = pngdata[0] self.width = pngdata[0]
self.height = pngdata[1] self.height = pngdata[1]
self.pixel_data = list(pngdata[2]) self.pixel_data = list(pngdata[2])
@ -528,6 +524,17 @@ class HGR(ScreenFormat):
return "00" 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): def high_bit_for_color(self, pixel):
# Note that we prefer high-bit white because blue fringe is less noticeable than magenta. # Note that we prefer high-bit white because blue fringe is less noticeable than magenta.
high_bit = "0" high_bit = "0"
@ -643,15 +650,10 @@ class HGRBW(HGR):
bits_per_pixel = 1 bits_per_pixel = 1
def bits_for_color(self, pixel): def bits_for_color(self, pixel):
if pixel == self.white: return self.bits_for_bw(pixel)
return "1"
else:
return "0"
def bits_for_mask(self, pixel): def bits_for_mask(self, pixel):
if pixel == self.key: return self.bits_for_bw_mask(pixel)
return "1"
return "0"
def pixel_color(self, pixel_data, row, col): def pixel_color(self, pixel_data, row, col):
r = pixel_data[row][col*3] r = pixel_data[row][col*3]
@ -980,6 +982,165 @@ class FastFont(Listing):
self.byte("$%02x" % ord(data[index]), 16) 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__": if __name__ == "__main__":
disclaimer = ''' disclaimer = '''
; This file was generated by HiSprite.py, a sprite compiler by Quinn Dunki. ; 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("-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("-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("-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("-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("-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)") 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: for pngfile in options.files:
try: 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: except RuntimeError, e:
print "%s: %s" % (pngfile, e) print "%s: %s" % (pngfile, e)
sys.exit(1) sys.exit(1)
except png.Error, e: except png.Error, e:
print "%s: %s" % (pngfile, e) print "%s: %s" % (pngfile, e)
sys.exit(1) sys.exit(1)
listings.append(sprite_code)
if options.output_prefix: name = options.name if options.name else os.path.splitext(pngfile)[0]
r = RowLookup(assembler, screen) slug = slugify(name)
luts[r.slug] = r
c = ColLookup(assembler, screen) w, h = pngdata[0:2]
luts[c.slug] = c 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())]) listings.extend([luts[k] for k in sorted(luts.keys())])

View File

@ -3,7 +3,7 @@ COLORSPRITE = moldy_burger.png
#COLORSPRITE = apple.png #COLORSPRITE = apple.png
BWSPRITE = apple-sprite9x11.png BWSPRITE = apple-sprite9x11.png
all: cpbg.dsk fonttest.dsk all: cpbg.dsk fonttest.dsk titles.dsk
rowlookup.s: HiSprite.py rowlookup.s: HiSprite.py
python HiSprite.py -a mac65 -p 6502 -r > rowlookup.s 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 cpbg.dsk: HiSprite.py cpbg.xex
atrcopy cpbg.dsk boot -b cpbg.xex --brun 6000 -f atrcopy cpbg.dsk boot -b cpbg.xex --brun 6000 -f
titles.dsk: HiSprite.py cpbg.xex player-missile.hgr: HiSprite.py player-missile.png
atrcopy titles.dsk boot -d partycrasher-software.hgr@2000 player-missile.hgr@4000 -b cpbg.xex --brun 6000 -f 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 fonttest.dsk: fonttest.s fatfont.s
atasm -ofonttest.xex fonttest.s -Lfonttest.var -gfonttest.lst 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 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 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 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