2023-07-12 22:26:17 +00:00
|
|
|
#!/usr/bin/python3
|
2023-07-08 23:45:24 +00:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import PIL
|
|
|
|
from PIL import Image
|
|
|
|
from numpy import asarray
|
|
|
|
|
2023-07-13 19:08:21 +00:00
|
|
|
def labelFromCharXY(prefix,charFirst, charX, numCharX, charY):
|
2023-07-10 00:58:05 +00:00
|
|
|
charIndex = charY*numCharX + charX
|
2023-07-12 23:46:57 +00:00
|
|
|
currChar = chr(charIndex+charFirst)
|
2023-07-13 19:08:21 +00:00
|
|
|
return "{:s}char{:d}".format(prefix,ord(currChar))
|
2023-07-13 20:04:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
def stackAdvance(bytes):
|
|
|
|
if (bytes==0):
|
|
|
|
return
|
|
|
|
|
|
|
|
print ("\ttsc")
|
|
|
|
if (bytes==1):
|
|
|
|
print ("\tdec")
|
|
|
|
elif (bytes==2):
|
|
|
|
print ("\tdec") # For two bytes, a double-DEC is still faster than SEC/SBC
|
|
|
|
print ("\tdec")
|
|
|
|
else:
|
|
|
|
print ("\tsec")
|
|
|
|
print ("\tsbc #%d" % bytes)
|
|
|
|
|
|
|
|
print ("\ttcs")
|
|
|
|
|
2023-07-10 00:58:05 +00:00
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
def main(argv):
|
2023-07-12 23:46:57 +00:00
|
|
|
CHAR_WIDTH = int(argv[0])
|
|
|
|
CHAR_HEIGHT = int(argv[1])
|
|
|
|
CHAR_FIRST = int(argv[2])
|
|
|
|
CHROMA = int(argv[3])
|
2023-07-13 19:08:21 +00:00
|
|
|
prefix = argv[4]
|
|
|
|
image = Image.open(argv[5])
|
2023-07-12 23:46:57 +00:00
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
pixels = asarray(image)
|
|
|
|
numCharX = (int)(image.size[0]/CHAR_WIDTH)
|
|
|
|
numCharY = (int)(image.size[1]/CHAR_HEIGHT)
|
|
|
|
|
2023-07-17 03:10:00 +00:00
|
|
|
# Generate wrapper code
|
|
|
|
addWrapperCode(prefix,CHAR_WIDTH,CHAR_FIRST)
|
|
|
|
|
2023-07-10 00:58:05 +00:00
|
|
|
# Generate jump table for glyphs
|
2023-07-13 19:08:21 +00:00
|
|
|
print ("%scharacterJumpTable:" % prefix)
|
2023-07-10 00:58:05 +00:00
|
|
|
for charY in range(0,numCharY):
|
|
|
|
for charX in range(0,numCharX):
|
2023-07-13 19:08:21 +00:00
|
|
|
print ("\t.addr %s" % labelFromCharXY(prefix,CHAR_FIRST,charX,numCharX,charY))
|
2023-07-13 20:34:03 +00:00
|
|
|
print ("\n; Chroma Key is $%x\n" % CHROMA)
|
2023-07-10 00:58:05 +00:00
|
|
|
|
|
|
|
# Generate code for each glyph
|
|
|
|
for charY in range(0,numCharY):
|
|
|
|
for charX in range(0,numCharX):
|
|
|
|
|
2023-07-13 19:08:21 +00:00
|
|
|
print ("%s:\n" % labelFromCharXY(prefix,CHAR_FIRST,charX,numCharX,charY), end="")
|
2023-07-08 23:45:24 +00:00
|
|
|
|
|
|
|
# Header for each rendering operation
|
|
|
|
print ("\ttya") # Transfer character VRAM position from Y to stack pointer
|
|
|
|
print ("\ttcs")
|
|
|
|
|
2023-07-12 22:26:17 +00:00
|
|
|
# Iterate through all the pixels
|
2023-07-08 23:45:24 +00:00
|
|
|
charOriginX = charX*CHAR_WIDTH
|
|
|
|
charOriginY = charY*CHAR_HEIGHT
|
2023-07-13 20:34:03 +00:00
|
|
|
pendingStackMove = 0
|
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
for charRow in reversed(range(0,CHAR_HEIGHT)):
|
2023-07-13 19:08:21 +00:00
|
|
|
|
|
|
|
# Print a comment to make generated source easier to understand
|
|
|
|
print ("\t; Line %d, Pixel values: " % charRow, end="")
|
|
|
|
for grouping in range(0,CHAR_WIDTH,4):
|
|
|
|
print("%x%x%x%x " %
|
|
|
|
(pixels[charOriginY+charRow][charOriginX+0+grouping],
|
|
|
|
pixels[charOriginY+charRow][charOriginX+1+grouping],
|
|
|
|
pixels[charOriginY+charRow][charOriginX+2+grouping],
|
|
|
|
pixels[charOriginY+charRow][charOriginX+3+grouping]), end="")
|
|
|
|
print ("")
|
2023-07-12 23:10:29 +00:00
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
nextRowDelta = 160
|
2023-07-12 23:10:29 +00:00
|
|
|
localStackList = []
|
|
|
|
rowPushTotal = 0 # Row-right relative
|
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
for charCol in reversed(range(0,CHAR_WIDTH,4)):
|
|
|
|
nibbles = [pixels[charOriginY+charRow][charOriginX+charCol],
|
|
|
|
pixels[charOriginY+charRow][charOriginX+charCol+1],
|
|
|
|
pixels[charOriginY+charRow][charOriginX+charCol+2],
|
|
|
|
pixels[charOriginY+charRow][charOriginX+charCol+3]]
|
|
|
|
|
|
|
|
word = nibbles[2]<<12 | nibbles[3]<<8 | nibbles[0]<<4 | nibbles[1]
|
2023-07-12 23:10:29 +00:00
|
|
|
requiredStackIndex = charCol/2
|
2023-07-08 23:45:24 +00:00
|
|
|
|
|
|
|
if (nibbles[0]==CHROMA and nibbles[1]==CHROMA and nibbles[2]==CHROMA and nibbles[3]==CHROMA):
|
2023-07-13 19:08:21 +00:00
|
|
|
# Case 1 : All chroma, so let stack advance with no work
|
|
|
|
pass
|
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
elif (nibbles[0]!=CHROMA and nibbles[1]!=CHROMA and nibbles[2]!=CHROMA and nibbles[3]!=CHROMA):
|
2023-07-13 20:04:57 +00:00
|
|
|
# Case 2 : No chroma, so fast push of all four pixels
|
2023-07-12 23:10:29 +00:00
|
|
|
offsetNeeded = (CHAR_WIDTH/2-requiredStackIndex) - rowPushTotal - 2
|
2023-07-13 19:08:21 +00:00
|
|
|
|
2023-07-12 23:10:29 +00:00
|
|
|
if (offsetNeeded>0):
|
2023-07-13 20:34:03 +00:00
|
|
|
stackAdvance(offsetNeeded+pendingStackMove) # Advance stack to position needed for our two byte push
|
|
|
|
pendingStackMove = 0
|
2023-07-12 23:37:24 +00:00
|
|
|
nextRowDelta -= offsetNeeded
|
2023-07-13 19:08:21 +00:00
|
|
|
else:
|
2023-07-13 20:34:03 +00:00
|
|
|
stackAdvance(pendingStackMove) # First thing we did wasn't a stack move, so apply previous row pending first
|
|
|
|
pendingStackMove = 0
|
2023-07-13 19:08:21 +00:00
|
|
|
offsetNeeded=0
|
|
|
|
print ("\tpea $%04x" % word)
|
|
|
|
nextRowDelta -= 2
|
|
|
|
rowPushTotal += (2+offsetNeeded)
|
2023-07-08 23:45:24 +00:00
|
|
|
else:
|
2023-07-13 20:04:57 +00:00
|
|
|
# Case 3 : Mixed chroma, so we need to and-in mask and or-in sprite.
|
|
|
|
# These pixels are saved until the end of the row, then backfilled with stack-relative addressing
|
2023-07-13 19:08:21 +00:00
|
|
|
mask = 0xFFFF
|
2023-07-08 23:45:24 +00:00
|
|
|
if (nibbles[0]!=CHROMA):
|
2023-07-10 00:58:05 +00:00
|
|
|
mask = mask & 0xFF0F
|
2023-07-08 23:45:24 +00:00
|
|
|
if (nibbles[1]!=CHROMA):
|
2023-07-10 00:58:05 +00:00
|
|
|
mask = mask & 0xFFF0
|
2023-07-08 23:45:24 +00:00
|
|
|
if (nibbles[2]!=CHROMA):
|
2023-07-10 00:58:05 +00:00
|
|
|
mask = mask & 0x0FFF
|
2023-07-08 23:45:24 +00:00
|
|
|
if (nibbles[3]!=CHROMA):
|
2023-07-10 00:58:05 +00:00
|
|
|
mask = mask & 0xF0FF
|
2023-07-08 23:45:24 +00:00
|
|
|
|
|
|
|
sprite = word
|
|
|
|
if (nibbles[0]==CHROMA):
|
|
|
|
sprite = sprite & 0xFF0F
|
|
|
|
if (nibbles[1]==CHROMA):
|
|
|
|
sprite = sprite & 0xFFF0
|
|
|
|
if (nibbles[2]==CHROMA):
|
|
|
|
sprite = sprite & 0x0FFF
|
|
|
|
if (nibbles[3]==CHROMA):
|
|
|
|
sprite = sprite & 0xF0FF
|
|
|
|
|
2023-07-12 23:10:29 +00:00
|
|
|
localStackEntry = [requiredStackIndex,mask,sprite]
|
|
|
|
localStackList.append(localStackEntry)
|
|
|
|
|
2023-07-13 20:04:57 +00:00
|
|
|
# Process any local stack-relative work we accumulated
|
2023-07-12 23:10:29 +00:00
|
|
|
if len(localStackList) > 0:
|
|
|
|
if (rowPushTotal < CHAR_WIDTH/2): # Get stack pointer to end of row if needed
|
2023-07-12 23:37:24 +00:00
|
|
|
cleanupPush = CHAR_WIDTH/2-rowPushTotal
|
2023-07-13 20:34:03 +00:00
|
|
|
stackAdvance(cleanupPush + pendingStackMove)
|
|
|
|
pendingStackMove=0
|
2023-07-12 23:10:29 +00:00
|
|
|
rowPushTotal = CHAR_WIDTH/2
|
2023-07-12 23:37:24 +00:00
|
|
|
nextRowDelta -= cleanupPush
|
|
|
|
|
2023-07-13 19:08:21 +00:00
|
|
|
extraReach = rowPushTotal - CHAR_WIDTH/2 + 1 # Amount to "reach back" from one byte past end of row so LDA/STA can fill in skipped pixels
|
2023-07-12 23:10:29 +00:00
|
|
|
for stackEntry in localStackList:
|
|
|
|
print ("\tlda %d,S" % (stackEntry[0] + extraReach)) # Blend mask, sprite, and background
|
|
|
|
print ("\tand #$%04x" % stackEntry[1])
|
|
|
|
print ("\tora #$%04x" % stackEntry[2])
|
|
|
|
print ("\tsta %d,S" % (stackEntry[0] + extraReach))
|
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
# Advance stack pointer to next row
|
2023-07-13 20:34:03 +00:00
|
|
|
pendingStackMove += nextRowDelta # Save this stack move for next row, because we can often combine them
|
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
# Footer for each rendering operation
|
2023-07-17 03:10:00 +00:00
|
|
|
print ("\tjmp renderCharJumpReturn_%s\n" % prefix)
|
|
|
|
return
|
|
|
|
|
2023-07-17 22:53:17 +00:00
|
|
|
def scaleForFontWidth(charWidth):
|
|
|
|
output = ""
|
|
|
|
for i in range(0,(int)(charWidth/4)):
|
|
|
|
output = output + "\tasl\n"
|
|
|
|
return output
|
|
|
|
|
2023-07-17 03:10:00 +00:00
|
|
|
def addWrapperCode(prefix, charWidth, firstChar):
|
|
|
|
code = """
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
; renderString_{:s}
|
|
|
|
;
|
|
|
|
; Draws a Pascal string for font "{:s}"
|
|
|
|
;
|
|
|
|
; PARAML0 = Pointer to string
|
2023-07-17 22:53:17 +00:00
|
|
|
; Y = VRAM position of lower left corner of string at which to draw
|
2023-07-17 03:10:00 +00:00
|
|
|
;
|
|
|
|
; Trashes SCRATCHL,X,Y,A
|
|
|
|
;
|
|
|
|
renderString_{:s}:
|
|
|
|
sty SCRATCHL ; Cache VRAM position
|
|
|
|
|
|
|
|
plb ; Temporarily revert to caller's DBR to access their pointer
|
|
|
|
BITS8
|
|
|
|
lda (PARAML0)
|
|
|
|
tax
|
|
|
|
BITS16
|
|
|
|
phb
|
|
|
|
|
2023-07-17 22:53:17 +00:00
|
|
|
; Advance VRAM pointer to end of string
|
|
|
|
{:s}
|
|
|
|
clc
|
|
|
|
adc SCRATCHL
|
|
|
|
dec
|
|
|
|
sta SCRATCHL
|
|
|
|
|
2023-07-17 03:10:00 +00:00
|
|
|
renderStringLoop_{:s}:
|
|
|
|
|
|
|
|
; Fetch and render next character in string
|
|
|
|
txy
|
|
|
|
lda #0
|
|
|
|
plb ; Temporarily revert to caller's DBR to access their pointer
|
|
|
|
BITS8A
|
|
|
|
lda (PARAML0),y
|
|
|
|
BITS16
|
|
|
|
phb
|
|
|
|
ldy SCRATCHL
|
|
|
|
jsr renderChar_{:s}
|
|
|
|
|
|
|
|
dex
|
|
|
|
beq renderStringDone_{:s}
|
|
|
|
|
|
|
|
; Calculate VRAM pointer for position of next character
|
|
|
|
lda SCRATCHL
|
|
|
|
sec
|
|
|
|
sbc #{:d}/2 ; Width of one char in bytes
|
|
|
|
sta SCRATCHL
|
|
|
|
bra renderStringLoop_{:s}
|
|
|
|
|
|
|
|
renderStringDone_{:s}:
|
|
|
|
jmp renderStringReturn
|
|
|
|
|
|
|
|
.export renderString_{:s}
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
; renderChar_{:s}
|
|
|
|
;
|
|
|
|
; Draws a single character
|
|
|
|
;
|
|
|
|
; A = ASCII code to draw
|
|
|
|
; Y = VRAM position of lower right corner at which to draw
|
|
|
|
;
|
|
|
|
renderChar_{:s}:
|
|
|
|
SAVE_AXY
|
|
|
|
|
|
|
|
sec
|
|
|
|
sbc #{:d} ; ASCII code of first char in font sheet
|
|
|
|
asl
|
|
|
|
tax
|
|
|
|
FASTGRAPHICS
|
|
|
|
|
|
|
|
jmp ({:s}characterJumpTable,x)
|
|
|
|
|
|
|
|
renderCharJumpReturn_{:s}: ; Compiled glyphs jump back here. Can't rts because stack is turboborked
|
|
|
|
SLOWGRAPHICS
|
|
|
|
|
|
|
|
RESTORE_AXY
|
|
|
|
rts
|
|
|
|
|
2023-07-17 22:53:17 +00:00
|
|
|
""".format(prefix,prefix,prefix,scaleForFontWidth(charWidth),prefix,prefix,prefix,charWidth,prefix,prefix,prefix,prefix,prefix,firstChar,prefix,prefix)
|
2023-07-17 03:10:00 +00:00
|
|
|
print (code)
|
|
|
|
return
|
|
|
|
|
|
|
|
|
2023-07-08 23:45:24 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main(sys.argv[1:])
|
2023-07-17 03:10:00 +00:00
|
|
|
|
|
|
|
|