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
rowlookup.s: quicksprite.py
python quicksprite.py -a mac65 -p 6502 -r > rowlookup.s
rowlookup.s: asmgen.py
python asmgen.py -a mac65 -p 6502 -r > rowlookup.s
collookupbw.s: quicksprite.py
python quicksprite.py -a mac65 -p 6502 -s hgrbw -c > collookupbw.s
collookupbw.s: asmgen.py
python asmgen.py -a mac65 -p 6502 -s hgrbw -c > collookupbw.s
collookupcolor.s: quicksprite.py
python quicksprite.py -a mac65 -p 6502 -c > collookupcolor.s
collookupcolor.s: asmgen.py
python asmgen.py -a mac65 -p 6502 -c > collookupcolor.s
bwsprite.s: quicksprite.py collookupbw.s rowlookup.s $(BWSPRITE)
python quicksprite.py -a mac65 -p 6502 -s hgrbw $(BWSPRITE) -n bwsprite -m -b > bwsprite.s
bwsprite.s: asmgen.py collookupbw.s rowlookup.s $(BWSPRITE)
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)
python quicksprite.py -a mac65 -p 6502 -s hgrcolor $(COLORSPRITE) -n colorsprite -m > colorsprite.s
colorsprite.s: asmgen.py collookupcolor.s rowlookup.s $(COLORSPRITE)
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
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
atrcopy colortest.dsk boot -b colortest.xex --brun 6000 -f
cpbg-sprite-driver.s: quicksprite.py $(BWSPRITE)
python quicksprite.py -a mac65 -p 6502 -s hgrbw -m -k -d -g -f fatfont128.dat -o cpbg $(BWSPRITE) $(COLORSPRITE)
cpbg-sprite-driver.s: asmgen.py $(BWSPRITE)
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
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
fonttest.dsk: fonttest.s fatfont.s

View File

@ -1,6 +1,6 @@
===========
Quicksprite
AsmGen
===========
@ -8,8 +8,8 @@ Quicksprite
Abstract
========
Quicksprite - sprite compiler and related utilities for Apple ][ hi-res and
Atari 8-bit software sprites
AsmGen - code generator for sprites, fonts, and images for Apple ][ hi-res and
Atari 8-bit computers
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
@ -34,10 +34,17 @@ Prerequisites
* 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
==========
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)
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
----------------------
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
International (CC BY 4.0), so you are free to use the generated code for
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
def slugify(s):
"""Simplifies ugly strings into something that can be used as an assembler
label.
@ -1242,16 +1244,130 @@ class FastScroll(Listing):
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__":
disclaimer = '''; AUTOGENERATED FILE; DO NOT EDIT!
;
; This file was generated by quicksprite.py, a sprite compiler sponsored by
; the Player/Missile Podcast (based on HiSprite by Quinn Dunki).
; This file was generated by asmgen.py, a 6502 code generator sponsored by
; 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
; Attribution 4.0 International (CC BY 4.0), so you are free to use the code
; in this file for commercial or non-commercial purposes. (The code generator
; itself is licensed under the GPLv3.)
; 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 in
; this file for any purpose. (The code generator itself is licensed under the
; 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.")
@ -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("-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("-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("--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("-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)")
@ -1310,31 +1427,37 @@ if __name__ == "__main__":
sys.exit(0)
for pngfile in options.files:
try:
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)
if pngfile.lower().endswith(".png"):
try:
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)
name = options.name if options.name else os.path.splitext(pngfile)[0]
slug = slugify(name)
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())
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
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
data = np.fromfile(pngfile, dtype=np.uint8)
if options.rle:
listings.append(RLE(assembler, data))
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:
long_description = fp.read()
scripts = ["quicksprite.py"]
scripts = ["asmgen.py"]
setup(name="quicksprite",
setup(name="asmgen",
version="1.0",
author="Rob McMullen",
author_email="feedback@playermissile.com",
url="https://github.com/robmcmullen/quicksprite",
url="https://github.com/robmcmullen/asmgen",
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,
license="GPL",
classifiers=[