;; ;; Routines common to several viewers go here ;; ;; ;; Copyright 2003, Raphael Espino ;; last updated 14-Oct-03 ;; ABORTCD = 128 ; code to tell decoder to abort BUFWID = 40 ; each buffer of data will be 40 columns wide ;; ------------- decoder information addresses ------------------ ;; mixed case addresses can be modified by viewer, changes made to ;; these will be read by the decoder LastCol = $600 ; Last column number to display LastRow = $601 ; Last row number to display FirstCol = $602 ; Column offset (left edge of output image) pixels/8 FirstRow = $603 ; Row offset (top edge of output image) pixels/8 IocbNum = $604 ; IOCB to read jpeg data from Error = $605 ; non 0 if error ocurred decoding jpeg, set to ABORT ; for abort from viewer, error codes as shown below ;; upper case addresses are set by decoder and should not be modified ;; by viewer WIDTH = $606 ; Width of image in pixels (2 bytes) - keep together HEIGHT = $608 ; Height of image in pixels (2 bytes) - keep together STACKPT = $60A ; stack pointer at program start, use to return ; to DOS at any point VERSION = $60B ; decoder version number RERUN = $60C ; 2 bytes - restart address MEMTOP = $60E ; End of output buffer, above this is free for viewer ; (2 bytes) SKIPERR = $610 ; Error in an image with restart markers ; decoder will continue decoding and set this to non 0 BUFCOUNT = $611 ; horizontal buffer count, this is increased by 1 for ; every buffer full of data on the current row ; the last buffer full will have msb set ;; page 6 addresses from $630 up are available to viewer ;; ------------- end of decoder info addresses ---------------------- OPEN = 3 ; open channel GETLNE = 5 ; get line GETBUF = 7 ; get buffer PUTLNE = 9 ; put line PUTBUF = 11 ; put buffer CLOSE = 12 ; close channel ;; IOCB open modes READ = 4 ; open for read READDIR= 6 ; open for directory read WRITE = 8 ; open for write ;; ;; Switch DMA ;; SetDMAOff: ; DMA off lda #0 SetDMA: ; Set DMA to value in Acc sta SDMCTL sta DMACTL rts ;; ;; Handle keypresses during decoding of image ;; DMAToggle: lda KBCODE and #%00111111 ; ignore Shift and Control keys cmp #40 ; 'r' aborts and reruns with same file beq @redisp cmp #18 ; 'c' aborts and reruns with same file bne @ckesc @redisp: inc repeat bne @abort ; forced branch @ckesc: cmp #28 ; ESC key aborts bne @ckdma @abort: lda #ABORTCD ; tell decoder to abort sta Error bne OSKEYBIRQ ; forced branch @ckdma: cmp #62 ; toggle DMA if 's' key is pressed bne OSKEYBIRQ ; otherwise pass key press to OS lda SAVEMODE bne OSKEYBIRQ ; don't toggle DMA if saving txa pha lda SDMCTL ; toggle DMA value ldx SAVEDM stx SDMCTL stx DMACTL sta SAVEDM pla tax OSKEYBIRQ: jmp 30000 ; gets changed - jump to previous keyb IRQ ;; ;; Trap TAB keypress, use it to complete the filename when pressed ;; TabIRQ: lda KBCODE cmp #44 ; use tab key to display previouis filename bne OSKEYBIRQ tya pha ldy #0 sty OLDCHR ; reset old character under cursor to space tya @lp2: iny sta (rendpt),y ; clear 254 bytes on screen to make sure there bne @lp2 ; isn't any junk left on screen @lp: lda RDBUF,y ; copy last filename to screen cmp #155 ; return char marks end of filename beq @exit jsr asc2int ; convert to internal sta (rendpt),y iny bpl @lp @exit: lda #128 ; cursor won't reappear until we move it sta (rendpt),y ; so simulate one with an inverse space tya adc #6 ; 7 characters for ' File:' sta COLCRS lda drawtemp sta ROWCRS tya clc adc rendpt ; move old cursor position too sta OLDADR ; so that inverse space will get lda #0 ; overwritten when cursor moves adc rendpt+1 sta OLDADR+1 pla tay pla ; ignore keypress rti ;; ;; Restore Keyboard IRQ back to default, disabling our IRQ ;; ClrKeyIRQ: sei lda OSKEYBIRQ+1 sta VKEYBD lda OSKEYBIRQ+2 sta VKEYBD+1 cli rts ;; ;; Get a key press without waiting for the return key to be pressed ;; ;; Returns ;; Acc = internal key code GetKey: ldx #0 stx HELPFG ; clear previous Help keypress dex stx CH ; clear previous keypress @waitkpd: lda HELPFG ; check for the Help key too bne @helpkeyp lda CH cmp #255 beq @waitkpd @helpkeyp: ldx #255 ; clear keypress stx CH inx stx HELPFG ; clear Help keypress and #%00111111 ; ignore shift and control modifiers rts ;; ;; Get number from user ;; GetNum: jsr GetLine ; read input data from user lda #0 tay @loop2: sta @temp @loop: lda RDBUF+2,Y cmp #155 ; finished if return key press found beq @done and #%01011111 cmp #'C' ; C means centre image bne @notctr sec ; return with carry set for centre rts @notctr: iny and #%00001111 ; convert from ATASCII to number sta @temp+1 ldx #10 lda #0 clc @l2: adc @temp ; multiply by 10 dex bne @l2 adc @temp+1 jmp @loop2 @done: lda @temp clc ; don't centre image rts @temp: .word 0 ;; ;; Get a line worth of data ;; GetLine: ldx #0 lda #GETLNE ; read line of data into buffer at RDBUF+2 ldy #(rdbuf2io-icbdat) jmp SetICBICC ;; ;; Get keypress for menu selection ;; GetMenuKp: ldx #0 lda #GETLNE ldy #(bufio-icbdat) ; set ICBLL/H to 0, don't care about ICBAL/H jsr SetICBICC ; get keypress cmp #155 beq @rts pha jsr CIOV ; ignore return key press pla and #127 ; convert menu selection character to uppercase cmp #97 bcc @rts and #%11011111 @rts: rts ;; ;; Calculate displayed image width as minimum of display width ;; and image width. Images can be larger or smaller than display ;; this calculates exactly how many pixels wide the image displayed ;; on screen will be ;; CalcWidth: lda #0 sta tmpcalcwid+1 sta dispwidth+1 sta bufpixs+1 sta tmpheight+1 lda dispcols sta dispwidth lda #BUFWID ; number of columns in a buffer sta bufcols sta bufpixs lda FirstRow sta tmpheight ldx #3 lda FirstCol ; convert horizontal image offset from @mult8: ; colums to pixels (1 col = 8 pixels) asl rol tmpcalcwid+1 asl dispwidth ; multiply a few other values by 8 too rol dispwidth+1 asl bufpixs rol bufpixs+1 ; by 8 to get number of pixels in a buffer asl tmpheight rol tmpheight+1 dex bne @mult8 sta tmpcalcwid lda WIDTH ; subtract offset in pixels from image sec ; width sbc tmpcalcwid ; substract FirstCol*8 sta tmpcalcwid lda WIDTH+1 sbc tmpcalcwid+1 sta tmpcalcwid+1 ldy USEFILT ; adjust image width to account for bmi @nofilt ; horizontal filter beq @filtx2 jsr DivTmpWid @filtx2: jsr DivTmpWid @nofilt: ldx tmpcalcwid lda tmpcalcwid+1 ; get smallest of image width and display width cmp dispwidth+1 bcc @notmx ; image width < display width beq @cklo ; need to check low byte bcs @max ; image width > display width @cklo: cpx dispwidth bcs @max @notmx: ; image width < display width sta dispwidth+1 ; set to displayed image width stx dispwidth @max: ; image width > display width, leave ; displayed image width set to display width ldx dispwidth ldy dispwidth+1 @lstlp: stx lastpixs ; calculate how many pixels there will be sty lastpixs+1 ; in the last buffer of data txa sec sbc bufpixs tax tya sbc bufpixs+1 tay bne @ckbcs txa beq @xitlp @ckbcs: bcs @lstlp ; not reached last buffer, lets repeat it @xitlp: lda dispcols @lstclp: sta lastcols ; do same for cols as we did for pixels sec ; calculate number of columns in last buffer sbc bufcols beq @xitlst bcs @lstclp @xitlst: lda dispwidth and #%00000011 ; calculate fractional part of WIDTH/4 sta dispwidfrac ; this is used by the filters lda HEIGHT ; subtract row offset from height sec sbc tmpheight tax lda HEIGHT+1 sbc tmpheight+1 sta tmpheight txa ldy USEVFILT ; adjust height taking vert filter into account bmi @novfilt beq @vfiltx2 lsr tmpheight ror @vfiltx2: lsr tmpheight ror @novfilt: sta dispheight ldx tmpheight bne @notvmx cmp #200 bcc @vmax @notvmx: ; image width < display width lda #200 ; max of 200 lines high sta dispheight @vmax: ; image width > display width, leave alone rts ;; ;; Divide width variables by 2, convenience routine ;; Acc=WIDTH low byte ;; returns ;; Acc=WIDTH/2 low byte ;; DivTmpWid: lsr bufcols lsr tmpcalcwid+1 ror tmpcalcwid lsr bufpixs+1 ror bufpixs rts pixcnt: .word 0 ; number of pixels to process in current buffer pixcntm1: .word 0 ; pixcnt - 1 colcnt: .byte 0 ; number of cols to process in current buffer lastpixs: .word 0 ; number of pixels in last buffer lastcols: .byte 0 ; number of cols in last buffer bufpixs: .word 0 ; number of pixels in a full buffer bufcols: .byte 0 ; number of cols in a full buffer ;; ;; Set number of columns of data expected in the next buffer ;; SetColCnt: lda BUFCOUNT bmi @lstbuf ; is this the last buffer full of data? lda bufcols ; update number of columns and pixels sta colcnt ; to expect next time round ldy bufpixs sty pixcnt dey sty pixcntm1 lda bufpixs+1 sta pixcnt+1 jmp @exit @lstbuf: ldy lastcols ; this is the last buffer full of data sty colcnt ; update number of columns and pixels ldy lastpixs ; to expect next time round sty pixcnt dey sty pixcntm1 ldy lastpixs+1 sty pixcnt+1 @exit: rts ;; ;; Set screen screen pointers ready for next buffer full ;; SetScrPos: lda BUFCOUNT bmi @lstbuf bne @not1st lda scrpt1 ; this is the 1st buffer full, save the sta bufend ; ending screen position for next row lda scrpt1+1 sta bufend+1 lda scrpt2 sta bufend2 lda scrpt2+1 sta bufend2+1 @not1st: ; this is not the 1st buffer lda bufstart ; move screen pointer to destination area of clc ; next buffer of data adc colcnt sta scrpt1 lda bufstart+1 adc #0 sta scrpt1+1 lda bufstart2 ; move screen pointer to destination area of clc ; next buffer of data adc colcnt sta scrpt2 lda bufstart2+1 adc #0 sta scrpt2+1 ldx nxtline ; remember current position in dither buffer ldy nxtline+1 inx bne @noiny iny @noiny: stx nxtval sty nxtval+1 jmp @exit @lstbuf: and #%01111111 beq @exit ; whole image fits in 1 buffer lda bufend sta scrpt1 lda bufend+1 sta scrpt1+1 lda bufend2 sta scrpt2 lda bufend2+1 sta scrpt2+1 @exit: rts bufstart: .word 0 ; starting pixel of current buffer bufstart2: .word 0 ; starting pixel of current buff for 2nd screen bufend: .word 0 ; end of current row/start of next row bufend2: .word 0 ; end of cur row/start of next row for 2nd scr ;; ;; Display row and column information ;; DispRowCol: jsr strout .byte "Col=", 0 lda FirstCol jsr AdjRedc jsr strout .byte " Row=", 0 jmp AdjRedcRow ;; ;; Display previous row and column information ;; ;; Acc = 0 - display column info ;; Acc = 1 - display row info ;; DispPrevRowCol: pha jsr strout .byte " (",0 pla tax lda PrevColOff,x jsr AdjRedc lda #')' jmp PRINT1BYTE PrevColOff: .byte 0 PrevRowOff: .byte 0 ;; ;; Display image width and height information ;; DispWidHei: jsr strout .byte " ",0 lda numcols ; display number of columns taking filter jsr AdjRedc ; into account jsr strout .byte "x",0 lda numrows ; display number of rows taking filter jmp AdjRedc ; into account ;; ;; Adjust row/column values to take reduction into account ;; ;; Params ;; Acc = value to modify (for AdjRedc only) ;; Returns ;; X = modified value ;; AdjRedcRow: lda FirstRow AdjRedc: ldx USEVFILT bmi @rts ; no reduction, return original value beq @x2 ; reduce by factor of 2 lsr ; divide by 2 adc #0 ; and round up @x2: lsr adc #0 @rts: tax jmp DecOut ; display result ;; ;; Display reduction information ;; DispRedc: lda USEVFILT DispRedcA: ; redc value is already in Acc bmi @noredc beq @redc2 jsr strout .byte "Quarter",0 rts @redc2: jsr strout .byte "Half",0 rts @noredc: jsr strout .byte "None",0 rts ;; ;; Convert a 9 bit decimal number to ATASCII, X=bits 1-8, C=bit 9 ;; DecOutRes: ; store ATASCII number in reslt, but don't display on the screen lda #' ' ldy #4 @erslp: sta reslt,y ; initialise result buffer dey bpl @erslp lda #1 bne decout9 .byte $AC ; LDY ABS - ignore next 2 bytes, need to keep Carry intact DecOut: ; store ATASCII number in reslt, and display on the screen lda #0 clc decout9: pha ldy #2 ; 3 characters sty decfirst @divlp: ; divide number by 10, the remainder gives us lda #0 ; the digit to display, then repeat for next digit rol stx declowb ldx #8 @loop: asl declowb rol cmp #10 bcc @skip sbc #10 inc declowb @skip: dex bne @loop ldx declowb cmp #0 ; remainder in accumulator beq @skipf sty decfirst ; if not 0, then update 1st character pos @skipf: clc adc #48 ; convert to ATASCII sta reslt,y dey bpl @divlp ; do next digit pla bne rts1 ; not displaying number, leave it in buffer clc lda decfirst ; get position of 1st non-zero character ldx #>reslt adc #SCRADR bne @swscr1 ; only display screen 1 .endif @both: lda #>SCRADR ; if currently on screen 1, switch to 2 cmp DLADR+3 beq @swscr2 @swscr1: sta DLADR+3 ; otherwise switch to screen 1 lda #(>SCRADR)+16 sta DLADR+107 lda RENDMODE @ckhip: cmp #'4' ; check for HIP mode (mode 4) bne @swclr lda #65 ; HIP mode, so change DLI sta hipgr1+1 lda #129 sta hipgr2+1 bne @exit @swclr: .if DEBUG lda vbiscr ; check if scr 1, 2 or both should be displayed bne @exit ; display both screens alternately .endif lda COLOR1 ; swap colours for odd and even screen lines ldx dlicol1 ; around in 2 screen GR.8 and GR.15 modes stx COLOR1 sta dlicol1 lda COLOR2 ; don't need to do this for GR.8 but it doesn't ldx dlicol2 ; hurt do to it anyway and saves a few bytes stx COLOR2 sta dlicol2 @exit: pla tax jmp EXITIM @swscr2: ; switch to screen 2 lda #>SCR2ADR sta DLADR+3 lda #(>SCR2ADR)+16 sta DLADR+107 lda RENDMODE cmp #'4' ; check for HIP mode (mode 4) bne @swclr lda #129 ; HIP mode, so change DLI sta hipgr1+1 lda #65 sta hipgr2+1 jmp @exit changeclr: .byte 0 .if DEBUG vbiscr: .byte 0 ; display screen 1, 2 or both in flicker modes .endif ;; ;; Disable VBI + DLIs ;; VBDLIOFF: jsr SetNMIEN ; make sure DLI's are disabled ldy #EXITIM bne setimvbi VBION: ldy #IMVBI setimvbi: lda #6 jmp SETVBV ;; ;; Reset IRQ and DMA ;; Reset: lda VKEYBD+1 ; if IRQ was not put in place, then leave it cmp #>DMAToggle ; alone. IRQ not set up when saving to disk bne resrts ; and save to disk does it's own DMA checking lda VKEYBD ; so if we don't reset IRQ then leave DMA cmp #320 sta scrpt1+1 jsr addtopt320 inc drawcount ; keep track of number of lines copied dec rendline ; until all lines in current block are saved bne @nxtline lda BUFCOUNT ; only save the block when we get to the bmi @saveblk ; last buffer for current horizontal strip jmp SetScrPos @saveblk: lda drawcount ; save lines previously copied sta rendline jsr SetScrPt1 stx rendpt sty rendpt+1 @nohfilt: lda rendpt ; set address to save data from sta putadr2 lda rendpt+1 sta putadr2+1 ldy #(putadr2-icbdat) ; save 1 line of data at a time ldx #(SAVECHN*16) ; IOCB should already be open jsr SetICBPut bmi SaveErr lda savelines ; stop at end of image beq UnusedVec dec savelines jsr addtopt320 dec rendline ; until all lines in current block are saved bne @nohfilt UnusedVec: rts savelines: .byte 0 ; number of lines to saved, used when saving ; in PGM format ;; ;; Save error occured while decoding, need to reset interrupts ;; close file and switch screen to GR.0 ;; SaveErr: jsr Reset ; restore DMA + IRQ jsr OpenGr0 jsr CloseInFile ; close input file jsr RestFN jsr SvErrMsg jmp WRunMenu ;; ;; Save error occured while opening file/writing headers ;; SaveErr2: jsr SvErrMsg lda tmpsavdisp ; image already displayed? bne @showhlp ; yes ldx #255 jsr WAITKEYP ; wait for key press jmp SavePt2 ; go back to save options @showhlp: sec rts ;; ;; Display save error message, and clean up ;; SvErrMsg: jsr NLStrOut .byte "Save error ",0 ldx ICSTA + (SAVECHN*16) jsr DecOut jsr PrintNL jmp CloseOutFile ; close output file ;; ;; Reduce pixels vertically, we do this by creating a line from the ;; average pixel values of the current and next lines. The resulting ;; line will overwrite the line after the current one, and rendpt ;; will be changed to point to the start of the new line ;; VFilter11: jsr HFilter ; do horizontal filter for first line VFilter11NoH: lda rendline ; if only 0 or 1 lines left, leave them alone cmp #2 bcc @rts dec rendline jsr SetFiltPt stx nxtline sty nxtline+1 jsr addtopt320 ; move rendpt to next line jsr HFilter ; do horizontal filter for 2nd line jsr SetFiltPt stx @destadr+1 sty @destadr+2 ldx #0 @filtlp: ldy #0 lda (nxtline),y ; get 1st pixel lsr ; divide it by 2 sta drawtemp+1 lda (filtpt),y ; get 2nd pixel lsr ; divide it by 2 clc adc drawtemp+1 ; and add to pixel 1 @destadr: sta 30000,x ; store result back at original pos lda #1 ; and skip a pixel jsr addtofl inc nxtline bne @noinchi inc nxtline+1 @noinchi: inx cpx pixcnt bcc @filtlp ; no, go back do some more @rts: rts ;; ;; Apply appropriate filter to image if necessary, otherwise ;; just return ;; Filter: lda USEVFILT beq VFilter11 bpl VFilter1111 jmp HFilter ;; ;; Apply divide by 4 vertical filter ;; VFilter1111: ldx rendline bne @not0 rts ; if 0 lines left, leave alone @not0: dex bne @not1 jmp HFilter ; do horizontal filter if only 1 line left @not1: dex bne @not2 ; if 2 lines left, do the vertical 1-1 filter jmp VFilter11 @not2: dex bne @dov4 ; at least 4 lines left, do as normal jsr VFilter11 ; 3 lines left, do vertical 1-1 filter twice jmp VFilter11NoH @dov4: jsr HFilter ; do horizontal filter for first line jsr SetFiltPt stx nxtline sty nxtline+1 jsr addtopt320 ; move rendpt to next line jsr HFilter ; do horizontal filter for 2nd line jsr SetFiltPt stx menupt sty menupt+1 jsr addtopt320 jsr HFilter jsr SetFiltPt stx vfiltpt sty vfiltpt+1 jsr addtopt320 jsr HFilter jsr SetFiltPt stx @destadr+1 sty @destadr+2 lda rendline sec sbc #3 sta rendline ldx #0 @filtlp: ldy #0 lda (nxtline),y ; get 1st pixel lsr ; divide it by 2 sta drawtemp+1 lda (menupt),y lsr clc adc drawtemp+1 sta drawtemp+1 lda (vfiltpt),y lsr lsr drawtemp+1 clc adc drawtemp+1 sta drawtemp+1 lda (filtpt),y ; get 2nd pixel lsr ; divide it by 2 lsr drawtemp+1 clc adc drawtemp+1 ; and add to pixel 1 @destadr: sta 30000,x ; store result back at original pos lda #1 ; and skip a pixel jsr addtofl inc nxtline bne @noinnhi inc nxtline+1 @noinnhi: inc menupt bne @noincmhi inc menupt+1 @noincmhi: inc vfiltpt bne @noinvhi inc nxtline+1 @noinvhi: inx cpx pixcnt bcc @filtlp ; no, go back do some more rts ;; ;; Apply divide by 2 horizontal filter ;; filter11: stx @destadr+1 sty @destadr+2 ldx #0 @filtlp: ldy #0 lda (filtpt),y ; get 1st pixel lsr ; divide it by 2 sta drawtemp+1 iny lda (filtpt),y ; get 2nd pixel lsr ; divide it by 2 clc adc drawtemp+1 ; and add to pixel 1 @destadr: sta 30000,x ; store result back at original pos lda #2 ; and skip a pixel jsr addtofl @noinchi: inx cpx pixcntm1 bcc @filtlp ; no, go back do some more beq @lastpix lda #0 ; when we have reached right edge, empty cpx #160 ; rest of line bcc @destadr rts @lastpix: lda dispwidfrac ; if there are 2 pixels left to process cmp #2 ; then do as normal beq @filtlp ldy #0 ; otherwise store last pixel lda (filtpt),y jmp @destadr ;; ;; Apply appropriate filter to image if necessary, otherwise ;; just return ;; HFilter: jsr SetFiltPt lda USEFILT beq filter11 bpl filter1111 rts ;; ;; Apply horizontal divide by 4 filter ;; filter1111: stx @destadr+1 sty @destadr+2 ldx #0 @filtlp: ldy #0 lda (filtpt),y ; get 1st pixel lsr ; divide it by 4 lsr sta drawtemp+1 iny lda (filtpt),y ; get 2nd pixel lsr ; divide it by 4 lsr clc adc drawtemp+1 ; and add to previous pixels sta drawtemp+1 iny lda (filtpt),y ; get 3rd pixel lsr ; divide it by 4 lsr clc adc drawtemp+1 ; and add to previous pixels sta drawtemp+1 iny lda (filtpt),y ; get 4th pixel @div4ret: lsr ; divide it by 4 @div2ret: lsr clc adc drawtemp+1 ; and add to previous pixels @destadr: sta 30000,x ; store result back at original pos lda #4 ; and skip 4 pixels jsr addtofl @noinchi: inx cpx pixcntm1 bcc @filtlp ; no, go back do some more beq @lastpix lda #0 ; when we have reached right edge, empty cpx #160 ; rest of line bcc @destadr rts @lastpix: ldy #0 lda dispwidfrac beq @filtlp ; 4 pixels left to process, do as normal cmp #1 beq @pix1 cmp #2 beq @pix2 ; 2 pixels left to process ;; 3 pixels left to process lda (filtpt),y ; get 1st pixel lsr ; divide it by 4 lsr sta drawtemp+1 iny lda (filtpt),y ; get 2nd pixel lsr ; divide it by 2 clc adc drawtemp+1 ; and add to pixel 1 sta drawtemp+1 iny lda (filtpt),y ; get 3rd pixel jmp @div4ret ; divide it by 4 @pix2: lda (filtpt),y ; get 1st pixel lsr ; divide it by 2 sta drawtemp+1 iny lda (filtpt),y ; get 2nd pixel jmp @div2ret ; divide it by 2 and display it @pix1: ldy #0 ; otherwise store last pixel lda (filtpt),y jmp @destadr ;; ;; Set up filter pointer from rendpt ;; SetFiltPt: ldx rendpt ; point current filter position at stx filtpt ; the current line ldy rendpt+1 sty filtpt+1 rts ;; ;; Save current screen pointer values ;; SaveScrPt: lda scrpt1 sta bufstart lda scrpt1+1 sta bufstart+1 lda scrpt2 sta bufstart2 lda scrpt2+1 sta bufstart2+1 rts ;; ;; Initialise variables to dither next line ;; SetDith: lda nxtval sta nxtline lda nxtval+1 sta nxtline+1 ldy rendline ; add in last dither value from previous lda nxtdith-1,y ; buffer full ldy #1 ; set to 1 as nxtline is dithbuff-1 clc adc (nxtline),y sec sbc #$80 ; adjust 0 point back to 128 sta nxterr lda #128 sta (nxtline),y dey sty dithend ; not reached end of line yet sty widhi sty tmpx sty tmpy rts ;; ;; Dithering works by calculating the difference between the pixel ;; colour we wanted and the colour we actually got. This difference ;; is the error value. This error value is then distributed over ;; adjacent pixels and added to those pixels as they are processed. ;; Error values are distributed as follows: ;; ;; PIX E*7/16 (E) ;; E*1/16 E*5/16 E*3/16 ;; (SW) (S) (SE) ;; ;; This implementation uses a 1 line buffer plus 1 extra byte (nxterr). ;; E*7/16 is stored in nxterr, the other 3 error values are stored in ;; the buffer. This works because we scan pixels left to right, top to ;; bottom, so E*7/16 will always be used immediately after the current ;; pixel, the remaining values will only be used on the next line. ;; ;; But there's more. Each error value is stored in 1 byte, as the ;; error is always going to be 128 <= E <= -127 if the lowest available ;; colour is 0 and the highest is 255. Instead of using 2's complement ;; signed arithmatic, we use a different system. ;; A value of 128 is taken to be 0, anything > 128 is positive, ;; anything < 128 is negative. Subtacting 128 from the value gives you ;; the real number, i.e. 127-128= -1. ;; ;; This gets around the problem of multiplying/dividing 2's complement ;; numbers and checking for overflows, but we have to be careful since ;; the value for '0' changes as we add/multiply/etc: ;; i.e. 3*0=0, right? But here '0' is 128, and 3*128=384, ;; so 384 is '0', now numbers > 384 are positive numbers < 384 are ;; negative. Then if we divide by 16, now 64 is '0'. ;; Same thing happens when you add/subtract numbers. This is OK, 'cos ;; after all the maths has been done we add or subtract a number to the ;; result to make 128='0' again. That's why those misterious 'adc #' ;; 'sbc #' are done after the maths. ;; ;; This gives a small overhead in terms of code/CPU cycles - just a ;; few extra clc/adc #/sec/sbc # instructions ;; (but has higher overhead in terms of mental grunt work - ;; but that's OK, you've got more brain cells than the Atari has bytes) ;; ;; params: ;; Y=tmpy - current index into line ;; ;; returns with carry set if end of line reached, carry clear otherwise DITHPIX: sta E1 ; Error value tya beq @no ; first pixel in line, don't do (SW) pixel lda E1 ; calculate Error * 1/16 lsr lsr lsr lsr ldy #0 ; Do (SW) pixel clc adc (nxtline),y ; add error to any existing value sec sbc #8 ; make 0 point = 128 sta (nxtline),y ; and store it back into buffer @no: ldy #2 ; do (SE) pixel lda (nxtline),y ; save next error val before it is overwritten sta nxterr lda E1 ; calculate Error * 3/16 lsr clc adc E1 ror tax lsr lsr clc adc #104 ; adjust 0 point to 128 sta (nxtline),y lda E1 ; calculate Error * 5/16 lsr lsr clc adc E1 ror lsr dey ; do (S) pixel clc adc (nxtline),y sec sbc #40 ; adjust 0 point to 128 sta (nxtline),y txa ; calculate Error * 7/16 clc adc E1 ror lsr clc adc nxterr sec sbc #56 ; adjust 0 point to 128 sta nxterr ; do (E) pixel inc nxtline ; move to next pixel lda nxtline bne @noinnx inc nxtline+1 @noinnx: ldy tmpy iny bne @noindt inc widhi inc rendpt+1 @noindt: lda widhi ; indicate if we've reached end of line cmp pixcnt+1 bcc @notend cpy pixcnt bcc @notend ; return with C clear inc dithend ; end of line reached sec ; return with C set @notend: rts SAVEDM: .byte 0 ; save previous DMA value RENDMODE: .byte 0 ; graphics mode menu selection SAVEMODE: .byte 0 ; last save operation type USE2SCR: .byte 0 ; use 2 screens? USEFILT: .byte 0 ; use filter: 255=no, 0=1/2, 1=1/4 USEVFILT: .byte 0 ; vertical filter: 255=no, 0=1/2, 1=1/4 savecount: .byte 0 ; number of blocks saved so far, used for ; formats with fixed image height (eg. .MIC) prevopt: .byte 255 ; previous display option selected dispprev: .byte 255 ; display prev row/col message ctrcol: .byte 0 ; centre image horizontally ctrrow: .byte 0 ; centre image vertically, should always be ; immediately after ctrcol numcols: .byte 0 ; image width, 1 column = 8 pixels (pixels/8) numrows: .byte 0 ; image height 1 row = 8 pixels (pixels/8) E1: .byte 0 ; dithering error value nxterr: .byte 128 ; used for calculating error value in dithering dispwidfrac: .byte 0 ; fractional part of MAX(dispwidth,WIDTH)/4 dispheight: .byte 0 ; Height of display in pixels rendline: .byte 0 ; lines left to draw in current buffer drawcount: .byte 0 ; lines drawn to screen from current buffer DO2SCR: .byte 255 ; use 2 screen (flicker) modes ;; ;; Find nearest available grey to the requested one ;; X = initial hash into grey table ;; Acc = grey value to search for ;; ;; returns ;; Acc = index into table ;; Y = Error value ;; FNDNR: eor #128 cmp CUTOFF,X bcc @flowr ; value is lower than current index beq @fndcset ; found it @fhighr: inx ; value is higher than current index cmp CUTOFF,X ; so try next one beq @fndcset bcs @fhighr ; still higher, try next one sec @fndcset: ; ensure carry is set if using this label sbc GREYTBL,X eor #128 tay txa sec sbc MINGREYPOS rts @flowr: cpx MINGREYPOS beq @fndcset cmp CUTOFF-1,X bcs @fndcset dex jmp @flowr MINGREYPOS: .byte 0 ;; ;; Display embedded string on screen ;; string data should follow strout call and be terminated ;; with a 0 ;; NLStrOut: ; print a return char before the string jsr PrintNL strout: pla ; get string address from stack tay pla tax iny bne @NIN2 inx @NIN2: sty putadr3 ; point at start of string stx @loop+2 stx putadr3+1 ldx #0 ; no data yet stx putadr3+2 stx putadr3+3 @loop: lda $c000,y ; - this address gets modified beq @exit ; continue until we find terminating 0 inc putadr3+2 ; increase length count bne @skplh inc putadr3+3 @skplh: iny bne @loop inc @loop+2 ; bump up to next page bne @loop @exit: tya pha ldy #(putadr3-icbdat) jsr SetICBPut ; X should already be 0 pla tay ; modify return address to lda @loop+2 ; return immediately after pha ; terminating 0 tya pha rts ;; ;; Put a return char (newline) on the screen ;; PrintNL: lda #155 ;; use CIO to put 1 byte on screen PRINT1BYTE: sta @dispchar jsr strout @dispchar: .byte 0,0 rts ;; ;; Convert a number from internal screen code to ATASCII ;; (this doesn't work for inverse characters) ;; int2asc: cmp #96 bcs @rts ; if >= 96 then don't change cmp #64 bcs ascbit6 ; carry already clear adc #32 ; bit 6 not set, < 64, so add 32 @rts: rts ascbit6: eor #%01000000 ; if >= 64 and < 96 then toggle bit 6 ;; for int2asc this will clear it, for asc2int it will set it rts ;; ;; Convert a number from ATASCII to internal screen code ;; (this doesn't work for inverse characters) ;; asc2int: cmp #96 bcs @rts ; if >= 96 then don't change cmp #32 bcc ascbit6 ; carry already set sbc #32 ; bit 6 not set, < 64, so add 32 @rts: rts ;; ;; Save Micropainter format colour information ;; SaveMicClrs: ldx #(SAVECHN*16) ldy #(micclrdata-icbdat) jmp SetICBPut ; save colours (grey levels) for image ;; ;; Close IOCB ;; CloseInFile: ldx #(LODCHN*16) .byte $2C CloseOutFile: ldx #(SAVECHN*16) CloseChX: lda #CLOSE jmp SetICBICC ;; ;; Set ICBAL and ICBAH to contents of rendpt and rendpt+1 ;; also set ICCOM to contents of y register ;; SetRend: lda rendpt sta ICBAL,x lda rendpt+1 sta ICBAH,x tya sta ICCOM,x rts ;; ;; Set up IOCB, y register selects data to load into IOCB addresses ;; SetICBOpen: lda #0 sta ICAX2,x lda #OPEN .byte $2C SetICBPut: ; set up IOCB for put buffer lda #PUTBUF SetICBICC: sta ICCOM,X SetICB: lda icbdat,y sta ICBAL,x lda icbdat+1,y sta ICBAH,x SetICBL: lda icbdat+2,y sta ICBLL,x lda icbdat+3,y sta ICBLH,x jmp CIOV ;; ;; Save one block of data from SCRADR ;; SaveBlock: lda #SCRADR ;; ;; Save one block of data ;; A = data address low byte ;; Y = data address high byte ;; SaveBlkAdr: sta putadr3 sty putadr3+1 lda #0 sta putadr3+3 lda drawcount ; multiply number of lines drawn by 40 asl ; (40=number of bytes per line) asl ; 40=8+32, multiply by 8, multiply by asl ; 32, then add together. Max number of sta putadr3+2 ; lines drawn in one go is 24, so we asl ; can multiply by 8 (24*8=192) in 1 byte rol putadr3+3 asl rol putadr3+3 clc adc putadr3+2 sta putadr3+2 bcc @noinhi inc putadr3+3 @noinhi: ldy #(putadr3-icbdat) ldx #(SAVECHN*16) jmp SetICBPut ;; ;; Switch interrupts off and enable OS RAM ;; .if .not .defined(PGM) OSRAMON: sei ; disable interrupts lda #0 sta NMIEN lda PORTB sta portbtmp and #%11111110 ; enable OS RAM sta PORTB rts portbtmp: .byte 0 ;; ;; Switch interrupts on and disable OS RAM ;; OSRAMOFF: lda portbtmp sta PORTB jsr SetNMIEN cli rts .endif ;; ;; Switch VBI's on and DLI's off ;; SetNMIEN: lda #64 sta NMIEN rts ;; ;; Set horizontal positions of Player and Missile 0 to hide jagged ;; edges in HIP modes ;; SetPMGPos: lda #47 ; left edge of screen will always be in sta HPOSM0 ; same position, hide that with missile 0 .if DEBUG sta hpm0 .endif ; which will be same colour as background lda dispwidth ; right edge depends on width of image clc ; so calculate position of player 0 from that adc #48 tax @skdec: stx HPOSP0 .if DEBUG stx hpp0 .endif rts ;; ;; Hide PMGs from view ;; HidePMGs: lda #0 sta HPOSP0 ; set horiz position to 0 sta HPOSM0 sta GRACTL ; disable PMGs sta GPRIOR sta PRIOR lda #34 ; disable PMG DMA jmp SetDMA ;; ;; Display PMGs on screen at correct position ;; ShowPMGs: lda #0 ; set player and missile widths to minimum sta SIZEP0 sta SIZEM lda #PMGBASE sta PMBASE lda #46 jsr SetDMA ; enable player and missile DMA lda #3 sta GRACTL ; enable players and missiles jmp SetPMGPos ; set player and missile positions ;; ;; Copy screen data back down again from OS RAM ;; CopyScr: jsr_osramon .if .defined(PGM) lda #<(SCR2ADR+40) sta filtpt lda #>(SCR2ADR+40) sta filtpt+1 .else lda #SCR2TMP sta rendpt+1 lda #SCR2ADR sta filtpt+1 .endif ;; flicker modes alternate lines (i.e GR.9/10/9/10) to reduce flicker lda #<(SCRADR+40) sta drawtemp lda #>(SCRADR+40) sta drawtemp+1 ldx #100 ; copy 200 lines worth, 2 at a time @hiplp: .if .defined(PGM) ;; swap odd GR.9/Gr.10 lines (1,3,5...) with each other ldy #39 @hipcp2: lda (drawtemp),y pha lda (filtpt),y sta (drawtemp),y pla sta (filtpt),y dey bpl @hipcp2 lda #80 jsr addtofl ; increase filtpt by 80 .else ;; copy even lines (0,2,4...) over to screen 2 ldy #39 @hipcp: lda (rendpt),y sta (filtpt),y dey bpl @hipcp jsr addtofl40 ;; copy odd GR.9 lines (1,3,5...) over to screen 2 ldy #39 @hipcp2: lda (drawtemp),y sta (filtpt),y dey bpl @hipcp2 jsr addtopt40 ; increase rendpt pointer by 40 bytes ;; copy odd GR.10 lines (1,3,5...) over to screen 1 ldy #39 @hipcp3: lda (rendpt),y sta (drawtemp),y dey bpl @hipcp3 jsr addtofl40 jsr addtopt40 ; increase rendpt by 40 bytes .endif lda drawtemp clc adc #80 sta drawtemp bcc @nodhi inc drawtemp+1 @nodhi: dex bne @hiplp jmp_osramoff K: .byte "K:" ;; ;; Add value onto rendpt pointer ;; ;; add 320 to pointer addtopt320: inc rendpt+1 ; 320 = 256+64, increasing hi byte by 1 = 256 lda #<320 ; then add 64 into lo byte .byte $2c ; skip next instruction ;; add 40 onto pointer addtopt40: lda #40 ;; add value in Acc onto pointer addtopt: clc adc rendpt sta rendpt bcc @noinhi inc rendpt+1 @noinhi: rts ;; ;; add 40 onto screen pointer(s) - move to next line ;; addscr40: lda scrpt2 ; move screen pointer onto next line clc adc #40 sta scrpt2 bcc add1scr40 inc scrpt2+1 add1scr40: lda scrpt1 ; move screen pointer onto next line clc adc #40 sta scrpt1 bcc @rts inc scrpt1+1 @rts: rts ;; ;; Add value onto filtpt pointer ;; addtofl40: lda #40 ;; add value in Acc onto pointer addtofl: clc adc filtpt sta filtpt bcc @noinhi inc filtpt+1 @noinhi: rts ;; ;; Set up pointer to 1st screen (scrpt1) ;; ;; returns ;; X = SCRADR low byte ;; Y = SCRADR high byte ;; SetScrPt1: ldx #SCRADR sty scrpt1+1 rts ;; ;; Open screen in appropriate graphics mode ;; graphics mode in y register ;; OpenGr15: lda #15 OpenGr: sta @mode ldy #0 lda #$60 sta DLADR,y iny lda #64 ; LMS clc adc @mode ; add in LMS screen mode sta DLADR,y iny lda #SCRADR sta DLADR,y ldy #202 lda @mode @filgr1: dey sta DLADR+4,y bne @filgr1 ldy #105 lda #64 ; LMS clc adc @mode ; add in LMS screen mode sta DLADR,y iny lda #0 sta DLADR,y ; set up data address for LMS iny lda #(>SCRADR)+16 sta DLADR,y ldy #205 lda #65 ; end the DL sta DLADR,y iny lda #dldata1 sta DLADR,y lda #>SCRADR ; clear screen memory area jsr ClrScr ; clear 8k of ram lda USE2SCR ; check if temp scr area needs cleaning too beq @notmp jsr_osramon ; enable OS RAM lda #>SCR2TMP ; clear temp area too jsr ClrScr jsr_osramoff @notmp: jsr SetDMAOff lda #dldata1 sta SDLSTL+1 lda #34 sta SDMCTL rts @mode: .byte 0 ;; ;; Set up DLI interrups on every other display list line ;; SetDLI: ldx #0 ; start at DL byte 0 ldy #2 ; end at DL byte 2 jsr @setdlisec ldx #4 ; start at DL byte 4 ldy #105 ; end at DL byte 105 jsr @setdlisec ldx #108 ; start at DL byte 108 ldy #205 ; end at DL byte 205 @setdlisec: sty @lastps+1 @setlp: lda DLADR,x ora #128 ; msb indicates DLI enabled sta DLADR,x inx inx @lastps: cpx #0 ; gets changed bcc @setlp rts ;; ;; DLI routine for HIP modes ;; HIPDLI: pha hipgr1: lda #65 ; gets changed during VBI sta WSYNC sta PRIOR ; enable either gr.9 or gr.10 hipgr2: lda #129 ; gets changed during VBI sta WSYNC ; wait for next horizontal blank sta PRIOR ; enable other mode for next line pla rti ; all done ;; ;; DLI routine for GR.15 modes ;; GR15DLI: pha txa pha lda COLOR1 ldx COLOR2 sta WSYNC sta COLPF1 stx COLPF2 lda dlicol1 ldx dlicol2 sta WSYNC ; wait for next horizontal blank sta COLPF1 stx COLPF2 pla tax pla rti ; all done ;; ;; DLI routine for GR.8 modes ;; GR8DLI: pha lda COLOR1 sta WSYNC sta COLPF1 lda dlicol1 sta WSYNC ; wait for next horizontal blank sta COLPF1 pla rti ; all done ;; keep dlicol1 and dlicol2 together dlicol1: .byte 0 ; alternate colour register 1 used in DLI dlicol2: .byte 0 ; alternate colour register 2 used in DLI ;; first 2 lines of display list followed by a jump to page 6 ;; this won't fit in the space available in page 6 so have it here dldata1: .byte $60,$60,$1,$30,$06 ;; colours used for GR.15 formats micclrs: .byte 0, 4, 8, 12 ;; ;; HIP file format is: ;; hiphdr10, ;; 8000 bytes of GR.10 screen, ;; hiphdr9 ;; 8000 bytes of GR.9 screen hiphdr10: .byte $FF, $FF, $10, $60, $4F, $7F hiphdr9: .byte $FF, $FF, $10, $80, $4F, $9F