Rename to asmgen; added WIP for RLE compression

This commit is contained in:
Rob McMullen 2017-07-26 23:58:16 -07:00
parent 8ab52b1722
commit 4f8cf05853
4 changed files with 183 additions and 53 deletions

View File

@ -3,36 +3,36 @@ BWSPRITE = apple-sprite9x11.png
all: cpbg.dsk fonttest.dsk all: cpbg.dsk fonttest.dsk
rowlookup.s: quicksprite.py rowlookup.s: asmgen.py
python quicksprite.py -a mac65 -p 6502 -r > rowlookup.s python asmgen.py -a mac65 -p 6502 -r > rowlookup.s
collookupbw.s: quicksprite.py collookupbw.s: asmgen.py
python quicksprite.py -a mac65 -p 6502 -s hgrbw -c > collookupbw.s python asmgen.py -a mac65 -p 6502 -s hgrbw -c > collookupbw.s
collookupcolor.s: quicksprite.py collookupcolor.s: asmgen.py
python quicksprite.py -a mac65 -p 6502 -c > collookupcolor.s python asmgen.py -a mac65 -p 6502 -c > collookupcolor.s
bwsprite.s: quicksprite.py collookupbw.s rowlookup.s $(BWSPRITE) bwsprite.s: asmgen.py collookupbw.s rowlookup.s $(BWSPRITE)
python quicksprite.py -a mac65 -p 6502 -s hgrbw $(BWSPRITE) -n bwsprite -m -b > bwsprite.s python asmgen.py -a mac65 -p 6502 -s hgrbw $(BWSPRITE) -n bwsprite -m -b > bwsprite.s
colorsprite.s: quicksprite.py collookupcolor.s rowlookup.s $(COLORSPRITE) colorsprite.s: asmgen.py collookupcolor.s rowlookup.s $(COLORSPRITE)
python quicksprite.py -a mac65 -p 6502 -s hgrcolor $(COLORSPRITE) -n colorsprite -m > colorsprite.s python asmgen.py -a mac65 -p 6502 -s hgrcolor $(COLORSPRITE) -n colorsprite -m > colorsprite.s
bwtest.dsk: quicksprite.py bwtest.s bwsprite.s bwtest.dsk: asmgen.py bwtest.s bwsprite.s
atasm -obwtest.xex bwtest.s -Lbwtest.var -gbwtest.lst atasm -obwtest.xex bwtest.s -Lbwtest.var -gbwtest.lst
atrcopy bwtest.dsk boot -b bwtest.xex --brun 6000 -f atrcopy bwtest.dsk boot -b bwtest.xex --brun 6000 -f
colortest.dsk: quicksprite.py colortest.s bwsprite.s colortest.dsk: asmgen.py colortest.s bwsprite.s
atasm -ocolortest.xex colortest.s -Lcolortest.var -gcolortest.lst atasm -ocolortest.xex colortest.s -Lcolortest.var -gcolortest.lst
atrcopy colortest.dsk boot -b colortest.xex --brun 6000 -f atrcopy colortest.dsk boot -b colortest.xex --brun 6000 -f
cpbg-sprite-driver.s: quicksprite.py $(BWSPRITE) cpbg-sprite-driver.s: asmgen.py $(BWSPRITE)
python quicksprite.py -a mac65 -p 6502 -s hgrbw -m -k -d -g -f fatfont128.dat -o cpbg $(BWSPRITE) $(COLORSPRITE) python asmgen.py -a mac65 -p 6502 -s hgrbw -m -k -d -g -f fatfont128.dat -o cpbg $(BWSPRITE) $(COLORSPRITE)
cpbg.xex: cpbg.s cpbg-sprite-driver.s cpbg.xex: cpbg.s cpbg-sprite-driver.s
atasm -mae -ocpbg.xex cpbg.s -Lcpbg.var -gcpbg.lst atasm -mae -ocpbg.xex cpbg.s -Lcpbg.var -gcpbg.lst
cpbg.dsk: quicksprite.py cpbg.xex cpbg.dsk: asmgen.py cpbg.xex
atrcopy cpbg.dsk boot -b cpbg.xex --brun 6000 -f atrcopy cpbg.dsk boot -b cpbg.xex --brun 6000 -f
fonttest.dsk: fonttest.s fatfont.s fonttest.dsk: fonttest.s fatfont.s

View File

