2018-07-23 19:18:14 +00:00
#!/usr/bin/env python
2016-07-21 03:39:11 +00:00
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
2017-07-16 05:41:07 +00:00
import numpy as np
2017-06-20 22:59:56 +00:00
2016-07-21 03:39:11 +00:00
2017-07-27 06:58:16 +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-30 06:14:39 +00:00
extension = " s "
2017-06-30 19:25:49 +00:00
comment_char = " ; "
2017-06-30 06:14:39 +00:00
2017-06-21 02:55:47 +00:00
def asm ( self , text ) :
return " \t %s " % text
def comment ( self , text ) :
2017-06-30 19:25:49 +00:00
return " \t %s %s " % ( self . comment_char , text )
2017-06-21 02:55:47 +00:00
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-30 06:14:39 +00:00
def include ( self , text ) :
return self . asm ( " .include \" %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 :
2017-06-26 19:40:19 +00:00
return " # %% %s " % format ( value , " 08b " )
2017-06-21 05:09:27 +00:00
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 ) :
try :
# a string
value = int ( value , 2 )
except TypeError :
pass
2017-06-26 19:40:19 +00:00
return " #~ %s " % format ( value , " 08b " )
2017-06-21 05:09:27 +00:00
2017-06-21 02:55:47 +00:00
2017-06-21 21:03:53 +00:00
class CC65 ( AssemblerSyntax ) :
2017-06-30 06:14:39 +00:00
extension = " s "
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-09-20 16:54:32 +00:00
def __init__ ( self , assembler , slug = " asmgen-driver " ) :
2017-06-21 02:55:47 +00:00
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-07-28 16:28:22 +00:00
self . slug = slug
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 "
2017-07-01 05:58:42 +00:00
def add_listing ( self , other ) :
self . lines . extend ( other . lines )
2017-06-30 06:14:39 +00:00
def get_filename ( self , basename ) :
return " %s - %s . %s " % ( basename , self . slug . lower ( ) , self . assembler . extension )
def write ( self , basename , disclaimer ) :
filename = self . get_filename ( basename )
2018-07-02 04:23:56 +00:00
print ( f " Writing to { filename } " )
2017-06-30 06:14:39 +00:00
with open ( filename , " w " ) as fh :
fh . write ( disclaimer + " \n \n " )
fh . write ( str ( self ) )
return filename
2017-06-30 19:25:49 +00:00
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
2017-06-30 19:25:49 +00:00
def pop_asm ( self , cmd = " " ) :
self . flush_stash ( )
if cmd :
search = self . assembler . asm ( cmd )
i = - 1
while self . lines [ i ] . strip ( ) . startswith ( self . assembler . comment_char ) :
i - = 1
if self . lines [ i ] == search :
self . lines . pop ( i )
else :
self . lines . pop ( - 1 )
2017-06-21 02:55:47 +00:00
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-30 06:14:39 +00:00
def include ( self , text ) :
self . out ( self . assembler . include ( 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-30 06:14:39 +00:00
backing_store_sizes = set ( )
2017-07-16 05:41:07 +00:00
def __init__ ( self , slug , pngdata , assembler , screen , xdraw = False , use_mask = False , backing_store = False , clobber = False , double_buffer = False , damage = False , processor = " any " ) :
2017-06-21 02:55:47 +00:00
Listing . __init__ ( self , assembler )
2017-07-16 05:41:07 +00:00
self . slug = slug
2017-06-21 21:03:53 +00:00
self . screen = screen
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-29 18:48:25 +00:00
self . backing_store = backing_store
2017-06-30 19:52:15 +00:00
self . clobber = clobber
2017-07-03 04:38:32 +00:00
self . double_buffer = double_buffer
2017-07-04 04:22:58 +00:00
self . damage = damage
2017-06-21 02:55:47 +00:00
self . processor = processor
2017-06-20 22:59:56 +00:00
self . width = pngdata [ 0 ]
self . height = pngdata [ 1 ]
2017-07-05 19:50:22 +00:00
self . pixel_data = list ( pngdata [ 2 ] )
self . jump_table ( )
for i in range ( self . screen . num_shifts ) :
self . blit_shift ( i )
2017-06-20 22:59:56 +00:00
2017-07-05 19:50:22 +00:00
def jump_table ( self ) :
2017-06-20 22:59:56 +00:00
# Prologue
2017-06-30 06:14:39 +00:00
self . label ( " %s " % self . slug )
2017-07-05 19:50:22 +00:00
self . comment ( " %d bytes per row " % self . screen . byte_width ( 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-30 19:52:15 +00:00
if not self . clobber :
self . save_axy_65C02 ( )
2017-07-05 19:50:22 +00:00
self . asm ( " ldy param_x " )
self . asm ( " ldx MOD %d _ %d ,y " % ( self . screen . num_shifts , self . screen . bits_per_pixel ) )
2017-06-21 21:28:22 +00:00
2017-06-30 06:14:39 +00:00
self . asm ( " jmp ( %s _JMP,x) \n " % ( self . slug ) )
2017-06-20 22:59:56 +00:00
offset_suffix = " "
# Bit-shift jump table for 65C02
2017-06-30 06:14:39 +00:00
self . label ( " %s _JMP " % ( self . slug ) )
2017-07-05 19:50:22 +00:00
for shift in range ( self . screen . num_shifts ) :
2017-06-30 06:14:39 +00:00
self . addr ( " %s _SHIFT %d " % ( self . slug , shift ) )
2017-06-20 22:59:56 +00:00
2017-06-21 02:55:47 +00:00
def jump6502 ( self ) :
2017-06-30 19:52:15 +00:00
if not self . clobber :
self . save_axy_6502 ( )
2017-07-05 19:50:22 +00:00
self . asm ( " ldy param_x " )
self . asm ( " ldx MOD %d _ %d ,y " % ( self . screen . num_shifts , self . screen . bits_per_pixel ) )
2017-06-21 21:28:22 +00:00
2017-06-20 22:59:56 +00:00
# Fast jump table routine; faster and smaller than self-modifying code
2017-06-30 06:14:39 +00:00
self . asm ( " lda %s _JMP+1,x " % ( self . slug ) )
2017-06-21 02:55:47 +00:00
self . asm ( " pha " )
2017-06-30 06:14:39 +00:00
self . asm ( " lda %s _JMP,x " % ( self . slug ) )
2017-06-21 02:55:47 +00:00
self . asm ( " pha " )
self . asm ( " rts \n " )
2017-06-20 22:59:56 +00:00
# Bit-shift jump table for generic 6502
2017-06-30 06:14:39 +00:00
self . label ( " %s _JMP " % ( self . slug ) )
2017-07-05 19:50:22 +00:00
for shift in range ( self . screen . num_shifts ) :
2017-06-30 06:14:39 +00:00
self . addr ( " %s _SHIFT %d -1 " % ( self . slug , shift ) )
2017-06-20 22:59:56 +00:00
2017-07-05 19:50:22 +00:00
def blit_shift ( self , shift ) :
2017-06-20 22:59:56 +00:00
# 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
2017-07-05 19:50:22 +00:00
cycle_count = 9 + 12 + 6 + 3 + 4 + 6
2017-06-20 13:37:57 +00:00
2017-07-03 04:38:32 +00:00
baselabel = " %s _SHIFT %d " % ( self . slug , shift )
self . label ( baselabel )
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
color_streams = self . screen . byte_streams_from_pixels ( shift , self )
mask_streams = self . screen . byte_streams_from_pixels ( shift , self , True )
for c , m in zip ( color_streams , mask_streams ) :
2017-06-26 04:13:58 +00:00
self . comment_line ( str ( c ) + " " + str ( m ) )
self . out ( " " )
2017-06-21 21:03:53 +00:00
2017-06-29 18:48:25 +00:00
if self . backing_store :
2017-07-05 19:50:22 +00:00
byte_width = len ( color_streams [ 0 ] )
self . asm ( " jsr savebg_ %d x %d " % ( byte_width , self . height ) )
self . backing_store_sizes . add ( ( byte_width , self . height ) )
cycle_count + = 6
2017-06-20 13:37:57 +00:00
2017-07-05 19:50:22 +00:00
cycle_count , optimization_count = self . generate_blitter ( color_streams , mask_streams , cycle_count , baselabel )
2016-12-25 20:43:14 +00:00
2017-06-30 19:52:15 +00:00
if not self . clobber :
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 )
2017-07-04 04:22:58 +00:00
if self . damage :
2017-07-05 19:50:22 +00:00
# the caller knows param_x and param_y for location, so no need
# to report those again. But the size varies by sprite (and perhaps
# by shift amount?) so store it here
byte_width = len ( color_streams [ 0 ] )
self . asm ( " lda # %d " % byte_width )
2017-07-04 04:22:58 +00:00
self . asm ( " sta DAMAGE_W " )
self . asm ( " lda # %d " % self . height )
self . asm ( " sta DAMAGE_H " )
2017-06-30 19:52:15 +00:00
self . out ( )
2017-06-21 21:28:22 +00:00
self . asm ( " rts " )
2017-07-05 19:50:22 +00:00
self . comment ( " Cycle count: %d , Optimized %d rows. " % ( cycle_count , optimization_count ) )
2017-06-21 21:28:22 +00:00
2017-07-05 19:50:22 +00:00
def generate_blitter ( self , color_streams , mask_streams , base_cycle_count , baselabel ) :
byte_width = len ( color_streams [ 0 ] )
2017-06-20 13:37:57 +00:00
2017-07-05 19:50:22 +00:00
cycle_count = base_cycle_count
optimization_count = 0
2017-06-20 22:59:56 +00:00
2017-07-03 04:38:32 +00:00
order = list ( range ( self . height ) )
for row in order :
2017-07-05 19:50:22 +00:00
cycle_count + = self . row_start_calculator_code ( row , baselabel )
2017-06-30 19:25:49 +00:00
2017-07-05 19:50:22 +00:00
byte_splits = color_streams [ row ]
mask_splits = mask_streams [ row ]
byte_count = len ( byte_splits )
2017-06-30 19:25:49 +00:00
# number of trailing iny to remove due to unchanged bytes at the
# end of the row
skip_iny = 0
2017-06-20 22:59:56 +00:00
# Generate blitting code
2017-07-05 19:50:22 +00:00
for index , ( value , mask ) in enumerate ( zip ( byte_splits , mask_splits ) ) :
2017-06-30 19:25:49 +00:00
if index > 0 :
self . asm ( " iny " )
2017-07-05 19:50:22 +00:00
cycle_count + = 2
2017-06-30 19:25:49 +00:00
2017-06-20 22:59:56 +00:00
# Optimization
2017-06-30 19:25:49 +00:00
if mask == " 01111111 " :
2017-07-05 19:50:22 +00:00
optimization_count + = 1
2017-06-30 19:25:49 +00:00
self . comment_line ( " byte %d : skipping! unchanged byte (mask = %s ) " % ( index , mask ) )
skip_iny + = 1
2017-06-22 17:36:04 +00:00
else :
2017-06-30 19:25:49 +00:00
value = self . binary_constant ( value )
skip_iny = 0
2017-06-20 22:59:56 +00:00
# Store byte into video memory
if self . xdraw :
2017-07-05 19:50:22 +00:00
self . asm ( " lda (scratch_addr),y " )
2017-06-30 19:25:49 +00:00
self . asm ( " eor %s " % value )
2017-07-05 19:50:22 +00:00
self . asm ( " sta (scratch_addr),y " ) ;
cycle_count + = 5 + 2 + 6
2017-06-22 18:09:50 +00:00
elif self . use_mask :
2017-06-30 19:25:49 +00:00
if mask == " 00000000 " :
# replacing all the bytes; no need for and/or!
self . asm ( " lda %s " % value )
2017-07-05 19:50:22 +00:00
self . asm ( " sta (scratch_addr),y " ) ;
cycle_count + = 2 + 5
2017-06-30 19:25:49 +00:00
else :
mask = self . binary_constant ( mask )
2017-07-05 19:50:22 +00:00
self . asm ( " lda (scratch_addr),y " )
2017-06-30 19:25:49 +00:00
self . asm ( " and %s " % mask )
self . asm ( " ora %s " % value )
2017-07-05 19:50:22 +00:00
self . asm ( " sta (scratch_addr),y " ) ;
cycle_count + = 5 + 2 + 2 + 6
2017-06-20 22:59:56 +00:00
else :
2017-06-30 19:25:49 +00:00
self . asm ( " lda %s " % value )
2017-07-05 19:50:22 +00:00
self . asm ( " sta (scratch_addr),y " ) ;
cycle_count + = 2 + 6
2017-06-20 22:59:56 +00:00
2017-06-30 19:25:49 +00:00
while skip_iny > 0 :
self . pop_asm ( " iny " )
skip_iny - = 1
2017-07-05 19:50:22 +00:00
cycle_count - = 2
2017-06-30 19:25:49 +00:00
2017-07-05 19:50:22 +00:00
return cycle_count , optimization_count
2016-08-18 19:12:11 +00:00
2017-07-05 19:50:22 +00:00
def row_start_calculator_code ( self , row , baselabel ) :
2017-06-30 19:25:49 +00:00
self . out ( )
self . comment_line ( " row %d " % row )
2017-07-03 23:31:35 +00:00
if row == 0 :
2017-07-05 19:50:22 +00:00
self . asm ( " ldx param_y " )
2017-07-03 23:31:35 +00:00
cycles = 3
2017-06-30 19:25:49 +00:00
else :
2017-07-03 23:31:35 +00:00
cycles = 0
self . asm ( " lda HGRROWS_H1+ %d ,x " % row )
cycles + = 4
if self . double_buffer :
# HGRSELECT must be set to $00 or $60. The eor then turns the high
# byte of page 1 into either page1 or page 2 by flipping the 5th
# and 6th bit
self . asm ( " eor HGRSELECT " )
cycles + = 3
2017-07-05 19:50:22 +00:00
self . asm ( " sta scratch_addr+1 " )
2017-07-03 04:54:04 +00:00
self . asm ( " lda HGRROWS_L+ %d ,x " % row )
2017-07-05 19:50:22 +00:00
self . asm ( " sta scratch_addr " )
2017-07-03 23:31:35 +00:00
cycles + = 3 + 4 + 3
if row == 0 :
2017-07-05 19:50:22 +00:00
self . asm ( " ldy param_x " )
self . asm ( " lda DIV %d _ %d ,y " % ( self . screen . num_shifts , self . screen . bits_per_pixel ) )
self . asm ( " sta scratch_col " ) # save the mod lookup; it doesn't change
2017-06-30 19:33:27 +00:00
self . asm ( " tay " )
cycles + = 3 + 4 + 3 + 2
else :
2017-07-05 19:50:22 +00:00
self . asm ( " ldy scratch_col " )
2017-06-30 19:33:27 +00:00
cycles + = 2
2017-07-03 23:31:35 +00:00
return cycles ;
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
def shift_string_right ( string , shift , bits_per_pixel , filler_bit ) :
2017-06-20 13:37:57 +00:00
if shift == 0 :
return string
2017-07-05 19:50:22 +00:00
shift * = bits_per_pixel
2017-06-20 13:37:57 +00:00
result = " "
for i in range ( shift ) :
2017-07-05 19:50:22 +00:00
result + = filler_bit
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 ) :
2017-07-05 19:50:22 +00:00
num_shifts = 8
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
bits_per_pixel = 1
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
screen_width = 320
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
screen_height = 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 ( )
2018-07-02 04:23:56 +00:00
self . numX = self . screen_width / / self . bits_per_pixel
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
def byte_width ( self , png_width ) :
return ( png_width * self . bits_per_pixel + self . num_shifts - 1 ) / / self . num_shifts + 1
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
def bits_for_color ( self , pixel ) :
2017-06-21 21:03:53 +00:00
raise NotImplementedError
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
def bits_for_mask ( self , pixel ) :
2017-06-21 21:03:53 +00:00
raise NotImplementedError
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
def pixel_color ( self , pixel_data , row , col ) :
2017-06-21 21:03:53 +00:00
raise NotImplementedError
2016-12-25 20:43:14 +00:00
2017-06-21 21:03:53 +00:00
def generate_row_offsets ( self ) :
2017-07-05 19:50:22 +00:00
offsets = [ 40 * y for y in range ( self . screen_height ) ]
2017-06-21 21:03:53 +00:00
return offsets
2016-12-25 20:43:14 +00:00
2017-07-05 19:50:22 +00:00
def generate_row_addresses ( self , base_addr ) :
addrs = [ base_addr + offset for offset in self . offsets ]
2017-06-21 21:03:53 +00:00
return addrs
class HGR ( ScreenFormat ) :
2017-07-05 19:50:22 +00:00
num_shifts = 7
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
bits_per_pixel = 2
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
screen_width = 280
2017-06-21 21:03:53 +00:00
2018-07-02 04:23:56 +00:00
black , magenta , green , orange , blue , white , key = list ( range ( 7 ) )
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
def bits_for_color ( 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 "
2017-07-05 19:50:22 +00:00
def bits_for_mask ( 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
2017-07-17 13:33:47 +00:00
def bits_for_bw ( self , pixel , pixel_index = 0 ) :
# if pixel == self.green or pixel == self.orange:
# pair = "01"
# elif pixel == self.blue or pixel == self.magenta:
# pair = "10"
# elif pixel == self.white:
2017-07-16 05:41:07 +00:00
if pixel == self . white :
return " 1 "
else :
return " 0 "
2017-07-17 13:33:47 +00:00
return pair [ pixel_index & 1 ]
2017-07-16 05:41:07 +00:00
def bits_for_bw_mask ( self , pixel ) :
if pixel == self . key :
return " 1 "
return " 0 "
2017-07-05 19:50:22 +00:00
def high_bit_for_color ( self , pixel ) :
2017-06-21 21:03:53 +00:00
# Note that we prefer high-bit white because blue fringe is less noticeable than magenta.
2017-07-05 19:50:22 +00:00
high_bit = " 0 "
2017-06-21 21:03:53 +00:00
if pixel == self . orange or pixel == self . blue or pixel == self . white :
2017-07-05 19:50:22 +00:00
high_bit = " 1 "
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
return high_bit
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
def high_bit_for_mask ( self , pixel ) :
2017-06-22 17:36:04 +00:00
return " 0 "
2017-06-21 21:03:53 +00:00
2017-07-17 13:33:47 +00:00
def get_rgb ( self , pixel_data , row , col ) :
2017-07-05 19:50:22 +00:00
r = pixel_data [ row ] [ col * 3 ]
g = pixel_data [ row ] [ col * 3 + 1 ]
b = pixel_data [ row ] [ col * 3 + 2 ]
2017-07-17 13:33:47 +00:00
return r , g , b
def pixel_color ( self , pixel_data , row , col ) :
r , g , b = self . get_rgb ( pixel_data , row , col )
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
2017-07-05 19:50:22 +00:00
def byte_streams_from_pixels ( self , shift , source , mask = False ) :
byte_streams = [ " " for x in range ( source . height ) ]
byte_width = self . byte_width ( source . width )
2017-06-21 21:03:53 +00:00
if mask :
2017-07-05 19:50:22 +00:00
bit_delegate = self . bits_for_mask
high_bit_delegate = self . high_bit_for_mask
filler_bit = " 1 "
2017-06-21 21:03:53 +00:00
else :
2017-07-05 19:50:22 +00:00
bit_delegate = self . bits_for_color
high_bit_delegate = self . high_bit_for_color
filler_bit = " 0 "
2016-07-21 03:39:11 +00:00
2017-06-21 21:03:53 +00:00
for row in range ( source . height ) :
2017-07-05 19:50:22 +00:00
bit_stream = " "
high_bit = " 0 "
high_bit_found = False
2017-06-21 21:03:53 +00:00
# Compute raw bitstream for row from PNG pixels
2017-07-05 19:50:22 +00:00
for pixel_index in range ( source . width ) :
pixel = self . pixel_color ( source . pixel_data , row , pixel_index )
bit_stream + = bit_delegate ( pixel )
2017-06-22 17:27:58 +00:00
# Determine palette bit from first non-black pixel on each row
2017-07-05 19:50:22 +00:00
if not high_bit_found and pixel != self . black and pixel != self . key :
high_bit = high_bit_delegate ( pixel )
high_bit_found = True
2017-06-21 21:03:53 +00:00
# Shift bit stream as needed
2017-07-05 19:50:22 +00:00
bit_stream = shift_string_right ( bit_stream , shift , self . bits_per_pixel , filler_bit )
bit_stream = bit_stream [ : byte_width * 8 ]
2017-06-21 21:03:53 +00:00
# Split bitstream into bytes
2017-07-05 19:50:22 +00:00
bit_pos = 0
byte_splits = [ 0 for x in range ( byte_width ) ]
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
for byte_index in range ( byte_width ) :
remaining_bits = len ( bit_stream ) - bit_pos
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
bit_chunk = " "
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
if remaining_bits < 0 :
bit_chunk = filler_bit * 7
2017-06-21 21:03:53 +00:00
else :
2017-07-05 19:50:22 +00:00
if remaining_bits < 7 :
bit_chunk = bit_stream [ bit_pos : ]
bit_chunk + = filler_bit * ( 7 - remaining_bits )
2017-06-21 21:03:53 +00:00
else :
2017-07-05 19:50:22 +00:00
bit_chunk = bit_stream [ bit_pos : bit_pos + 7 ]
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
bit_chunk = bit_chunk [ : : - 1 ]
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
byte_splits [ byte_index ] = high_bit + bit_chunk
bit_pos + = 7
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
byte_streams [ row ] = byte_splits ;
2016-08-18 19:12:11 +00:00
2017-07-05 19:50:22 +00:00
return byte_streams
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-07-05 19:50:22 +00:00
for y in range ( self . screen_height ) :
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 ) :
2017-07-05 19:50:22 +00:00
bits_per_pixel = 1
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
def bits_for_color ( self , pixel ) :
2017-07-16 05:41:07 +00:00
return self . bits_for_bw ( pixel )
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
def bits_for_mask ( self , pixel ) :
2017-07-16 05:41:07 +00:00
return self . bits_for_bw_mask ( pixel )
2017-06-21 21:03:53 +00:00
2017-07-05 19:50:22 +00:00
def pixel_color ( self , pixel_data , row , col ) :
2017-07-17 13:33:47 +00:00
r , g , b = self . get_rgb ( pixel_data , row , col )
2017-06-21 21:03:53 +00:00
color = self . black
2017-06-30 21:13:05 +00:00
if abs ( r - g ) < 16 and abs ( g - b ) < 16 and r != 0 and r != 255 : # Any grayish color is chroma key
2017-06-21 21:03:53 +00:00
color = self . key
2017-06-30 21:13:05 +00:00
elif r > 25 or g > 25 or b > 25 : # pretty much all other colors are white
color = self . white
2017-06-21 21:03:53 +00:00
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 )
2017-06-30 06:14:39 +00:00
self . slug = " hgrrows "
2017-06-21 21:03:53 +00:00
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 )
2017-07-05 19:50:22 +00:00
self . slug = " hgrcols- %d x %d " % ( screen . num_shifts , screen . bits_per_pixel )
2017-06-21 21:11:48 +00:00
self . generate_x ( screen )
2017-06-21 21:03:53 +00:00
def generate_x ( self , screen ) :
self . out ( " \n " )
2017-07-05 19:50:22 +00:00
self . label ( " DIV %d _ %d " % ( screen . num_shifts , screen . bits_per_pixel ) )
2017-06-21 21:03:53 +00:00
for pixel in range ( screen . numX ) :
2018-07-02 04:23:56 +00:00
self . byte ( " $ %02x " % ( ( pixel * screen . bits_per_pixel ) / / screen . num_shifts ) , screen . num_shifts )
2016-07-21 03:39:11 +00:00
2017-06-21 02:55:47 +00:00
self . out ( " \n " )
2017-07-05 19:50:22 +00:00
self . label ( " MOD %d _ %d " % ( screen . num_shifts , screen . bits_per_pixel ) )
2017-06-21 21:03:53 +00:00
for pixel in range ( screen . numX ) :
2017-06-26 12:48:52 +00:00
# This is the index into the jump table, so it's always multiplied
# by 2
2017-07-05 19:50:22 +00:00
self . byte ( " $ %02x " % ( ( pixel % screen . num_shifts ) * 2 ) , screen . num_shifts )
2016-07-21 03:39:11 +00:00
2017-07-01 05:58:42 +00:00
class BackingStore ( Listing ) :
# Each entry in the stack includes:
# 2 bytes: address of restore routine
# 1 byte: x coordinate
# 1 byte: y coordinate
# nn: x * y bytes of data, in lists of rows
def __init__ ( self , assembler , byte_width , row_height ) :
Listing . __init__ ( self , assembler )
self . byte_width = byte_width
self . row_height = row_height
self . save_label = " savebg_ %d x %d " % ( byte_width , row_height )
self . restore_label = " restorebg_ %d x %d " % ( byte_width , row_height )
self . space_needed = self . compute_size ( )
self . create_save ( )
self . out ( )
self . create_restore ( )
self . out ( )
def compute_size ( self ) :
return 2 + 1 + 1 + ( self . byte_width * self . row_height )
def create_save ( self ) :
self . label ( self . save_label )
# reserve space in the backing store stack
self . asm ( " sec " )
self . asm ( " lda bgstore " )
self . asm ( " sbc # %d " % self . space_needed )
self . asm ( " sta bgstore " )
self . asm ( " lda bgstore+1 " )
self . asm ( " sbc #0 " )
self . asm ( " sta bgstore+1 " )
# save the metadata
self . asm ( " ldy #0 " )
self . asm ( " lda #< %s " % self . restore_label )
self . asm ( " sta (bgstore),y " )
self . asm ( " iny " )
self . asm ( " lda #> %s " % self . restore_label )
self . asm ( " sta (bgstore),y " )
self . asm ( " iny " )
2017-07-05 19:50:22 +00:00
self . asm ( " lda param_x " )
2017-07-01 05:58:42 +00:00
self . asm ( " sta (bgstore),y " )
self . asm ( " iny " )
2017-07-05 19:50:22 +00:00
self . asm ( " lda param_y " )
2017-07-01 05:58:42 +00:00
2017-07-05 19:50:22 +00:00
# Note that we can't clobber param_y like the restore routine can
2017-07-01 05:58:42 +00:00
# because this is called in the sprite drawing routine and these
# values must be retained to draw the sprite in the right place!
2017-07-05 19:50:22 +00:00
self . asm ( " sta scratch_addr " )
2017-07-01 05:58:42 +00:00
self . asm ( " sta (bgstore),y " )
self . asm ( " iny " )
2017-07-01 15:00:50 +00:00
# The unrolled code is taken from Quinn's row sweep backing store
# code in a previous version of HiSprite
2017-07-05 19:50:22 +00:00
loop_label , col_label = self . smc_row_col ( self . save_label , " scratch_addr " )
2017-07-01 05:58:42 +00:00
for c in range ( self . byte_width ) :
self . label ( col_label % c )
self . asm ( " lda $2000,x " )
self . asm ( " sta (bgstore),y " )
self . asm ( " iny " )
if c < self . byte_width - 1 :
# last loop doesn't need this
self . asm ( " inx " )
2017-07-05 19:50:22 +00:00
self . asm ( " inc scratch_addr " )
2017-07-01 05:58:42 +00:00
self . asm ( " cpy # %d " % self . space_needed )
self . asm ( " bcc %s " % loop_label )
self . asm ( " rts " )
def smc_row_col ( self , label , row_var ) :
# set up smc for hires column, because the starting column doesn't
# change when moving to the next row
2017-07-05 19:50:22 +00:00
self . asm ( " ldx param_x " )
2017-07-01 05:58:42 +00:00
self . asm ( " lda DIV7_1,x " )
smc_label = " %s _smc1 " % label
self . asm ( " sta %s +1 " % smc_label )
loop_label = " %s _line " % label
# save a line, starting from the topmost and working down
self . label ( loop_label )
self . asm ( " ldx %s " % row_var )
self . asm ( " lda HGRROWS_H1,x " )
col_label = " %s _col %% s " % label
for c in range ( self . byte_width ) :
self . asm ( " sta %s +2 " % ( col_label % c ) )
self . asm ( " lda HGRROWS_L,x " )
for c in range ( self . byte_width ) :
self . asm ( " sta %s +1 " % ( col_label % c ) )
self . label ( smc_label )
self . asm ( " ldx #$ff " )
return loop_label , col_label
def create_restore ( self ) :
# bgstore will be pointing right to the data to be blitted back to the
# screen, which is 4 bytes into the bgstore array. Everything before
# the data will have already been pulled off by the driver in order to
# figure out which restore routine to call. Y will be 4 upon entry,
2017-07-05 19:50:22 +00:00
# and param_x and param_y will be filled with the x & y values.
2017-07-01 05:58:42 +00:00
#
# also, no need to save registers because this is being called from a
# driver that will do all of that.
self . label ( self . restore_label )
2017-07-05 19:50:22 +00:00
# we can clobber the heck out of param_y because we're being called from
2017-07-01 05:58:42 +00:00
# the restore driver and when we return we are just going to load it up
# with the next value anyway.
2017-07-05 19:50:22 +00:00
loop_label , col_label = self . smc_row_col ( self . restore_label , " param_y " )
2017-07-01 05:58:42 +00:00
for c in range ( self . byte_width ) :
self . asm ( " lda (bgstore),y " )
self . label ( col_label % c )
self . asm ( " sta $2000,x " )
self . asm ( " iny " )
if c < self . byte_width - 1 :
# last loop doesn't need this
self . asm ( " inx " )
2017-07-05 19:50:22 +00:00
self . asm ( " inc param_y " )
2017-07-01 05:58:42 +00:00
self . asm ( " cpy # %d " % self . space_needed )
self . asm ( " bcc %s " % loop_label )
self . asm ( " rts " )
class BackingStoreDriver ( Listing ) :
# Driver to restore the screen using all the saved data.
# The backing store is a stack that grows downward in order to restore the
# chunks in reverse order that they were saved.
#
# variables used:
# bgstore: (lo byte, hi byte) 1 + the first byte of free memory.
# I.e. points just beyond the last byte
2017-07-05 19:50:22 +00:00
# param_x: (byte) x coord
# param_y: (byte) y coord
2017-07-01 05:58:42 +00:00
#
# everything else is known because the sizes of each erase/restore
# routine are hardcoded because this is a sprite *compiler*.
2017-07-01 15:00:50 +00:00
#
# Note that sprites of different sizes will have different sized entries in
# the stack, so the entire list has to be processed in order. But you want
# that anyway, so it's not a big deal.
#
# The global variable 'bgstore' is used as the stack pointer. It must be
# initialized to a page boundary, the stack grows downward from there.
# starting from the last byte on the previous page. E.g. if the initial
# value is $c000, the stack grows down using $bfff as the highest address,
# the initial bgstore value must point to 1 + the last usable byte
#
# All registers are clobbered because there's no real need to save them
# since this will be called from the main game loop.
2017-07-01 05:58:42 +00:00
def __init__ ( self , assembler , sizes ) :
Listing . __init__ ( self , assembler )
self . slug = " backing-store "
2017-07-01 15:00:50 +00:00
self . add_driver ( )
2017-07-01 05:58:42 +00:00
for byte_width , row_height in sizes :
code = BackingStore ( assembler , byte_width , row_height )
self . add_listing ( code )
2017-07-01 15:00:50 +00:00
def add_driver ( self ) :
# Initialization routine needs to be called once at the beginning
# of the program so that the savebg_* functions will have a valid
# bgstore
self . label ( " restorebg_init " )
self . asm ( " lda #0 " )
self . asm ( " sta bgstore " )
self . asm ( " lda #BGTOP " )
self . asm ( " sta bgstore+1 " )
self . asm ( " rts " )
self . out ( )
# Driver routine to loop through the bgstore stack and copy the
# data back to the screen
self . label ( " restorebg_driver " )
self . asm ( " ldy #0 " )
self . asm ( " lda (bgstore),y " )
self . asm ( " sta restorebg_jsr_smc+1 " )
self . asm ( " iny " )
self . asm ( " lda (bgstore),y " )
self . asm ( " sta restorebg_jsr_smc+2 " )
self . asm ( " iny " )
self . asm ( " lda (bgstore),y " )
2017-07-05 19:50:22 +00:00
self . asm ( " sta param_x " )
2017-07-01 15:00:50 +00:00
self . asm ( " iny " )
self . asm ( " lda (bgstore),y " )
2017-07-05 19:50:22 +00:00
self . asm ( " sta param_y " )
2017-07-01 15:00:50 +00:00
self . asm ( " iny " )
self . label ( " restorebg_jsr_smc " )
self . asm ( " jsr $ffff " )
self . asm ( " clc " )
self . asm ( " tya " ) # y contains the number of bytes processed
self . asm ( " adc bgstore " )
self . asm ( " sta bgstore " )
self . asm ( " lda bgstore+1 " )
self . asm ( " adc #0 " )
self . asm ( " sta bgstore+1 " )
self . asm ( " cmp #BGTOP " )
self . asm ( " bcc restorebg_driver " )
self . asm ( " rts " )
self . out ( )
2017-07-01 05:58:42 +00:00
2017-07-04 20:34:25 +00:00
class FastFont ( Listing ) :
def __init__ ( self , assembler , screen , font , double_buffer ) :
Listing . __init__ ( self , assembler )
self . slug = " fastfont "
self . generate_table ( screen , " H1 " , 0x2000 )
if double_buffer :
self . generate_table ( screen , " H2 " , 0x4000 )
self . generate_transposed_font ( screen , font )
def generate_table ( self , screen , suffix , hgrbase ) :
label = " FASTFONT_ %s " % suffix
self . label ( label )
# Have to use self-modifying code because assembler may not allow
# taking the hi/lo bytes of an address - 1
self . comment ( " A = character, X = column, Y = row; A is clobbered, X&Y are not " )
self . asm ( " pha " )
self . asm ( " lda %s _JMP_HI,y " % label )
self . asm ( " sta %s _JMP+2 " % label )
self . asm ( " lda %s _JMP_LO,y " % label )
self . asm ( " sta %s _JMP+1 " % label )
2017-07-05 19:50:22 +00:00
self . asm ( " sty scratch_0 " )
2017-07-04 20:34:25 +00:00
self . asm ( " pla " )
self . asm ( " tay " )
self . label ( " %s _JMP " % label )
self . asm ( " jmp $ffff \n " )
# Bit-shift jump table for generic 6502
self . out ( )
self . label ( " %s _JMP_HI " % label )
for r in range ( 24 ) :
self . asm ( " .byte > %s _ %d " % ( label , r ) )
self . label ( " %s _JMP_LO " % label )
for r in range ( 24 ) :
self . asm ( " .byte < %s _ %d " % ( label , r ) )
self . out ( )
hgr1 = screen . generate_row_addresses ( hgrbase )
for r in range ( 24 ) :
self . label ( " %s _ %d " % ( label , r ) )
for i in range ( 8 ) :
2017-07-05 19:50:22 +00:00
self . asm ( " lda TransposedFontRow %d ,y " % i )
2017-07-04 20:34:25 +00:00
self . asm ( " sta $ %04x ,x " % ( hgr1 [ r * 8 + i ] ) )
2017-07-05 19:50:22 +00:00
self . asm ( " ldy scratch_0 " )
2017-07-04 20:34:25 +00:00
self . asm ( " rts " )
self . out ( )
def generate_transposed_font ( self , screen , font ) :
with open ( font , ' rb ' ) as fh :
data = fh . read ( )
num_bytes = len ( data )
2018-07-02 04:23:56 +00:00
num_chars = num_bytes / / 8
2017-07-04 20:34:25 +00:00
for r in range ( 8 ) :
2017-07-05 19:50:22 +00:00
self . label ( " TransposedFontRow %d " % r )
2017-07-04 20:34:25 +00:00
for i in range ( num_chars ) :
index = i * 8 + r
2018-07-02 04:23:56 +00:00
self . byte ( " $ %02x " % data [ index ] , 16 )
2017-07-04 20:34:25 +00:00
2018-07-21 02:26:40 +00:00
class CompiledFastFont ( Listing ) :
def __init__ ( self , assembler , screen , font , double_buffer ) :
Listing . __init__ ( self , assembler )
self . slug = " compiledfont "
with open ( font , ' rb ' ) as fh :
self . font_data = fh . read ( )
self . num_chars = len ( self . font_data ) / / 8
self . generate_table ( screen , " H1 " , 0x2000 )
if double_buffer :
self . generate_table ( screen , " H2 " , 0x4000 )
def generate_table ( self , screen , suffix , hgrbase ) :
label = " COMPILEDFONT_ %s " % suffix
self . label ( label )
# Have to use self-modifying code because assembler may not allow
# taking the hi/lo bytes of an address - 1
self . comment ( " A = character, X = column, Y = row; A is clobbered, X&Y are not " )
self . asm ( " sty scratch_0 " )
self . asm ( " tay " )
self . asm ( " lda %s _JMP_HI,y " % label )
self . asm ( " sta %s _JMP+2 " % label )
self . asm ( " lda %s _JMP_LO,y " % label )
self . asm ( " sta %s _JMP+1 " % label )
self . asm ( " ldy scratch_0 " )
self . asm ( " lda hgrtextrow_l,y " )
self . asm ( " sta hgr_ptr " )
self . asm ( " lda hgrtextrow_h,y " )
self . asm ( " sta hgr_ptr+1 " )
self . asm ( " txa " )
self . asm ( " tay " )
self . label ( " %s _JMP " % label )
self . asm ( " jmp $ffff \n " )
# Bit-shift jump table for generic 6502
self . out ( )
self . label ( " %s _JMP_HI " % label )
for r in range ( self . num_chars ) :
self . asm ( " .byte > %s _ %d " % ( label , r ) )
self . label ( " %s _JMP_LO " % label )
for r in range ( self . num_chars ) :
self . asm ( " .byte < %s _ %d " % ( label , r ) )
self . out ( )
index = 0
for r in range ( self . num_chars ) :
self . label ( " %s _ %d " % ( label , r ) )
for i in range ( 8 ) :
self . asm ( " lda #$ %02x " % self . font_data [ index ] )
self . asm ( " sta (hgr_ptr),y " )
self . asm ( " clc " )
self . asm ( " lda #4 " )
self . asm ( " adc hgr_ptr+1 " )
self . asm ( " sta hgr_ptr+1 " )
index + = 1
self . asm ( " ldy scratch_0 " )
self . asm ( " rts " )
self . out ( )
2017-07-16 05:41:07 +00:00
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 ) :
2017-07-17 13:33:47 +00:00
r , g , b = self . get_rgb ( pixel_data , row , col )
2017-07-16 05:41:07 +00:00
# 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 )
2017-07-17 13:33:47 +00:00
bit_stream + = self . bits_for_bw ( pixel , pixel_index )
2017-07-16 05:41:07 +00:00
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 ] )
2017-07-17 13:33:47 +00:00
self . lines = self . convert ( fileroot )
self . save ( fileroot )
2017-07-16 05:41:07 +00:00
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 )
2018-07-02 04:23:56 +00:00
print ( f " created bw representation of HGR screen: { output } " )
2017-07-17 13:33:47 +00:00
return lines
2017-07-16 05:41:07 +00:00
2017-07-17 13:33:47 +00:00
def save ( self , fileroot , other = None , merge = 96 ) :
2017-07-16 05:41:07 +00:00
offsets = self . screen . generate_row_addresses ( 0 )
# print ["%04x" % i for i in offsets]
screen = np . zeros ( 8192 , dtype = np . uint8 )
2017-07-17 13:33:47 +00:00
lines = self . lines
2017-07-16 05:41:07 +00:00
for row in range ( 192 ) :
2017-07-17 13:33:47 +00:00
if other is not None and row == merge :
lines = other . lines
2017-07-16 05:41:07 +00:00
offset = offsets [ row ]
screen [ offset : offset + 40 ] = lines [ row ]
output = " %s .hgr " % fileroot
with open ( output , " wb " ) as fh :
fh . write ( screen )
2018-07-02 04:23:56 +00:00
print ( f " created HGR screen: { output } " )
2017-07-16 05:41:07 +00:00
2017-07-17 13:33:47 +00:00
class RawHGRImage ( object ) :
def __init__ ( self , pathname ) :
self . screen = HGR ( )
self . raw = np . fromfile ( pathname , dtype = np . uint8 )
if len ( self . raw ) != 8192 :
raise RuntimeError ( " Not HGR image size " )
2017-07-24 21:05:23 +00:00
def merge ( self , other , merge_list ) :
2017-07-17 13:33:47 +00:00
offsets = self . screen . generate_row_addresses ( 0 )
# print ["%04x" % i for i in offsets]
screen = np . zeros ( 8192 , dtype = np . uint8 )
raw = self . raw
2017-07-24 21:05:23 +00:00
others = [ 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 ]
choices = [ raw , other . raw ]
2018-07-02 04:23:56 +00:00
print ( others )
2017-07-24 21:05:23 +00:00
merge = merge_list . pop ( 0 )
2017-07-17 13:33:47 +00:00
for row in range ( 192 ) :
if row == merge :
# switch!
2017-07-24 21:05:23 +00:00
i = others . pop ( 0 )
raw = choices [ i ]
try :
merge = merge_list . pop ( 0 )
except IndexError :
merge = - 1
2017-07-17 13:33:47 +00:00
offset = offsets [ row ]
screen [ offset : offset + 40 ] = raw [ offset : offset + 40 ]
self . raw [ : ] = screen
def save ( self , fileroot ) :
output = " %s .hgr " % fileroot
with open ( output , " wb " ) as fh :
fh . write ( self . raw )
2018-07-02 04:23:56 +00:00
print ( f " created HGR screen: { output } " )
2017-07-17 13:33:47 +00:00
2017-07-26 17:40:11 +00:00
class FastScroll ( Listing ) :
def __init__ ( self , assembler , screen , lines = 1 , screen1 = 0x4000 , screen2 = 0x2000 ) :
Listing . __init__ ( self , assembler )
self . slug = " fastscroll "
self . generate_table ( screen , lines , screen1 , screen2 )
def generate_table ( self , screen , lines , source , dest ) :
label = " FASTSCROLL_ %x _ %x " % ( source , dest )
end_label = " %s _RTS " % label
outer_label = " %s _OUTER " % label
inner_label = " %s _INNER " % label
cont_label = " %s _NEXT_OUTER " % label
self . label ( end_label )
self . asm ( " rts " )
self . label ( label )
smc_labels = [ ]
# Have to use self-modifying code because assembler may not allow
# taking the hi/lo bytes of an address - 1
self . comment ( " A,X,Y clobbered " )
self . asm ( " ldy #0 " )
self . label ( outer_label )
self . asm ( " cpy #192 " )
self . asm ( " bcs %s " % end_label )
for r in range ( lines ) :
smc_labels . append ( " %s _SMC %d " % ( label , r ) )
self . asm ( " lda HGRROWS_L,y " )
self . asm ( " sta %s +1 " % smc_labels [ r ] )
self . asm ( " lda HGRROWS_H2,y " )
self . asm ( " sta %s +2 " % smc_labels [ r ] )
self . asm ( " iny " )
self . asm ( " ldx #39 " )
self . label ( inner_label )
s = screen . generate_row_addresses ( source )
d = screen . generate_row_addresses ( dest )
for r in range ( screen . screen_height - lines ) :
self . asm ( " lda $ %04x ,x " % d [ r + lines ] )
self . asm ( " sta $ %04x ,x " % d [ r ] )
source = screen . screen_height - lines
for r in range ( lines ) :
self . label ( smc_labels [ r ] )
self . asm ( " lda $ffff,x " )
self . asm ( " sta $ %04x ,x " % d [ r + screen . screen_height - lines ] )
self . asm ( " dex " )
self . asm ( " bmi %s \n " % cont_label )
self . asm ( " jmp %s " % inner_label )
self . label ( cont_label )
self . asm ( " jmp %s " % outer_label )
self . out ( )
2018-07-20 04:21:25 +00:00
class FastClear ( Listing ) :
def __init__ ( self , assembler , screen ) :
Listing . __init__ ( self , assembler )
self . slug = " fastclear "
self . generate_table ( screen , 0 )
def generate_table ( self , screen , offset ) :
base = 0x2000 + offset
label = " FASTCLEAR_ %x " % ( base )
end_label = " %s _RTS " % label
inner_label = " %s _INNER " % label
self . label ( label )
smc_labels = [ ]
# Have to use self-modifying code because assembler may not allow
# taking the hi/lo bytes of an address - 1
self . comment ( " A,X clobbered " )
self . asm ( " lda #$aa " )
self . asm ( " ldx #39 " )
self . label ( inner_label )
s = screen . generate_row_addresses ( base )
for r in range ( screen . screen_height ) :
self . asm ( " sta $ %04x ,x " % s [ r ] )
self . asm ( " dex " )
self . asm ( " bmi %s \n " % end_label )
self . asm ( " jmp %s " % inner_label )
self . label ( end_label )
self . asm ( " rts " )
self . out ( )
2018-07-21 02:26:40 +00:00
class InsaneClear ( Listing ) :
def __init__ ( self , assembler , screen ) :
Listing . __init__ ( self , assembler )
self . slug = " insaneclear "
self . generate_table ( screen , 0 )
def generate_table ( self , screen , offset ) :
base = 0x2000 + offset
label = " INSANECLEAR_ %x " % ( base )
self . label ( label )
self . comment ( " A clobbered " )
self . asm ( " lda #$aa " )
s = screen . generate_row_addresses ( base )
for c in range ( 40 ) :
for r in range ( screen . screen_height ) :
self . asm ( " sta $ %04x " % ( s [ r ] + c ) )
self . asm ( " rts " )
self . out ( )
2017-07-27 06:58:16 +00:00
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
2018-07-02 04:23:56 +00:00
print ( f " { p } : run { r } of { v } ( { num_tokens } ) " )
2017-07-27 06:58:16 +00:00
compressed_size + = num_tokens
2018-07-02 04:23:56 +00:00
print ( f " compressed size: { compressed_size } " )
2017-07-27 06:58:16 +00:00
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
2018-07-02 04:23:56 +00:00
high_bit_notice = " " if changed == high_bit_set else f " high bit = { high_bit_set } "
print ( f " { p } : run { r } of { v } ( { num_tokens } ) { high_bit_notice } " )
2017-07-27 06:58:16 +00:00
compressed_size + = num_tokens
2018-07-02 04:23:56 +00:00
print ( f " compressed size: { compressed_size } " )
2017-07-27 06:58:16 +00:00
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
2018-07-02 04:23:56 +00:00
print ( f " { copy_start } : copy { num } ( { num + 1 } ) " )
2017-07-27 06:58:16 +00:00
copy_start = p
else :
if copy_start > = 0 :
num = p - copy_start
compressed_size + = num + 1
2018-07-02 04:23:56 +00:00
print ( f " { copy_start } : copy { num } ( { num + 1 } ) " )
2017-07-27 06:58:16 +00:00
copy_start = - 1
while r > 2 :
num = min ( r , 128 )
2018-07-02 04:23:56 +00:00
print ( f " { p } : run { num } of { v } (2) " % ( p , num , v ) )
2017-07-27 06:58:16 +00:00
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
2018-07-02 04:23:56 +00:00
print ( f " { copy_start } : copy { num } ( { num + 1 } ) " )
print ( f " compressed size: { compressed_size } " )
2017-07-27 06:58:16 +00:00
return compressed_size
2017-07-28 16:28:22 +00:00
class RawToSource ( Listing ) :
def __init__ ( self , assembler , data , slug ) :
Listing . __init__ ( self , assembler , slug )
raw = np . asarray ( data , dtype = np . uint8 )
self . generate_table ( raw )
def generate_table ( self , raw ) :
self . label ( " %s _START " % ( self . slug ) )
for i in range ( len ( raw ) ) :
self . byte ( str ( int ( raw [ i ] ) ) , 16 )
self . label ( " %s _END " % ( self . slug ) )
self . comment ( " %d bytes " % len ( raw ) )
self . out ( )
2016-07-21 03:39:11 +00:00
if __name__ == " __main__ " :
2017-07-17 17:59:00 +00:00
disclaimer = ''' ; AUTOGENERATED FILE; DO NOT EDIT!
;
2017-07-27 06:58:16 +00:00
; 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 ) .
2017-07-17 17:59:00 +00:00
;
2017-07-27 06:58:16 +00:00
; 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 . )
2017-06-21 21:11:48 +00:00
'''
2018-07-24 17:18:02 +00:00
parser = argparse . ArgumentParser ( description = " Code generator for 65C02/6502 to generate assembly code for a number of tasks, including fast font rendering to the hi-res screen and sprite compiling. The sprite compiler will 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. " )
2017-06-20 22:59:56 +00:00
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-29 18:48:25 +00:00
parser . add_argument ( " -b " , " --backing-store " , action = " store_true " , default = False , help = " add code to store background " )
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-07-16 05:41:07 +00:00
parser . add_argument ( " -i " , " --image " , default = " line " , choices = [ " line " , " color " , " bw " ] , help = " Screen format used for full page image conversion (default: %(default)s ) " )
2017-07-27 06:58:16 +00:00
parser . add_argument ( " -l " , " --scroll " , default = 0 , type = int , help = " Unrolled loop to scroll screen (default: %(default)s ) " )
2018-07-20 04:21:25 +00:00
parser . add_argument ( " --clear " , action = " store_true " , default = False , help = " Unrolled loop to clear screen (default: %(default)s ) " )
2018-07-21 02:26:40 +00:00
parser . add_argument ( " --insane-clear " , action = " store_true " , default = False , help = " Unrolled loop to clear screen (default: %(default)s ) " )
2017-07-24 21:05:23 +00:00
parser . add_argument ( " --merge " , type = int , nargs = " * " , help = " Merge two HGR images, switching images at the scan line " )
2017-07-27 06:58:16 +00:00
parser . add_argument ( " --rle " , action = " store_true " , default = False , help = " Create run-length-encoded version of data (assumed to be an image) " )
2017-07-28 16:28:22 +00:00
parser . add_argument ( " --src " , action = " store_true " , default = False , help = " Create source version of binary file " )
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-30 19:52:15 +00:00
parser . add_argument ( " -k " , " --clobber " , action = " store_true " , default = False , help = " don ' t save the registers on the stack " )
2017-07-03 04:38:32 +00:00
parser . add_argument ( " -d " , " --double-buffer " , action = " store_true " , default = False , help = " add code blit to either page (default: page 1 only) " )
2017-07-04 04:22:58 +00:00
parser . add_argument ( " -g " , " --damage " , action = " store_true " , default = False , help = " add code to report size of sprite upon return. Can be used in a damage list to restore an area from a pristine source. " )
2017-07-04 20:34:25 +00:00
parser . add_argument ( " -f " , " --font " , action = " store " , default = " " , help = " generate a fast font blitter for text on the hgr screen using the specified binary font file " )
2018-07-21 02:26:40 +00:00
parser . add_argument ( " --compiled-font " , action = " store " , default = " " , help = " generate a font-compiled fairly fast font blitter for text on the hgr screen using the specified binary font file " )
2017-06-30 06:14:39 +00:00
parser . add_argument ( " -o " , " --output-prefix " , default = " " , help = " Base name to create a set of output files. If not supplied, all code will be sent to stdout. " )
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 :
2018-07-02 04:23:56 +00:00
print ( f " Unknown assembler { options . assembler } " )
2017-06-21 21:03:53 +00:00
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 :
2018-07-02 04:23:56 +00:00
print ( f " Unknown screen format { 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-30 19:25:49 +00:00
luts = { } # dict of lookup tables to prevent duplication in output files
2017-06-20 22:59:56 +00:00
2017-07-24 21:05:23 +00:00
if options . merge :
2017-07-17 13:33:47 +00:00
if len ( options . files ) != 2 :
print ( " Merge requires exactly 2 HGR images " )
parser . print_help ( )
sys . exit ( 1 )
2018-07-02 04:23:56 +00:00
print ( options . merge )
2017-07-17 13:33:47 +00:00
hgr1 = RawHGRImage ( options . files [ 0 ] )
hgr2 = RawHGRImage ( options . files [ 1 ] )
hgr1 . merge ( hgr2 , options . merge )
hgr1 . save ( options . output_prefix )
sys . exit ( 0 )
2017-06-20 22:59:56 +00:00
for pngfile in options . files :
2017-07-28 16:28:22 +00:00
name = options . name if options . name else os . path . splitext ( pngfile ) [ 0 ]
slug = slugify ( name )
2017-07-27 06:58:16 +00:00
if pngfile . lower ( ) . endswith ( " .png " ) :
try :
reader = png . Reader ( pngfile )
pngdata = reader . asRGB8 ( )
2018-07-02 04:23:56 +00:00
except RuntimeError as e :
print ( f " { pngfile } : { e } " )
2017-07-27 06:58:16 +00:00
sys . exit ( 1 )
2018-07-02 04:23:56 +00:00
except png . Error as e :
print ( f " { pngfile } : { e } " )
2017-07-27 06:58:16 +00:00
sys . exit ( 1 )
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
2017-07-16 05:41:07 +00:00
else :
2017-07-27 06:58:16 +00:00
data = np . fromfile ( pngfile , dtype = np . uint8 )
if options . rle :
listings . append ( RLE ( assembler , data ) )
2017-07-28 16:28:22 +00:00
elif options . src :
listings . append ( RawToSource ( assembler , data , slug ) )
2017-07-27 06:58:16 +00:00
2017-06-30 06:14:39 +00:00
listings . extend ( [ luts [ k ] for k in sorted ( luts . keys ( ) ) ] )
2017-06-21 21:11:48 +00:00
if options . rows :
listings . append ( RowLookup ( assembler , screen ) )
if options . cols :
listings . append ( ColLookup ( assembler , screen ) )
2017-07-04 20:34:25 +00:00
if options . font :
listings . append ( FastFont ( assembler , screen , options . font , options . double_buffer ) )
2018-07-21 02:26:40 +00:00
if options . compiled_font :
listings . append ( CompiledFastFont ( assembler , screen , options . compiled_font , options . double_buffer ) )
2017-07-26 17:40:11 +00:00
if options . scroll :
listings . append ( FastScroll ( assembler , screen , options . scroll ) )
2018-07-20 04:21:25 +00:00
if options . clear :
listings . append ( FastClear ( assembler , screen ) )
2018-07-21 02:26:40 +00:00
if options . insane_clear :
listings . append ( InsaneClear ( assembler , screen ) )
2017-06-21 21:11:48 +00:00
if listings :
2017-06-30 06:14:39 +00:00
if options . output_prefix :
2017-07-01 05:58:42 +00:00
if Sprite . backing_store_sizes :
backing_store_code = BackingStoreDriver ( assembler , Sprite . backing_store_sizes )
listings . append ( backing_store_code )
2017-06-30 06:14:39 +00:00
driver = Listing ( assembler )
for source in listings :
genfile = source . write ( options . output_prefix , disclaimer )
driver . include ( genfile )
driver . write ( options . output_prefix , disclaimer )
else :
2018-07-02 04:23:56 +00:00
print ( disclaimer )
2017-06-21 21:11:48 +00:00
2017-06-30 06:14:39 +00:00
for section in listings :
2018-07-02 04:23:56 +00:00
print ( section )