2016-07-21 03:39:11 +00:00
#!/usr/bin/python
2017-06-20 22:59:56 +00:00
# system packages
import sys
import os
2017-05-30 21:06:33 +00:00
import argparse
2017-06-20 22:59:56 +00:00
import re
# external packages
import png # package name is "pypng" on pypi.python.org
2016-07-21 03:39:11 +00:00
2017-06-20 22:59:56 +00:00
def slugify ( s ) :
""" Simplifies ugly strings into something that can be used as an assembler
label .
2016-09-10 18:04:57 +00:00
2017-06-20 22:59:56 +00:00
>> > print slugify ( " [Some] _ Article ' s Title-- " )
SOME_ARTICLES_TITLE
2017-05-30 21:06:33 +00:00
2017-06-20 22:59:56 +00:00
From https : / / gist . github . com / dolph / 3622892 #file-slugify-py
"""
2016-07-21 03:39:11 +00:00
2017-06-20 22:59:56 +00:00
# "[Some] _ Article's Title--"
# "[SOME] _ ARTICLE'S TITLE--"
s = s . upper ( )
2016-07-21 03:39:11 +00:00
2017-06-20 22:59:56 +00:00
# "[SOME] _ ARTICLE'S_TITLE--"
# "[SOME]___ARTICLE'S_TITLE__"
for c in [ ' ' , ' - ' , ' . ' , ' / ' ] :
s = s . replace ( c , ' _ ' )
2017-05-30 21:06:33 +00:00
2017-06-20 22:59:56 +00:00
# "[SOME]___ARTICLE'S_TITLE__"
# "SOME___ARTICLES_TITLE__"
s = re . sub ( ' \ W ' , ' ' , s )
2017-06-20 13:37:57 +00:00
2017-06-20 22:59:56 +00:00
# "SOME___ARTICLES_TITLE__"
# "SOME ARTICLES TITLE "
s = s . replace ( ' _ ' , ' ' )
# "SOME ARTICLES TITLE "
# "SOME ARTICLES TITLE "
s = re . sub ( ' \ s+ ' , ' ' , s )
# "SOME ARTICLES TITLE "
# "SOME ARTICLES TITLE"
s = s . strip ( )
# "SOME ARTICLES TITLE"
# "SOME_ARTICLES_TITLE"
s = s . replace ( ' ' , ' _ ' )
return s
2017-06-21 21:03:53 +00:00
class AssemblerSyntax ( object ) :
2017-06-21 02:55:47 +00:00
def asm ( self , text ) :
return " \t %s " % text
def comment ( self , text ) :
return " \t ; %s " % text
def label ( self , text ) :
return text
def byte ( self , text ) :
return self . asm ( " .byte %s " % text )
def word ( self , text ) :
return self . asm ( " .word %s " % text )
def address ( self , text ) :
return self . asm ( " .addr %s " % text )
def origin ( self , text ) :
return self . asm ( " *= %s " % text )
2017-06-21 05:09:27 +00:00
def binary_constant ( self , value ) :
try :
# already a string
_ = len ( value )
return " # %% %s " % value
except TypeError :
return " # %s " % format ( value , " 08b " )
2017-06-21 02:55:47 +00:00
2017-06-21 21:03:53 +00:00
class Mac65 ( AssemblerSyntax ) :
2017-06-21 02:55:47 +00:00
def address ( self , text ) :
return self . asm ( " .word %s " % text )
2017-06-21 05:09:27 +00:00
def binary_constant ( self , value ) :
# MAC/65 doesn't do binary constants
try :
# a string
value = int ( value , 2 )
except TypeError :
pass
return " #$ %02x ; %s " % ( value , format ( value , " 08b " ) )
2017-06-21 02:55:47 +00:00
2017-06-21 21:03:53 +00:00
class CC65 ( AssemblerSyntax ) :
2017-06-21 02:55:47 +00:00
def label ( self , text ) :
return " %s : " % text
2017-06-20 22:59:56 +00:00
class Listing ( object ) :
2017-06-21 02:55:47 +00:00
def __init__ ( self , assembler ) :
self . assembler = assembler
2017-06-21 21:11:48 +00:00
self . lines = [ ]
2017-06-21 04:46:16 +00:00
self . current = None
self . desired_count = 1
self . stash_list = [ ]
2017-06-20 22:59:56 +00:00
def __str__ ( self ) :
2017-06-21 04:46:16 +00:00
self . flush_stash ( )
2017-06-20 22:59:56 +00:00
return " \n " . join ( self . lines ) + " \n "
def out ( self , line ) :
2017-06-21 04:46:16 +00:00
self . flush_stash ( )
2017-06-20 22:59:56 +00:00
self . lines . append ( line )
2017-06-21 02:55:47 +00:00
def out_append_last ( self , line ) :
self . lines [ - 1 ] + = line
def label ( self , text ) :
self . out ( self . assembler . label ( text ) )
def comment ( self , text ) :
self . out_append_last ( self . assembler . comment ( text ) )
2017-06-21 21:03:53 +00:00
def comment_line ( self , text ) :
self . out ( self . assembler . comment ( text ) )
2017-06-21 02:55:47 +00:00
def asm ( self , text ) :
self . out ( self . assembler . asm ( text ) )
def addr ( self , text ) :
self . out ( self . assembler . address ( text ) )
2017-06-21 04:46:16 +00:00
def flush_stash ( self ) :
if self . current is not None and len ( self . stash_list ) > 0 :
self . lines . append ( self . current ( " , " . join ( self . stash_list ) ) )
self . current = None
self . stash_list = [ ]
self . desired_count = 1
def stash ( self , desired , text , per_line ) :
if self . current is not None and ( self . current != desired or per_line == 1 ) :
self . flush_stash ( )
if per_line > 1 :
if self . current is None :
self . current = desired
self . desired_count = per_line
self . stash_list . append ( text )
if len ( self . stash_list ) > = self . desired_count :
self . flush_stash ( )
else :
self . out ( desired ( text ) )
2017-06-21 02:55:47 +00:00
2017-06-21 05:09:27 +00:00
def binary_constant ( self , value ) :
return self . assembler . binary_constant ( value )
2017-06-21 04:46:16 +00:00
def byte ( self , text , per_line = 1 ) :
self . stash ( self . assembler . byte , text , per_line )
def word ( self , text , per_line = 1 ) :
self . stash ( self . assembler . word , text , per_line )
2017-06-21 02:55:47 +00:00
2017-06-20 22:59:56 +00:00
class Sprite ( Listing ) :
2017-06-22 18:09:50 +00:00
def __init__ ( self , pngfile , assembler , screen , xdraw = False , use_mask = False , processor = " any " , name = " " ) :
2017-06-21 02:55:47 +00:00
Listing . __init__ ( self , assembler )
2017-06-21 21:03:53 +00:00
self . screen = screen
2017-06-20 22:59:56 +00:00
reader = png . Reader ( pngfile )
2017-06-22 05:44:45 +00:00
pngdata = reader . asRGB8 ( )
2017-06-20 22:59:56 +00:00
self . xdraw = xdraw
2017-06-22 18:09:50 +00:00
self . use_mask = use_mask
2017-06-21 02:55:47 +00:00
self . processor = processor
2017-06-22 16:39:40 +00:00
if not name :
name = os . path . splitext ( pngfile ) [ 0 ]
self . niceName = slugify ( name )
2017-06-20 22:59:56 +00:00
self . width = pngdata [ 0 ]
self . height = pngdata [ 1 ]
self . pixelData = list ( pngdata [ 2 ] )
self . jumpTable ( )
2017-06-21 21:03:53 +00:00
for i in range ( self . screen . numShifts ) :
2017-06-20 22:59:56 +00:00
self . blitShift ( i )
def jumpTable ( self ) :
# Prologue
2017-06-21 02:55:47 +00:00
self . label ( " %s " % self . niceName )
2017-06-21 21:03:53 +00:00
self . comment ( " %d bytes per row " % self . screen . byteWidth ( self . width ) )
2017-06-21 02:55:47 +00:00
if self . processor == " any " :
self . out ( " .ifpC02 " )
self . jump65C02 ( )
self . out ( " .else " )
self . jump6502 ( )
self . out ( " .endif " )
elif self . processor == " 65C02 " :
self . jump65C02 ( )
elif self . processor == " 6502 " :
self . jump6502 ( )
else :
raise RuntimeError ( " Processor %s not supported " % self . processor )
2017-06-21 21:28:22 +00:00
def save_axy_65C02 ( self ) :
self . asm ( " pha " )
self . asm ( " phx " )
self . asm ( " phy " )
def restore_axy_65C02 ( self ) :
self . asm ( " ply " )
self . asm ( " plx " )
self . asm ( " pla " )
def save_axy_6502 ( self ) :
self . asm ( " pha " )
self . asm ( " txa " )
self . asm ( " pha " )
self . asm ( " tya " )
self . asm ( " pha " )
def restore_axy_6502 ( self ) :
self . asm ( " pla " )
self . asm ( " tay " )
self . asm ( " pla " )
self . asm ( " tax " )
self . asm ( " pla " )
2017-06-21 02:55:47 +00:00
def jump65C02 ( self ) :
2017-06-21 21:28:22 +00:00
self . save_axy_65C02 ( )
self . asm ( " ldy PARAM0 " )
self . asm ( " ldx MOD %d _ %d ,y " % ( self . screen . numShifts , self . screen . bitsPerPixel ) )
2017-06-21 02:55:47 +00:00
self . asm ( " jmp ( %s _JMP,x) \n " % ( self . niceName ) )
2017-06-20 22:59:56 +00:00
offset_suffix = " "
# Bit-shift jump table for 65C02
2017-06-21 02:55:47 +00:00
self . label ( " %s _JMP " % ( self . niceName ) )
2017-06-21 21:03:53 +00:00
for shift in range ( self . screen . numShifts ) :
2017-06-21 02:55:47 +00:00
self . addr ( " %s _SHIFT %d " % ( self . niceName , shift ) )
2017-06-20 22:59:56 +00:00
2017-06-21 02:55:47 +00:00
def jump6502 ( self ) :
2017-06-21 21:28:22 +00:00
self . save_axy_6502 ( )
self . asm ( " ldy PARAM0 " )
self . asm ( " ldx MOD %d _ %d ,y " % ( self . screen . numShifts , self . screen . bitsPerPixel ) )
2017-06-20 22:59:56 +00:00
# Fast jump table routine; faster and smaller than self-modifying code
2017-06-21 02:55:47 +00:00
self . asm ( " lda %s _JMP+1,x " % ( self . niceName ) )
self . asm ( " pha " )
self . asm ( " lda %s _JMP,x " % ( self . niceName ) )
self . asm ( " pha " )
self . asm ( " rts \n " )
2017-06-20 22:59:56 +00:00
# Bit-shift jump table for generic 6502
2017-06-21 02:55:47 +00:00
self . label ( " %s _JMP " % ( self . niceName ) )
2017-06-21 21:03:53 +00:00
for shift in range ( self . screen . numShifts ) :
2017-06-21 02:55:47 +00:00
self . addr ( " %s _SHIFT %d -1 " % ( self . niceName , shift ) )
2017-06-20 22:59:56 +00:00
def blitShift ( self , shift ) :
# Blitting functions
self . out ( " \n " )
2017-06-20 13:37:57 +00:00
# Track cycle count of the blitter. We start with fixed overhead:
# SAVE_AXY + RESTORE_AXY + rts + sprite jump table
cycleCount = 9 + 12 + 6 + 3 + 4 + 6
2017-06-21 02:55:47 +00:00
self . label ( " %s _SHIFT %d " % ( self . niceName , shift ) )
2017-06-21 21:03:53 +00:00
colorStreams = self . screen . byteStreamsFromPixels ( shift , self )
for c in colorStreams :
self . comment_line ( str ( c ) )
self . out ( " " )
maskStreams = self . screen . byteStreamsFromPixels ( shift , self , True )
2017-06-21 02:55:47 +00:00
self . asm ( " ldx PARAM1 " )
2017-06-20 13:37:57 +00:00
cycleCount + = 3
2017-06-20 22:59:56 +00:00
rowStartCode , extraCycles = self . rowStartCalculatorCode ( ) ;
self . out ( rowStartCode )
2017-06-20 13:37:57 +00:00
cycleCount + = extraCycles
2017-06-21 21:28:22 +00:00
spriteChunks , cycleCount , optimizationCount = self . generateBlitter ( colorStreams , maskStreams , cycleCount )
2017-06-20 13:37:57 +00:00
2017-06-20 22:59:56 +00:00
for row in range ( self . height ) :
2017-06-20 13:37:57 +00:00
for chunkIndex in range ( len ( spriteChunks ) ) :
2017-06-20 22:59:56 +00:00
self . out ( spriteChunks [ chunkIndex ] [ row ] )
2016-12-25 20:43:14 +00:00
2017-06-21 21:28:22 +00:00
if self . processor == " any " :
self . out ( " .ifpC02 " )
self . restore_axy_65C02 ( )
self . out ( " .else " )
self . restore_axy_6502 ( )
self . out ( " .endif " )
elif self . processor == " 65C02 " :
self . restore_axy_65C02 ( )
elif self . processor == " 6502 " :
self . restore_axy_6502 ( )
else :
raise RuntimeError ( " Processor %s not supported " % self . processor )
self . asm ( " rts " )
self . comment ( " Cycle count: %d , Optimized %d rows. " % ( cycleCount , optimizationCount ) )
2017-06-20 22:59:56 +00:00
def generateBlitter ( self , colorStreams , maskStreams , baseCycleCount ) :
byteWidth = len ( colorStreams [ 0 ] )
spriteChunks = [ [ " " for y in range ( self . height ) ] for x in range ( byteWidth ) ]
2017-06-20 13:37:57 +00:00
2017-06-20 22:59:56 +00:00
cycleCount = baseCycleCount
optimizationCount = 0
for row in range ( self . height ) :
2017-06-20 13:37:57 +00:00
2017-06-20 22:59:56 +00:00
byteSplits = colorStreams [ row ]
2017-06-22 17:36:04 +00:00
maskSplits = maskStreams [ row ]
2017-06-20 13:37:57 +00:00
2017-06-20 22:59:56 +00:00
# Generate blitting code
for chunkIndex in range ( len ( byteSplits ) ) :
# Optimization
2017-06-22 18:09:50 +00:00
if maskSplits [ chunkIndex ] == " 01111111 " :
2017-06-22 17:36:04 +00:00
optimizationCount + = 1
else :
2017-06-21 05:09:27 +00:00
value = self . binary_constant ( byteSplits [ chunkIndex ] )
2017-06-20 22:59:56 +00:00
# Store byte into video memory
if self . xdraw :
spriteChunks [ chunkIndex ] [ row ] = \
" \t lda (SCRATCH0),y \n " + \
2017-06-21 05:09:27 +00:00
" \t eor %s \n " % value + \
2017-06-20 22:59:56 +00:00
" \t sta (SCRATCH0),y \n " ;
cycleCount + = 5 + 2 + 6
2017-06-22 18:09:50 +00:00
elif self . use_mask :
mask = self . binary_constant ( maskSplits [ chunkIndex ] )
spriteChunks [ chunkIndex ] [ row ] = \
" \t lda (SCRATCH0),y \n " + \
" \t and %s \n " % mask + \
" \t ora %s \n " % value + \
" \t sta (SCRATCH0),y \n " ;
cycleCount + = 5 + 2 + 6
2017-06-20 22:59:56 +00:00
else :
spriteChunks [ chunkIndex ] [ row ] = \
2017-06-21 05:09:27 +00:00
" \t lda %s \n " % value + \
2017-06-20 22:59:56 +00:00
" \t sta (SCRATCH0),y \n " ;
cycleCount + = 2 + 6
# Increment indices
if chunkIndex == len ( byteSplits ) - 1 :
spriteChunks [ chunkIndex ] [ row ] + = " \n "
else :
spriteChunks [ chunkIndex ] [ row ] + = " \t iny "
cycleCount + = 2
# Finish the row
if row < self . height - 1 :
rowStartCode , extraCycles = self . rowStartCalculatorCode ( )
spriteChunks [ chunkIndex ] [ row ] + = " \t inx \n " + rowStartCode ;
cycleCount + = 2 + extraCycles
2017-06-20 13:37:57 +00:00
2017-06-21 21:28:22 +00:00
return spriteChunks , cycleCount , optimizationCount
2016-08-18 19:12:11 +00:00
2017-06-20 22:59:56 +00:00
def rowStartCalculatorCode ( self ) :
return \
" \t lda HGRROWS_H1,x \n " + \
" \t sta SCRATCH1 \n " + \
" \t lda HGRROWS_L,x \n " + \
" \t sta SCRATCH0 \n " + \
" \t ldy PARAM0 \n " + \
2017-06-21 21:03:53 +00:00
" \t lda DIV %d _ %d ,y \n " % ( self . screen . numShifts , self . screen . bitsPerPixel ) + \
2017-06-20 22:59:56 +00:00
" \t tay \n " , 4 + 3 + 4 + 3 + 3 + 4 + 2 ;
2016-12-25 20:43:14 +00:00
2017-06-22 18:09:50 +00:00
def shiftStringRight ( string , shift , bitsPerPixel , fillerBit ) :
2017-06-20 13:37:57 +00:00
if shift == 0 :
return string
2017-06-21 21:03:53 +00:00
shift * = bitsPerPixel
2017-06-20 13:37:57 +00:00
result = " "
for i in range ( shift ) :
2017-06-22 18:09:50 +00:00
result + = fillerBit
2017-06-20 13:37:57 +00:00
result + = string
return result
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
class ScreenFormat ( object ) :
numShifts = 8
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
bitsPerPixel = 1
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
screenWidth = 320
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
screenHeight = 192
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def __init__ ( self ) :
self . offsets = self . generate_row_offsets ( )
self . numX = self . screenWidth / self . bitsPerPixel
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def byteWidth ( self , png_width ) :
return ( png_width * self . bitsPerPixel + self . numShifts - 1 ) / / self . numShifts + 1
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def bitsForColor ( self , pixel ) :
raise NotImplementedError
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def bitsForMask ( self , pixel ) :
raise NotImplementedError
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def pixelColor ( self , pixelData , row , col ) :
raise NotImplementedError
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def generate_row_offsets ( self ) :
offsets = [ 40 * y for y in range ( self . screenHeight ) ]
return offsets
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def generate_row_addresses ( self , baseAddr ) :
addrs = [ baseAddr + offset for offset in self . offsets ]
return addrs
class HGR ( ScreenFormat ) :
numShifts = 7
bitsPerPixel = 2
screenWidth = 280
black , magenta , green , orange , blue , white , key = range ( 7 )
def bitsForColor ( self , pixel ) :
2017-06-22 17:36:04 +00:00
if pixel == self . black or pixel == self . key :
2017-06-21 21:03:53 +00:00
return " 00 "
2017-06-20 13:37:57 +00:00
else :
2017-06-21 21:03:53 +00:00
if pixel == self . white :
return " 11 "
2017-06-20 13:37:57 +00:00
else :
2017-06-21 21:03:53 +00:00
if pixel == self . green or pixel == self . orange :
return " 01 "
# blue or magenta
return " 10 "
def bitsForMask ( self , pixel ) :
2017-06-22 17:36:04 +00:00
if pixel == self . key :
2017-06-22 18:09:50 +00:00
return " 11 "
2017-06-21 21:03:53 +00:00
2017-06-22 18:09:50 +00:00
return " 00 "
2017-06-21 21:03:53 +00:00
def highBitForColor ( self , pixel ) :
# Note that we prefer high-bit white because blue fringe is less noticeable than magenta.
highBit = " 0 "
if pixel == self . orange or pixel == self . blue or pixel == self . white :
highBit = " 1 "
return highBit
def highBitForMask ( self , pixel ) :
2017-06-22 17:36:04 +00:00
return " 0 "
2017-06-21 21:03:53 +00:00
def pixelColor ( self , pixelData , row , col ) :
r = pixelData [ row ] [ col * 3 ]
g = pixelData [ row ] [ col * 3 + 1 ]
b = pixelData [ row ] [ col * 3 + 2 ]
2017-06-22 17:01:52 +00:00
rhi = r == 255
rlo = r == 0
ghi = g == 255
glo = g == 0
bhi = b == 255
blo = b == 0
if rhi and ghi and bhi :
color = self . white
elif rlo and glo and blo :
color = self . black
elif rhi and bhi :
2017-06-21 21:03:53 +00:00
color = self . magenta
2017-06-22 17:01:52 +00:00
elif rhi and g > 0 :
color = self . orange
elif bhi :
color = self . blue
elif ghi :
color = self . green
2017-06-21 21:03:53 +00:00
else :
2017-06-22 17:01:52 +00:00
# anything else is chroma key
color = self . key
2017-06-21 21:03:53 +00:00
return color
def byteStreamsFromPixels ( self , shift , source , mask = False ) :
byteStreams = [ " " for x in range ( source . height ) ]
byteWidth = self . byteWidth ( source . width )
if mask :
bitDelegate = self . bitsForMask
highBitDelegate = self . highBitForMask
2017-06-22 18:09:50 +00:00
fillerBit = " 1 "
2017-06-21 21:03:53 +00:00
else :
bitDelegate = self . bitsForColor
highBitDelegate = self . highBitForColor
2017-06-22 18:09:50 +00:00
fillerBit = " 0 "
2016-07-21 03:39:11 +00:00
2017-06-21 21:03:53 +00:00
for row in range ( source . height ) :
bitStream = " "
2017-06-22 17:27:58 +00:00
highBit = " 0 "
highBitFound = False
2017-06-21 21:03:53 +00:00
# Compute raw bitstream for row from PNG pixels
for pixelIndex in range ( source . width ) :
pixel = self . pixelColor ( source . pixelData , row , pixelIndex )
bitStream + = bitDelegate ( pixel )
2017-06-22 17:27:58 +00:00
# Determine palette bit from first non-black pixel on each row
if not highBitFound and pixel != self . black and pixel != self . key :
highBit = highBitDelegate ( pixel )
highBitFound = True
2017-06-21 21:03:53 +00:00
# Shift bit stream as needed
2017-06-22 18:09:50 +00:00
bitStream = shiftStringRight ( bitStream , shift , self . bitsPerPixel , fillerBit )
2017-06-21 21:03:53 +00:00
bitStream = bitStream [ : byteWidth * 8 ]
# Split bitstream into bytes
bitPos = 0
byteSplits = [ 0 for x in range ( byteWidth ) ]
for byteIndex in range ( byteWidth ) :
remainingBits = len ( bitStream ) - bitPos
bitChunk = " "
if remainingBits < 0 :
2017-06-22 18:09:50 +00:00
bitChunk = fillerBit * 7
2017-06-21 21:03:53 +00:00
else :
if remainingBits < 7 :
bitChunk = bitStream [ bitPos : ]
2017-06-22 18:09:50 +00:00
bitChunk + = fillerBit * ( 7 - remainingBits )
2017-06-21 21:03:53 +00:00
else :
bitChunk = bitStream [ bitPos : bitPos + 7 ]
bitChunk = bitChunk [ : : - 1 ]
byteSplits [ byteIndex ] = highBit + bitChunk
bitPos + = 7
byteStreams [ row ] = byteSplits ;
2016-08-18 19:12:11 +00:00
2017-06-21 21:03:53 +00:00
return byteStreams
2017-05-30 21:06:33 +00:00
2017-06-21 21:03:53 +00:00
def generate_row_offsets ( self ) :
2017-06-21 02:55:47 +00:00
offsets = [ ]
2017-06-21 21:03:53 +00:00
for y in range ( self . screenHeight ) :
2017-06-21 02:55:47 +00:00
# From Apple Graphics and Arcade Game Design
a = y / / 64
d = y - ( 64 * a )
b = d / / 8
c = d - 8 * b
offsets . append ( ( 1024 * c ) + ( 128 * b ) + ( 40 * a ) )
2017-06-21 21:03:53 +00:00
return offsets
class HGRBW ( HGR ) :
bitsPerPixel = 1
def bitsForColor ( self , pixel ) :
if pixel == self . white :
return " 1 "
else :
return " 0 "
def bitsForMask ( self , pixel ) :
if pixel == self . key :
return " 0 "
return " 1 "
def pixelColor ( self , pixelData , row , col ) :
r = pixelData [ row ] [ col * 3 ]
g = pixelData [ row ] [ col * 3 + 1 ]
b = pixelData [ row ] [ col * 3 + 2 ]
color = self . black
if r == 255 and g == 255 and b == 255 :
color = self . white
elif r == g and r == b and r != 0 and r != 255 : # Any gray is chroma key
color = self . key
else :
color = self . black
return color
2017-06-21 02:55:47 +00:00
2017-06-21 21:11:48 +00:00
class RowLookup ( Listing ) :
2017-06-21 21:03:53 +00:00
def __init__ ( self , assembler , screen ) :
Listing . __init__ ( self , assembler )
self . generate_y ( screen )
def generate_y ( self , screen ) :
2017-06-21 02:55:47 +00:00
self . label ( " HGRROWS_H1 " )
2017-06-21 21:03:53 +00:00
for addr in screen . generate_row_addresses ( 0x2000 ) :
2017-06-21 04:46:16 +00:00
self . byte ( " $ %02x " % ( addr / / 256 ) , 8 )
2017-06-21 02:55:47 +00:00
2017-06-21 04:46:16 +00:00
self . out ( " \n " )
2017-06-21 02:55:47 +00:00
self . label ( " HGRROWS_H2 " )
2017-06-21 21:03:53 +00:00
for addr in screen . generate_row_addresses ( 0x4000 ) :
2017-06-21 04:46:16 +00:00
self . byte ( " $ %02x " % ( addr / / 256 ) , 8 )
2017-06-21 02:55:47 +00:00
2017-06-21 04:46:16 +00:00
self . out ( " \n " )
2017-06-21 02:55:47 +00:00
self . label ( " HGRROWS_L " )
2017-06-21 21:03:53 +00:00
for addr in screen . generate_row_addresses ( 0x2000 ) :
2017-06-21 04:46:16 +00:00
self . byte ( " $ %02x " % ( addr & 0xff ) , 8 )
2017-06-21 02:55:47 +00:00
2017-06-21 21:11:48 +00:00
class ColLookup ( Listing ) :
def __init__ ( self , assembler , screen ) :
Listing . __init__ ( self , assembler )
self . generate_x ( screen )
2017-06-21 21:03:53 +00:00
def generate_x ( self , screen ) :
self . out ( " \n " )
self . label ( " DIV %d _ %d " % ( screen . numShifts , screen . bitsPerPixel ) )
for pixel in range ( screen . numX ) :
self . byte ( " $ %02x " % ( ( pixel / screen . numShifts ) * screen . bitsPerPixel ) , screen . numShifts )
2016-07-21 03:39:11 +00:00
2017-06-21 02:55:47 +00:00
self . out ( " \n " )
2017-06-21 21:03:53 +00:00
self . label ( " MOD %d _ %d " % ( screen . numShifts , screen . bitsPerPixel ) )
for pixel in range ( screen . numX ) :
self . byte ( " $ %02x " % ( ( pixel % screen . numShifts ) * screen . bitsPerPixel ) , screen . numShifts )
2016-07-21 03:39:11 +00:00
if __name__ == " __main__ " :
2017-06-21 21:11:48 +00:00
disclaimer = '''
; This file was generated by HiSprite . py , a sprite compiler by Quinn Dunki .
; If you feel the need to modify this file , you are probably doing it wrong .
'''
2017-06-20 22:59:56 +00:00
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 . add_argument ( " -v " , " --verbose " , default = 0 , action = " count " )
2017-06-21 21:11:48 +00:00
parser . add_argument ( " -c " , " --cols " , action = " store_true " , default = False , help = " output column (x position) lookup tables " )
parser . add_argument ( " -r " , " --rows " , action = " store_true " , default = False , help = " output row (y position) lookup tables " )
2017-06-20 22:59:56 +00:00
parser . add_argument ( " -x " , " --xdraw " , action = " store_true " , default = False , help = " use XOR for sprite drawing " )
2017-06-22 18:09:50 +00:00
parser . add_argument ( " -m " , " --mask " , action = " store_true " , default = False , help = " use mask for sprite drawing " )
2017-06-21 21:03:53 +00:00
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 ) " )
2017-06-22 16:39:40 +00:00
parser . add_argument ( " -n " , " --name " , default = " " , help = " Name for generated assembly function (default: based on image filename) " )
2017-06-20 22:59:56 +00:00
parser . add_argument ( " files " , metavar = " IMAGE " , nargs = " * " , help = " a PNG image [or a list of them]. PNG files must not have an alpha channel! " )
options , extra_args = parser . parse_known_args ( )
2017-06-21 21:03:53 +00:00
if options . assembler . lower ( ) == " cc65 " :
assembler = CC65 ( )
elif options . assembler . lower ( ) == " mac65 " :
assembler = Mac65 ( )
else :
print ( " Unknown assembler %s " % options . assembler )
parser . print_help ( )
2017-06-22 05:44:45 +00:00
sys . exit ( 1 )
2017-06-21 21:03:53 +00:00
if options . screen . lower ( ) == " hgrcolor " :
screen = HGR ( )
elif options . screen . lower ( ) == " hgrbw " :
screen = HGRBW ( )
2017-06-21 02:55:47 +00:00
else :
2017-06-21 21:03:53 +00:00
print ( " Unknown screen format %s " % options . screen )
2017-06-21 02:55:47 +00:00
parser . print_help ( )
2017-06-22 05:44:45 +00:00
sys . exit ( 1 )
2017-06-21 02:55:47 +00:00
2017-06-21 21:11:48 +00:00
listings = [ ]
2017-06-20 22:59:56 +00:00
for pngfile in options . files :
try :
2017-06-22 18:09:50 +00:00
listings . append ( Sprite ( pngfile , assembler , screen , options . xdraw , options . mask , options . processor , options . name ) )
2017-06-21 02:55:47 +00:00
except RuntimeError , e :
2017-06-22 05:44:45 +00:00
print " %s : %s " % ( pngfile , e )
sys . exit ( 1 )
except png . Error , e :
print " %s : %s " % ( pngfile , e )
sys . exit ( 1 )
2017-06-21 21:11:48 +00:00
if options . rows :
listings . append ( RowLookup ( assembler , screen ) )
if options . cols :
listings . append ( ColLookup ( assembler , screen ) )
if listings :
print disclaimer
for section in listings :
print section