@ -1,6 +1,6 @@
=========== ===========
Quicksprite AsmGen
=========== ===========
@ -8,8 +8,8 @@ Quicksprite
Abstract Abstract
======== ========
Quicksprite - sprite compiler and related utilities for Apple ][ hi-res and AsmGen - code generator for sprites, fonts, and images for Apple ][ hi-res and
Atari 8-bit software sprites Atari 8-bit computers
This program creates 6502 (or 65c02) code that draws sprites using unrolled This program creates 6502 (or 65c02) code that draws sprites using unrolled
loops for each shifted shape. By removing data shifts, image lookups, and loops loops for each shifted shape. By removing data shifts, image lookups, and loops
@ -34,10 +34,17 @@ Prerequisites
* pypng * pypng
Credits
=======
The sample font is modified from Michael Pohoreski's excellent tutorial on
Apple II fonts: https://github.com/Michaelangel007/apple2_hgr_font_tutorial
Disclaimer Disclaimer
========== ==========
Quicksprite, the 6502 sprite compiler sponsored by the Player/Missile Podcast AsmGen, the 6502 code generator sponsored by the Player/Missile Podcast
Copyright (c) 2017 Rob McMullen (feedback@playermissile.com) Copyright (c) 2017 Rob McMullen (feedback@playermissile.com)
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
@ -58,7 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Generated Code License Generated Code License
---------------------- ----------------------
While the code for quicksprite itself is licensed under the GPLv3, the code it While the code for AsmGen itself is licensed under the GPLv3, the code it
produces is licensed under the the Creative Commons Attribution 4.0 produces is licensed under the the Creative Commons Attribution 4.0
International (CC BY 4.0), so you are free to use the generated code for International (CC BY 4.0), so you are free to use the generated code for
commercial or non-commercial purposes. commercial or non-commercial purposes.

View File

@ -11,6 +11,8 @@ import png # package name is "pypng" on pypi.python.org
import numpy as np import numpy as np
def slugify(s): def slugify(s):
"""Simplifies ugly strings into something that can be used as an assembler """Simplifies ugly strings into something that can be used as an assembler
label. label.
@ -1242,16 +1244,130 @@ class FastScroll(Listing):
self.out() self.out()
class RLE(Listing):
def __init__(self, assembler, data):
Listing.__init__(self, assembler)
self.raw = np.asarray(data, dtype=np.uint8)
rle = self.calc_rle()
#c1 = self.compress_pcx(rle)
#c2 = self.compress_high_bit_swap(rle)
c3 = self.compress_run_copy(rle)
def calc_rle(self):
""" run length encoding. Partial credit to R rle function.
Multi datatype arrays catered for including non Numpy
returns: tuple (runlengths, startpositions, values) """
ia = self.raw
n = len(ia)
if n == 0:
return ([], [], [])
else:
y = np.array(ia[1:] != ia[:-1]) # pairwise unequal (string safe)
i = np.append(np.where(y), n - 1) # must include last element posi
z = np.diff(np.append(-1, i)) # run lengths
p = np.cumsum(np.append(0, z))[:-1] # positions
return(z, p, ia[i])
def compress_pcx(self, rle):
run_lengths, pos, values = rle
# Max size will be the same size as the original data. If compressed is
# greater than original, abort
compressed = np.empty(len(self.raw), dtype=np.uint8)
compressed_size = 0
for i in range(len(run_lengths)):
p, r, v = pos[i], run_lengths[i], values[i]
if v < 0x80:
num_tokens = 1
else:
num_tokens = 2
print("%d: run %d of %d (%d)" % (p, r, v, num_tokens))
compressed_size += num_tokens
print("compressed size: %d" % compressed_size)
return compressed_size
def compress_high_bit_swap(self, rle):
run_lengths, pos, values = rle
# Max size will be the same size as the original data. If compressed is
# greater than original, abort
compressed = np.empty(len(self.raw), dtype=np.uint8)
compressed_size = 0
high_bit_set = False
for i in range(len(run_lengths)):
p, r, v = pos[i], run_lengths[i], values[i]
changed = high_bit_set
if r == 1:
if v < 0x80 and not high_bit_set:
num_tokens = 1
elif v > 0x80 and high_bit_set:
num_tokens = 1
elif v == 0x80:
num_tokens = 2
else:
high_bit_set = not high_bit_set
num_tokens = 2
else:
num_tokens = 2
print("%d: run %d of %d (%d)%s" % (p, r, v, num_tokens, "" if changed == high_bit_set else " high bit = %s" % high_bit_set))
compressed_size += num_tokens
print("compressed size: %d" % compressed_size)
return compressed_size
def compress_run_copy(self, rle):
run_lengths, pos, values = rle
# Max size will be the same size as the original data. If compressed is
# greater than original, abort
compressed = np.empty(len(self.raw), dtype=np.uint8)
compressed_size = 0
copy_start = -1
for i in range(len(run_lengths)):
p, r, v = pos[i], run_lengths[i], values[i]
if r < 3:
if copy_start < 0:
copy_start = p
elif copy_start - p + r > 127:
num = p - copy_start
compressed_size += num + 1
print("%d: copy %d (%d)" % (copy_start, num, num + 1))
copy_start = p
else:
if copy_start >= 0:
num = p - copy_start
compressed_size += num + 1
print("%d: copy %d (%d)" % (copy_start, num, num + 1))
copy_start = -1
while r > 2:
num = min(r, 128)
print("%d: run %d of %d (2)" % (p, num, v))
p += num
r -= num
compressed_size += 2
if r > 0:
copy_start = p
if copy_start >= 0:
num = p - copy_start
compressed_size += num + 1
print("%d: copy %d (%d)" % (copy_start, num, num + 1))
print("compressed size: %d" % compressed_size)
return compressed_size
if __name__ == "__main__": if __name__ == "__main__":
disclaimer = '''; AUTOGENERATED FILE; DO NOT EDIT! disclaimer = '''; AUTOGENERATED FILE; DO NOT EDIT!
; ;
; This file was generated by quicksprite.py, a sprite compiler sponsored by ; This file was generated by asmgen.py, a 6502 code generator sponsored by
; the Player/Missile Podcast (based on HiSprite by Quinn Dunki). ; the Player/Missile Podcast. (The sprite compiler is based on HiSprite by
; Quinn Dunki).
; ;
; The code produced by quicksprite is licensed under the Creative Commons ; The code produced by asmgen is licensed under the Creative Commons
; Attribution 4.0 International (CC BY 4.0), so you are free to use the code ; Attribution 4.0 International (CC BY 4.0), so you are free to use the code in
; in this file for commercial or non-commercial purposes. (The code generator ; this file for any purpose. (The code generator itself is licensed under the
; itself is licensed under the GPLv3.) ; GPLv3.)
''' '''
parser = argparse.ArgumentParser(description="Sprite compiler for 65C02/6502 to generate assembly code to render all shifts of the given sprite, optionally with exclusive-or drawing (if background will be non-black). Generated code has conditional compilation directives for the CC65 assembler to allow the same file to be compiled for either architecture.") parser = argparse.ArgumentParser(description="Sprite compiler for 65C02/6502 to generate assembly code to render all shifts of the given sprite, optionally with exclusive-or drawing (if background will be non-black). Generated code has conditional compilation directives for the CC65 assembler to allow the same file to be compiled for either architecture.")
@ -1265,8 +1381,9 @@ if __name__ == "__main__":
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("-i", "--image", default="line", choices=["line", "color","bw"], help="Screen format used for full page image conversion (default: %(default)s)")
parser.add_argument("-l", "--scroll", default=1, type=int, help="Unrolled loop to scroll screen (default: %(default)s)") parser.add_argument("-l", "--scroll", default=0, type=int, help="Unrolled loop to scroll screen (default: %(default)s)")
parser.add_argument("--merge", type=int, nargs="*", help="Merge two HGR images, switching images at the scan line") parser.add_argument("--merge", type=int, nargs="*", help="Merge two HGR images, switching images at the scan line")
parser.add_argument("--rle", action="store_true", default=False, help="Create run-length-encoded version of data (assumed to be an image)")
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)")
@ -1310,31 +1427,37 @@ if __name__ == "__main__":
sys.exit(0) sys.exit(0)
for pngfile in options.files: for pngfile in options.files:
try: if pngfile.lower().endswith(".png"):
reader = png.Reader(pngfile) try:
pngdata = reader.asRGB8() reader = png.Reader(pngfile)
except RuntimeError, e: pngdata = reader.asRGB8()
print "%s: %s" % (pngfile, e) except RuntimeError, e:
sys.exit(1) print "%s: %s" % (pngfile, e)
except png.Error, e: sys.exit(1)
print "%s: %s" % (pngfile, e) except png.Error, e:
sys.exit(1) print "%s: %s" % (pngfile, e)
sys.exit(1)
name = options.name if options.name else os.path.splitext(pngfile)[0] name = options.name if options.name else os.path.splitext(pngfile)[0]
slug = slugify(name) slug = slugify(name)
w, h = pngdata[0:2] w, h = pngdata[0:2]
if w == 280 and h == 192: if w == 280 and h == 192:
# Full screen conversion! # Full screen conversion!
Image(pngdata, name, options.image.lower()) 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
else: else:
sprite_code = Sprite(slug, pngdata, assembler, screen, options.xdraw, options.mask, options.backing_store, options.clobber, options.double_buffer, options.damage, options.processor) data = np.fromfile(pngfile, dtype=np.uint8)
listings.append(sprite_code) if options.rle:
if options.output_prefix: listings.append(RLE(assembler, data))
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

@ -8,15 +8,15 @@ except ImportError:
with open("README.rst", "r") as fp: with open("README.rst", "r") as fp:
long_description = fp.read() long_description = fp.read()
scripts = ["quicksprite.py"] scripts = ["asmgen.py"]
setup(name="quicksprite", setup(name="asmgen",
version="1.0", version="1.0",
author="Rob McMullen", author="Rob McMullen",
author_email="feedback@playermissile.com", author_email="feedback@playermissile.com",
url="https://github.com/robmcmullen/quicksprite", url="https://github.com/robmcmullen/asmgen",
scripts=scripts, scripts=scripts,
description="Sprite compiler for Apple ][ and Atari 8-bit", description="6502 code generator for Apple ][ and Atari 8-bit",
long_description=long_description, long_description=long_description,
license="GPL", license="GPL",
classifiers=[ classifiers=[