;; ;; CpegView - JPEG colour image viewer ;; ;; Copyright 2000-2007, Raphael Espino ;; last updated 18-Aug-07 ;; ;; ;; Assemble the decoder first to create jpz1223-8.o then ;; ;; to assemble CpegView: ;; ca65 cpegview.asm ;; ld65 cpegview.o jpz1223-8.o -o cpegview -t atari ;; ---------- viewer zero page addresses, 192 and up are available rendpt = 192 ; 2 byte pointer to image data filtpt = 194 ; 2 byte filter pointer drawtemp = 196 ; 2 temporary storage bytes devpt = 198 ; 2 byte pointer to device name menupt = 200 ; 2 byte pointer to current menu options fnlen = 202 ; 1 byte filename length nxtline = 203 ; 2 byte pointer for filtering + dithering vfiltpt = 205 ; 2 byte vertical filter pointer scrpt1 = 207 scrpt2 = 209 dlitmp = 211 ;; ---------- end of viewer zero page addresses LODCHN = 2 ; IOCB to use for loading file SAVECHN = 3 ; IOCB to use for saving to file KEYBCHN = 4 ; IOCB to use for reading from keyboard MAXLEN = 64 ; max file name length ROWCRS = 84 ; current cursor row COLCRS = 85 ; current cursor column SAVMSC = 88 VDSLST = 512 ; DLI vector VKEYBD = 520 ; keyboard IRQ vector SDMCTL = 559 ; shadow DMA control address SDLSTL = 560 GPRIOR = 623 ; priority register, enable GR.9 TABMAP = 675 ; Tab positions PCOLR0 = 704 ; colour shadow addresses COLOR0 = 708 COLOR1 = 709 COLOR2 = 710 COLOR3 = 711 COLOR4 = 712 CRSINH = 752 ; cursor inhibit flag CH = 764 ; last keypress shadow address ;; IOCB addresses ICCOM = 834 ICSTA = 835 ICBAL = 836 ICBAH = 837 ICBLL = 840 ICBLH = 841 ICAX1 = 842 ICAX2 = 843 masknm = $580 ; directory mask filenm = masknm+MAXLEN ; file name DLADR = $630 ; display list address SCRADR = $A010 ; 1st screen address SCR2ADR = $2010 ; 2nd screen address PMGBASE = $40 ; put PMG's at $4000 TMPCINBUF = $4000 ; temporary buffer for CIN colours, 1 page worth ; can overlap with PMGBASE, CIN doesn't use PMGs TOPLINE = 7 ; vertical position of graphics mode menu BUFWID = 20 ; each buffer of data will be 20 columns wide BUFHEI = 1 ; each buffer of data will be 1 row high MAXBUFS = 2 ; maximum of 2 buffers in height ;; size of dither buffer is the maximum width in pixels of the data ;; received from the decoder, plus some overscan DITHBUFLEN = (BUFWID * 8 + 5 ) ; length of dither buffer ;; we need to keep track of the rightmost column of pixels when ;; we have multiple horizontal buffers arriving from the decoder ;; so that the error value from the previous buffer can be carried ;; over into the next buffer. This will be the maximum height ;; in pixels of a decoder buffer times the number of components NXTBUFLEN = BUFHEI * 8 * MAXBUFS .define jsr_osramon jsr OSRAMON ; JPEG viewer uses RAM under OS .define jsr_osramoff jsr OSRAMOFF .define jmp_osramoff jmp OSRAMOFF ;; set up viewer jmp vectors .addr segvector .addr segvectorend-1 .org $0620 segvector: ;; next 12 bytes should be JMP's to viewer's init, start, draw ;; and end code JMP RafRendInit ; init viewer JMP RafRendStart ; image data about to arrive JMP RafRendDraw ; 8 lines of image data available JMP RafRendEnd ; image completed JMP UnusedVec ; unused for now, point at an RTS segvectorend: .addr filemask .addr filemaskend-1 .org masknm filemask: .byte "D1:*.JPG",155 ; default file mask for JPEGs filemaskend: ;; header for viewer .addr segcode .addr segcodeend-1 ;; viewer has area from $7200 upwards for itself and screen .org $7200 ;; ;; Init code, this will be called when decoder starts or ;; when it is re-run. Viewer should display start up information, ;; open an IOCB for the decoder to read the JPEG data from, ;; and store the (IOCB number * 16) in IocbNum ;; segcode: RafRendInit: jsr SetNMIEN ; make sure DLIs are disabled @diragn: jsr ClrDirScr ldx #1 stx ctrrow stx ctrcol lda #0 inx ; x is now 2 stx CRSINH jsr SetRowCol ;; display viewer information jsr strout .byte "CpegView 0.5" .byte "(18Aug07) Raphael Espino",0 ldx #2 lda #3 jsr SetRowCol lda repeat ; if we are redisplaying bne @readfl ; then don't display directory jsr DispMask jsr DrawScr ;; make sure IOCB is available jsr CloseInFile lda #READDIR ; do a directory ldx #(LODCHN*16) ;; get ready to open the file stx IocbNum ; tell decoder what IOCB to use ldy #(bufio-icbdat) jsr OpenFile bpl @notdend lda #1 ; couldn't open directory, so flag end of dir .byte $2C @notdend: lda #0 ; directory end not found yet sta dirend jsr RestMask jsr DispDir jsr CloseInFile lsr redodir bcs @diragn @readfl: lda #0 ;ldx repeat ; should user be asked for graphics mode? ;bmi @skrstrpt ; no, don't reset repeat value then ;sta repeat ;@skrstrpt: sta CRSINH lda #READ ; open the file ldx #(LODCHN*16) ldy #(filenmio-icbdat) jsr OpenFile bmi @error jsr strout .byte 125, "Loading ",0 jmp DispFN ; display filename and return to decoder ;; an error occured, display error code and restart @error: lda #0 sta repeat ldx #(LODCHN*16) lda ICSTA,X ; read status value pha ; remember error code jsr CloseInFile ; close file jsr NLStrOut .byte "Error ",0 pla tax jsr DecOut ; display error code ; display file name too jsr strout .byte " - ",0 jsr DispFN ; display file name jmp WaitandRun ;; ;; Open an IOCB ;; Acc = 4 -> open file for read ;; Acc = 6 -> open for directory read ;; Acc = 8 -> open file for write ;; OpenFile: sta ICAX1,x jmp SetICBOpen devlen: .byte 2 ; length of device + directory name in filename ;; ;; IOCB ICBAL/H ICBLL/H set up data, used by the SetICB routines ;; address, length ;; icbdat: rdbuf2io: .word RDBUF+2, MAXLEN-6 kio: .word K eio: .word E bufio: .word masknm ; 2 bytes, keep together .word 0 ; 2 bytes, keep together fortyio: .word 40 filenmio: .word filenm, MAXLEN dispwidth: .word 320*8 putadr3: .word 0, 0 masknmio: .word masknm masklen: .word 8 rdbufio: .word RDBUF, MAXLEN riphdrio: .word riphdr, riphdrend-riphdr tiphdrio: .word tiphdr, tiphdrend-tiphdr micclrdata: .word micclrs, 6 SaveLine40FiltPt: ldx filtpt ldy filtpt+1 jmp SaveLine40Bytes SaveLine40RendPt: ldx rendpt ldy rendpt+1 ; drops through ; ; Save a line of image data ; X = lo byte of line address ; Y = hi byte of line address ; ; Returns ; Negative bit set on error ; Negative bit clear on success ; SaveLine40Bytes: lda #40 sta putadr3+2 lda #0 sta putadr3+3 stx putadr3 sty putadr3+1 ldy #(putadr3-icbdat) ; save 1 line of data at a time ldx #(SAVECHN*16) ; IOCB should already be open jmp SetICBPut ;; ;; Display the disk directory using an open IOCB ;; FNCHAR1 = 4 ; position of first character in filename MAXDIRLN = 18 ; max number of directory lines to display on 1 screen DispDir: jsr OpenKeyb lda #0 sta dircount ; no filenames read from directory yet lda #<(SCRADR+2) sta rendpt lda #>(SCRADR+2) sta rendpt+1 @dirlp: ldx #(LODCHN*16) @notlast: ldy #GETLNE ; first read directory into buffer jsr SetRend ldy #(fortyio-icbdat) jsr SetICBL php ldy ICBLL,x iny lda #155 ; make sure there is a return char at end sta (rendpt),y ; of each filename jsr addtopt40 ; move onto next filename slot inc dircount ; and increase file count by one plp bpl @notlast dec dircount lda #SCRADR sta rendpt+1 @doagain: lda rendpt ; save start of screen full of data so sta filtpt ; we can find it again later lda rendpt+1 sta filtpt+1 lda dircount sta svdircount lda #'A' ; menu entries start at 'A' sta lastdirc @nxtline: dec dircount bmi @dirend ; exit when end of list is reached bne @dsline inc dirend ; this is the last line, (FREE SECTORS line) @dsline: jsr DispLine ; display the line on screen inc lastdirc ; increase menu selection letter lda lastdirc cmp #'A'+MAXDIRLN ; menu letters run from A-T bcc @nxtline @dirend: ;; display instructions on right hand side of screen lda #4 ; row for top line of instructions sta @rowtmp jsr @updtpos jsr strout .byte '1'+128, '-', '9'+128," Dir",0 jsr @updtpos jsr strout .byte 'E'+128, 's'+128, 'c'+128, " Quit",0 jsr @updtpos jsr strout .byte 'T'+128, 'a'+128, 'b'+128, " Mask",0 jsr @updtpos lda hasextraram beq @ckdirend ; no extra ram, can only use RAM under OS lda extraram ; 0 = RAM under OS, 2 = extended RAM bank bne @extended jsr strout .byte 'X'+128, " OS RAM ",0 jmp @ckdirend @extended: jsr strout .byte 'X'+128, " Extended RAM",0 @ckdirend: lda dirend ; is this the end of the directory? bne @nonext ; if so, then don't display 'Next' option jsr @updtpos jsr strout .byte 'S'+128, 'p'+128, 'a'+128, 'c'+128, 'e'+128, " Next",0 @nonext: ldx filtpt+1 ; are we on 1st screen? cpx #>SCRADR beq @noprev ; this is 1st screen, don't show previous jsr @updtpos jsr strout .byte '-'+128, " Prev",0 @noprev: lda devlen cmp #3 ; we're not in a subdirectory, so can't move bcc @nodir ; up one level jsr @updtpos jsr strout .byte '<'+128, " Up Dir",0 @nodir: @getagain: jsr GetKpNoRet ; get keypress without waiting for return cmp #'<' bne @notudir ldx devlen cpx #3 ; we're not in a subdirectory, so can't move bcc @getagain ; up one level txa tay dex jsr FindDevLenX inx iny inc redodir ; redisplay directory jmp @cpdrct ; copy mask to end of dir name @notudir: cmp #'-' ; move to previous screen? bne @notprv ldx filtpt+1 ; are we on 1st screen? cpx #>SCRADR beq @getagain ; this is 1st screen, ignore keypress lda filtpt sec sbc #<(MAXDIRLN*40) ; update current top of screen position sta rendpt ; this should be on the previous screenfull now txa sbc #>(MAXDIRLN*40) sta rendpt+1 clc lda svdircount adc #MAXDIRLN sta dircount ; update current position in filename list lsr dirend ; reset dirend to 0 beq @nextscr ; display next screen @notprv: cmp #32 ; is this a space? bne @ntspc lda dirend ; is this the end of the directory? bne @getagain @nextscr: jsr ClrDirScr ; clear screen ready for next screenfull jsr DrawVLine ; redraw vertical line jmp @doagain ; display next screenfull @ntspc: cmp #27 ; is this the Esc key? bne @skjmpx jmp DOS ; yes, so exit to DOS @skjmpx: cmp #127 ; check for TAB key bne @nottab ldx masklen inx inx lda #3 jsr SetRowCol lda #0 sta CRSINH jsr strout .byte 30,31,0 jsr GetLine jsr ReadMask sec rol CRSINH jmp @redodrct @nottab: cmp #'X' bne @notx ;; extended memory options lda hasextraram ; don't allow toggling of extended memory option if beq @jmpdirend ; no OS RAM available lda extraram eor #2 sta extraram @jmpdirend: jmp @dirend ; forced branch @notx: cmp #'1' ; check for numbers 1-9 bcc @jmpagain cmp #'9'+1 bcs @chklet ;; user pressed a number 1-9, redo directory for that drive sta masknm+1 @redodrct: inc redodir jmp @exit @chklet: cmp #'A' ; did user press a menu key? bcc @jmpagain ; unknown key, go get another one cmp lastdirc ; key is above last valid keypress bcc @letok @jmpagain: jmp @getagain ; go get another one @letok: sec ; find position of user's selection sbc #'A' ; in the directory list tax lda filtpt ; point us back at start of this screen sta rendpt lda filtpt+1 sta rendpt+1 @findlp: dex ; now find line user selected bmi @found jsr addtopt40 ; add 40 onto rendpt jmp @findlp @found: ; we found the line the user selected jsr RestMask ldx devlen ; and copy filename/dirname after inx ; last colon ldy #FNCHAR1-1 lda #':' cmp (rendpt),y bne @nodrct ; user didn't select a directory inc redodir ; this is a directory, need to redisplay it @nodrct: iny @cplp: lda (rendpt),y ; copy file from storage area into last cmp #32 ; filename, skip any spaces beq @skspc sta RDBUF,x inx @skspc: iny cpy #8+FNCHAR1 ; and don't do any more than 8 characters bcc @cplp lda (rendpt),y ; if there is a space here, then there isn't cmp #'D'+128 ; an extension bne @ntdir lda #'>' ldy devlen sta masknm,y inc redodir bne @cpdrdv @ntdir: lda redodir ; don't add '.' for directory name bne @cplp2 lda #'.' ; otherwise add '.' char for extender sta RDBUF,x inx @cplp2: lda (rendpt),y ; now copy extender into filename cmp #32 ; if we find a space then finished beq @noext ; with extender sta RDBUF,x inx iny cpy #13+FNCHAR1 ; no more than 8+1+3 chars for filename bcc @cplp2 @noext: lda redodir beq @norddir @cpdrdv: ldy devlen stx devlen @cpdrct: lda masknm,y sta RDBUF,x iny inx cmp #155 bne @cpdrct dex stx masklen jsr SaveMask jmp @exit @norddir: lda #155 ; end last filename with RETURN char sta RDBUF,x dex stx fnlen jsr SaveFN @exit: jmp CloseKeyb ;; update cursor pos for next instruction line @updtpos: ldx #25 lda @rowtmp clc adc #2 sta @rowtmp jmp SetRowCol @rowtmp: .byte 0 ; current instruction line lastdirc: .byte 0 ; last character used in directory menu dirend: .byte 0 ; has end of directory been reached? dircount: .byte 0 ; number of entries in directory list svdircount: .byte 0 ; save of entries on current screen redodir: .byte 0 ; do directory again ;; ;; Open keyboard for input ;; OpenKeyb: ldx #(KEYBCHN*16) ; open keyboard so we can read single keypress jsr CloseChX LDA #4 STA ICAX1,x ldy #(kio-icbdat) ; open K: jmp SetICBOpen ; this will set ICBLL/H as well, but they are ; ignored anyway ;; ;; Close keyboard again ;; CloseKeyb: ldx #(KEYBCHN*16) ; close keyboard and exit jmp CloseChX ;; ;; Get keypress without waiting for return key ;; ;; Returns ;; Acc = ATASCII key value, with high bit clear (inverse key changed ;; to normal) ;; GetKpNoRet: ldx #(KEYBCHN*16) lda #GETBUF ldy #(bufio-icbdat) ; set ICBLL/H to 0, don't care about ICBAL/H jsr SetICBICC ; get keypress cmp #155 beq @noinv ; don't clear top bit for return key and #%01111111 ; ignore inverse video @noinv: rts ;; ;; Display character on directory menu ;; DispLine: lda dirend ; is this last entry in directory? bne @lstlne lda lastdirc ; if not last entry then put letter ldx #')' ; and ')' on screen bne @notlst @lstlne: dec lastdirc ; don't display letter next to last lda #' ' ; directory entry (# of free sectors) tax @notlst: ldy #0 sta (rendpt),y iny txa sta (rendpt),y ldx #0 ldy #PUTLNE jsr SetRend ldy #(filenmio-icbdat) jsr SetICBL jmp addtopt40 ;; ;; Draw lines on screen ;; DrawScr: lda #82 ; control-r in internal code ldy #119 @horizlp: sta (SAVMSC),y dey cpy #80 bcs @horizlp ldy #199 @horizlp2: sta (SAVMSC),y dey cpy #160 bcs @horizlp2 ldy #183 lda #87 ; control-w in internal sta (SAVMSC),y DrawVLine: clc lda SAVMSC adc #205 sta drawtemp lda SAVMSC+1 adc #0 sta drawtemp+1 ldy #MAXDIRLN ; 18 lines worth @vertlp: lda #124 ; vertical bar char in internal code sta (drawtemp),y clc lda drawtemp adc #41 sta drawtemp bcc @noinhi inc drawtemp+1 @noinhi: dey bpl @vertlp ldx #2 lda #5 ; drops through to SetRowCol SetRowCol: stx COLCRS sta ROWCRS rts ;; ;; Adjust row and col offset input if using filters ;; AdjRowCol: ldx USEVFILT ldy RENDMODE cpy #'4' ; is this TIP mode? bne @nottip dex ; screen is only 1/2 as high in TIP mode ; so reduction needs to be divided by 2 as well @nottip: cpx #0 bmi @rts beq @x2 asl ; multiply by 4 @x2: asl ; multiply by 2 @rts: rts ;; ;; Subtract row and col offset from number of rows and columns to ;; display, results stored in tmpnumrows and tmpnumcols ;; SubRowCol: ldx #1 @loop: lda numcols,x ldy ctrcol,x ; if image is being centred, offset will bne @ok ; be calculated from image size sec sbc FirstCol,x bcs @ok ; check for negative result lda #0 ; make sure it's never less than 0 @ok: sta tmpnumcols,x dex bpl @loop rts ;; ;; Clear directory display area on screen ;; ClrDirScr: lda SAVMSC clc adc #200 sta drawtemp lda SAVMSC+1 adc #3 sta drawtemp+1 ldx #4 lda #0 ldy #71 @clrlp: dey sta (drawtemp),y bne @clrlp dec drawtemp+1 dex bne @clrlp rts ;; ;; Read new mask value from screen ;; MASKPOS = 122 ; starting position of mask on screen ReadMask: ldy #MASKPOS @findcl: lda (SAVMSC),y ; search for first ':' or '>' character and #%11111011 cmp #26 ; ':' and ('>' AND 11111011) in internal beq @fndcln iny cpy #MASKPOS+38 bcc @findcl bcs @cpmskpos ; no colon found, copy whole name after dev @fndcln: cpy #MASKPOS+1 ; is colon 2nd character? bmi @ispos0 ; is colon 1st character? bne @notpos1 dey lda (SAVMSC),y ; yes, so copy first letter as dev name jsr int2asc sta masknm ; leave dev number alone and copy rest lda #'1' ; set device number to 1 sta masknm+1 iny ; of mask after colon @ispos0: iny bne @cpdirdv @notpos1: cpy #MASKPOS+2 ; is colon 3rd character? bne @notpos2 ldx #0 ldy #MASKPOS ; yes, so just copy the whole thing across bne @cpdirn @notpos2: @cpmskpos: ldy #MASKPOS ; if colon is at position 4 or greater then @cpdirdv: ldx #3 ; copy the whole thing after device @cpdirn: lda (SAVMSC),y beq @spc ; found a space, end of filename jsr int2asc sta masknm,x iny inx bne @cpdirn @spc: lda #155 ; make sure we've got a return char at end sta masknm,x stx masklen ;; ;; Update position of last device+directory name, ':' or '>' used ;; to separate directory names ;; FindDevLen: ldx masklen FindDevLenX: @schcln: lda masknm,x ; ':' is 58, '>' is 62 and #%11111011 ; converts 62 to 58 cmp #':' beq @fndcln dex bpl @schcln @fndcln: stx devlen rts ;; ;; Save filename for later ;; SaveFN: ldx #MAXLEN dex @cpflnam: lda RDBUF,x ; copy filename from buffer to storage area sta filenm,x dex bpl @cpflnam rts ;; ;; Restore previously saved file name ;; RestFN: ldx #MAXLEN dex @cpflnam: lda filenm,x ; copy filename from buffer to storage area sta RDBUF,x dex bpl @cpflnam rts repeat: .byte 0 ;; ;; Display file name ;; DispFN: ldy #(filenmio-icbdat) lda #PUTLNE ; write out file name setx0: ldx #0 jmp SetICBICC ;; ;; Save directory mask for later ;; SaveMask: ldx #MAXLEN dex @cpmsnam: lda RDBUF,x ; copy filename from buffer to storage area sta masknm,x dex bpl @cpmsnam rts ;; ;; Restore previously saved mask ;; RestMask: ldx #MAXLEN dex @cpflnam: lda masknm,x ; copy filename from buffer to storage area sta RDBUF,x dex bpl @cpflnam rts ;; ;; Display mask value ;; DispMask: ldy #159 lda #0 @clrlne: ; clear out line first sta (SAVMSC),y dey cpy #120 bcs @clrlne ldy #(masknmio-icbdat) lda #PUTBUF bne setx0 ; forced branch ;; ;; Get address of render routine for selected mode ;; ;; Params ;; Acc = keypress to search for ;; ;; Returns ;; Y = index into options ;; Acc = keypress searched for (same as input) GetAdr: ldx #menuopts stx menupt+1 ldy #4 ; 5 options on the menu @optloop: cmp (menupt),y ; check if key is on menu beq @optfnd dey bpl @optloop clc rts @optfnd: tax tya asl tay txa iny sec rts ;; recognised options for main menu menuopts: .byte "1", "2", "3", "4", "5" ; number of columns mode is capable of without any reduction modecols: .byte 10, 10, 10, 10, 10 ; number of rows mode is capable of without any reduction moderows: .byte 25, 25, 25, 12, 25 ;; RIP file format header information riphdr: .byte "RIP1.0 ", $20, 0, 0, >(riphdrend-riphdr), <(riphdrend-riphdr), 0 riphdrwid: .byte 80, 0 riphdrhei: .byte 200, $20, ripauthnoteend-ripauthnote, "T:" ripauthnote: .byte "CPEGVIEW 0.5" ripauthnoteend: .byte 9, "CM:" ; there should always be 9 data items here, the last value ; (RipColours+8) is read in VBI RIPLUM = 2; RipColours: .byte 0, 2*16+RIPLUM, 3*16+RIPLUM, 5*16+RIPLUM, 7*16+RIPLUM, 9*16+RIPLUM .byte 10*16+RIPLUM, 12*16+RIPLUM, 14*16+RIPLUM riphdrend: tiphdr: .byte "TIP", 1, 0 tiphdrwid: .byte 160 tiphdrhei: .byte 100 tiphdrframesize: .word 4000 tiphdrend: ;; ;; Show GR.0 screen ;; RestoreScr: jsr SwapScr lda #34 ; re-enable DMA jmp SetDMA ;; ;; End of data. Gets called when image has finished. Viewer ;; should close files, restore system back to original state, ;; wait for user to press a key and then return. After returning ;; decoder will exit back to environment (DOS). To restart decoder ;; instead do JMP (RERUN) instead of RTS. If decoder failed to ;; decode image then this will be called with Error > 0 ;; RafRendEnd: jsr Reset ; reset IRQ and DMA jsr CloseInFile ; close input file ldx #255 ; clear last key press stx CH inx stx COLOR4 ; set background colour to black stx tmpscron lda Error ; check for error or user abort bne waitmsg lda RENDMODE cmp #'2' bne @notcx lda USE2SCR ; don't alternate graphics mode if this is a beq @notcx ; greyscale image ;; C15 mode needs to alternate graphics modes, between mode 'E' and 'F' ;; down the DL so toggle the lsb for every other graphics mode line ;; in the DL ;; do top half of DL first ldx #100 @modelp: lda DLADR+4,x eor #1 sta DLADR+4,x dex dex bpl @modelp ;; then do the bottom half of the DL ldx #96 @modelp2: lda DLADR+108,x eor #1 sta DLADR+108,x dex dex bpl @modelp2 @notcx: jmp notsave waitmsg: jsr PrintNL lda Error beq WRunMenu ; save completed cmp #ABORTCD ; user aborted, rerun beq beqrerun WaitandRun: ldx #0 ; don't show Redisplay option .byte $2c WRunMenu: ldx #1 ; show Redisplay option ;; drops down into WAITKEYP ;; ;; wait for key press ;; ;; X = 0, don't display "Try again" or "Change image options" ;; X = positive, don't display "Try Again" WAITKEYP: stx tmpwaitkeyp txa beq @noredis bpl @nocont jsr NLStrOut .byte 'A'+128, "-Try again",0 jsr NLStrOut .byte 'S'+128, 'p'+128, 'a'+128, 'c'+128, 'e' + 128 .byte "-Back to image",0 @nocont: jsr NLStrOut .byte 'C'+128,"-Change image options",0 @noredis: jsr NLStrOut .byte 'E'+128,'s'+128,'c'+128, "-Run again",155 .byte 'X'+128, "-Exit to DOS",155,0 nomenu: jsr GetKey cmp #28 ; Esc key beqrerun: beq runagain cmp #22 ; X key bne @notdos jsr CloseInFile jmp DOS @notdos: ldx tmpwaitkeyp beq nomenu ; no A or C options on menu bpl @nottryagain ; no A option on menu cmp #33 ; 'Space' key - go back to image beq @backtoimage cmp #63 ; 'A' key - try again bne @nottryagain @backtoimage: rts @nottryagain: cmp #18 ; 'C' key - change image options bne nomenu inc repeat ; redisplay current image runagain: jsr CloseInFile jmp (RERUN) ; rerun decoder DOS: jsr strout .byte 125,0 ldx STACKPT txs ldy #0 sty CRSINH rts notsave: jsr VBION ; set up immediate VBI to switch screens jsr SetNMIEN ; make sure DLIs are disabled sta tmpnmien lda USE2SCR beq waitkp ; if we copied data to OS RAM lda RENDMODE cmp #'4' ; TIP uses a different routine to copy data to screen bcc @nottipcopy beq @istip ; this is RIP mode jsr CopyScr ; set up RIP colours in colour registers lda #0 sta COLOR4 ; correct COLOR4 value will be copied here during ; DLI, but we need to set it to 0 to stop the blank ; lines at the top of the screen displaying a colour ldx #7 @stripc: ; set colour registers up for RIP lda RipColours,x sta PCOLR0,x dex bpl @stripc bmi @showpmg ; forced branch @istip: jsr CopyTipScr @showpmg: ; only enable PMGs in TIP and RIP modes jsr ShowPMGs jmp @skipcopyscr @nottipcopy: jsr CopyScr ; then copy it back down again @skipcopyscr: lda RENDMODE cmp #'1' ; is this APAC? beq @apactipdli cmp #'4' ; is this TIP? bne @c815ripdli ; is this C8, C15 or RIP? ; drop through for TIP @apactipdli: lda #APACTIPDLI bne @stupdl ; forced branch @c815ripdli: lda #C815RIPDLI @stupdl: sta VDSLST stx VDSLST+1 jsr SetDLI lda #192 sta NMIEN ; enable DLIs sta tmpnmien ;; get key press waitkp: jsr GetKey ldy #0 cmp #6 ; + (left) key beq @mvleft cmp #14 ; - (up) key beq @mvup cmp #7 beq @mvright ; (right) key cmp #15 ; (down) key beq @mvdown cmp #28 ; ESC key - rerun decoder beq @beqexit cmp #33 ; SPACE BAR - show help beq @beqinfsc cmp #17 ; HELP key - display image info/help beq @beqinfsc cmp #39 ; Inverse vid/Atari key - disp image info/help beq @beqinfsc cmp #38 beq @beqinfsc ; '/' key (shifted is '?' key) image info/help cmp #22 ; 'X' key - exit to DOS @beqexit: beq @exit cmp #18 ; 'C' change options for this image beq @repeat cmp #40 ; 'R' key repeats same image again beq @repeat cmp #62 ; 'S' key saves current image beq @dosave cmp #57 ; 'H' - display image info/help bne waitkp ; forced branch @beqinfsc: jmp InfoScr ; Y should be 0 here, X contains full internal key press code with shift ; and control bits set @mvup: ; scroll up iny ; set y to 1, modify first row value @mvleft: ; scroll left jsr @SetTmpCols ; decide how much to scroll by lda FirstCol,y beq waitkp ; if already at 1st column, can't move further left sec sbc @tmpcols,y bcs @notminc lda #0 ; go to fist column @notminc: sta FirstCol,y dec repeat ; set repeat to -ve value bne @exit ; forced branch ; Y should be 0 here, X contains full internal key press code with shift ; and control bits set @mvdown: ; scroll down iny ; set y to 1, modify first row value @mvright: ; scroll right jsr @SetTmpCols ; decide how much to scroll by lda LastCol,y cmp numcols,y bcs waitkp ; already at maximum column tax ; clc ; carry already clear here adc @tmpcols,y bcs @maxc cmp numcols,y bcc @notmaxc @maxc: lda numcols,y ; reached maximum column, use max first col value sec sbc decdrcols,y tax @notmaxc: txa sta FirstCol,y dec repeat ; set repeat to -ve value bne @exit ; forced branch ; ; Decide how much to scroll the screen by. If Shift key is pressed then ; scroll by a whole screen's worth, otherwise scroll half a screen's worth ; @SetTmpCols: lda decdrcols sta @tmpcols lda decdrcols+1 sta @tmpcols+1 txa and #%01000000 ; is shift key pressed? bne @shifted lsr @tmpcols ; shift key is not pressed, scroll by 1/2 screen lsr @tmpcols+1 @shifted: rts @tmpcols: .byte 0,0 ;@incclr: ; lda COLOR4 ; and #%00001111 ; cmp #%00001111 ; beq waitkp ; already reached maximum brightness ; inc COLOR4 ; bne waitkp ; forced branch ;@decclr: ; lda COLOR4 ; and #%00001111 ; beq waitkp ; already reached minimum brightness ; dec COLOR4 ; jmp waitkp @repeat: inc repeat ; redisplay current image @exit: pha jsr VBDLIOFF jsr HidePMGs pla cmp #22 ; if this was 'X' key, then go do to DOS beq @rts jmp (RERUN) @rts: jmp DOS @dosave: jsr CanSave ; can image be saved in this mode? bne SaveImg jmp waitkp SaveImg: ; save image here jsr Scr0on ; enable GR.0 screen RedoSave: lda #125 ; clear the screen jsr PRINT1BYTE jsr RestFN jsr PrintNL jsr DispFN ; display filename jsr NLStrOut .byte "Save as ",0 ; tell user what mode we're saving in lda RENDMODE cmp #'2' beq @cin ; saving in CIN format cmp #'4' beq @tip ; saving in TIP format jsr DispRIP ; must be RIP lda #8 bne @dpress @cin: jsr DispCIN lda #0 beq @dpress ; forced branch @tip: jsr DispTIP lda #4 @dpress: ; remember save mode so we can get the sta tmpsvmode ; correct filename extension later lda #5 ; make sure prompt is always on the same line sta ROWCRS ; otherwise TAB completion won't work correctly jsr NLStrOut .byte "Press ",'T'+128,'a'+128,'b'+128," for default filename" .byte 155,"File:D:",0 ;; remove existing extension if there is one ;; and/or add the new one lda #'.' ldx fnlen ; filename len, including device name ldy #4 ; check last 4 characters for extension @extlp: cmp RDBUF,x beq @addext ; found '.' replace old extension dex beq @noext ; end of name, add extension dey bne @extlp ; if no '.' in last 4 chars, then no extension @noext: ldx fnlen inx @addext: ldy tmpsvmode lda #3 sta tmpsavval @extlp2: lda @extensions,y ; copy correct extension to end of filename sta RDBUF,x iny inx dec tmpsavval bpl @extlp2 lda #155 sta RDBUF,x lda SAVMSC clc adc #SVFNOFF sta rendpt+1 lda #SVFNOFF/40 ; tell TabIRQ which line prompt is on sta drawtemp ldx #TabIRQ ; tab key, then display old filename jsr SetKeyIRQ jsr GetLine ; get filename from user jsr GetFileNM jsr ClrKeyIRQ lda RDBUF+2 ; check if a filename was entered cmp #':' ; if 3rd char is ':' then check 4th char bne @ckret ; this handles 'D1:' case lda RDBUF+3 @ckret: cmp #155 ; if it is a return char, then no filename bne @cont sec jmp ShowImg @cont: ;; make sure IOCB is available jsr CloseOutFile bpl @ok @jmsverr: jmp SaveError @ok: ;; now open the file lda #WRITE ldy #(rdbufio-icbdat) jsr OpenFile bmi @jmsverr jsr SetDMAOff jsr SwapScr lda tmpsvmode cmp #4 bne @notsavtip ; TIP is 4 jmp @savtip @notsavtip: bcs @savrip ; CIN is 0, RIP is 8 jmp @savcin @savrip: ; save in RIP format ; save 1st RIP screen (GR.10 - colour) ; need to set up image height here ldy #(riphdrio-icbdat) ; save RIP jsr SaveHeader bmi @bmiCloseAndErr lda #SCR2ADR sta rendpt+1 lda #<(SCRADR+40) sta filtpt lda #>(SCRADR+40) sta filtpt+1 lda riphdrhei sta @savelines @savnxtripline: jsr SaveLine40RendPt bmi @bmiCloseAndErr dec @savelines ; stop at end of image beq @savscr2 jsr SaveLine40FiltPt bmi @bmiCloseAndErr jsr MoveToNextLine dec @savelines ; stop at end of image bne @savnxtripline @savscr2: ; save 2nd RIP screen (GR.9 - greyscale) lda #SCRADR sta rendpt+1 lda #<(SCR2ADR+40) sta filtpt lda #>(SCR2ADR+40) sta filtpt+1 lda riphdrhei sta @savelines @savnxtripline2: jsr SaveLine40RendPt bmi @bmiCloseAndErr dec @savelines ; stop at end of image beq @beqexit jsr SaveLine40FiltPt @bmiCloseAndErr: bmi @bmiCloseAndErr jsr MoveToNextLine dec @savelines ; stop at end of image bne @savnxtripline2 jmp @exit @savtip: ; save in TIP format ; need to set up image height here ldy #(tiphdrio-icbdat) ; save TIP jsr SaveHeader bmi @bmiCloseAndErr ; save gr.9 screen lda #<(SCR2ADR+40) sta rendpt lda #>(SCR2ADR+40) sta rendpt+1 lda tiphdrhei sta @savelines @savnxttipline: jsr SaveLine40RendPt bmi @bmiCloseAndErr jsr addtopt80 dec @savelines ; stop at end of image bne @savnxttipline ; save gr.10 screen lda #<(SCRADR+40) sta rendpt lda #>(SCRADR+40) sta rendpt+1 lda tiphdrhei sta @savelines @savnxttipline2: jsr SaveLine40RendPt bmi @bmiCloseAndErr jsr addtopt80 dec @savelines ; stop at end of image bne @savnxttipline2 ; save gr.11 screen lda #SCRADR sta rendpt+1 lda tiphdrhei sta @savelines @savnxttipline3: jsr SaveLine40RendPt bmi @bmiCloseAndErr jsr addtopt80 dec @savelines ; stop at end of image bne @savnxttipline3 @beqexit: beq @exit ; forced branch @savcin: ; save in CIN format ; save gr.15 screen lda #SCRADR sta rendpt+1 lda #<(SCR2ADR+40) sta filtpt lda #>(SCR2ADR+40) sta filtpt+1 lda #192 sta @savelines @savnxtcinline: jsr SaveLine40RendPt bmi CloseAndErr dec @savelines ; stop at end of image beq @exit jsr SaveLine40FiltPt bmi CloseAndErr jsr MoveToNextLine dec @savelines ; stop at end of image bne @savnxtcinline ; save gr.11 screen lda #SCR2ADR sta rendpt+1 lda #<(SCRADR+40) sta filtpt lda #>(SCRADR+40) sta filtpt+1 lda #192 sta @savelines @savnxtcinline2: jsr SaveLine40RendPt bmi CloseAndErr dec @savelines ; stop at end of image beq @exit jsr SaveLine40FiltPt bmi CloseAndErr jsr MoveToNextLine dec @savelines ; stop at end of image bne @savnxtcinline2 ; save CIN colour information to file ldx #0 @savenxtclr: stx @savecinidx lda micclrs,x jsr SaveCinColourPage bmi CloseAndErr ldx @savecinidx inx cpx #4 bcc @savenxtclr @exit: jsr CloseOutFile ; finished save jsr RestoreScr jmp ShowImg @savelines: .byte 0 ; number of lines to save @savecinidx: .byte 0 ; index of the current CIN mode colour being saved ;; default extensions for save file formats @extensions: @cinext: .byte ".CIN" @tipext: .byte ".TIP" @ripext: .byte ".RIP" SaveHeader: ldx #(SAVECHN*16) jmp SetICBPut CloseAndErr: ; display error jsr RestoreScr jmp SaveError ; ; Move current save line pointers to the next line ; MoveToNextLine: jsr addtopt80 ; move rendpt to next line lda #80 jmp addtofl ; move filtpt to next line ; ; Set up a page worth of CIN mode colour information and save to file ; A = CIN colour value ; ; Returns ; Negative bit set on error, clear on success ; SaveCinColourPage: ldx #0 @setcinclr: sta TMPCINBUF,x dex bne @setcinclr ; x is 0 here stx putadr3+2 ; save 1 page worth of data inx ; x is now 1 stx putadr3+3 lda #TMPCINBUF sta putadr3+1 ldy #(putadr3-icbdat) ; save 1 line of data at a time ldx #(SAVECHN*16) ; IOCB should already be open jmp SetICBPut ;; ;; Display image information screen ;; InfoScr: jsr Scr0on jsr strout .byte "Image: ",0 ; display all info for this image jsr RestFN jsr DispFN jsr NLStrOut .byte "Mode: ",0 lda RENDMODE ; display the name of the render mode cmp #'2' beq @dispc15 ; mode 2 = C15 bcc @dispapac ; < mode 2 is APAC cmp #'4' bcc @dispc8 ; if > 2 and < 4, then it must be 3 = C8 beq @disptip ; mode 4 = TIP ; it must be RIP32 then jsr DispRIP jmp @disptype @disptip: jsr DispTIP jmp @disptype @dispc8: jsr strout .byte "C8",0 ; it must be C8 jmp @disptype @dispc15: jsr DispCIN jmp @disptype @dispapac: jsr strout .byte "APAC",0 @disptype: jsr NLStrOut .byte "Type:",0 lda USE2SCR ; is this a colour image? bne @dispcolr ; if so, display "colour" jsr strout ; otherwise display "greyscale" .byte " Greyscale",0 jmp @dispsize @dispcolr: jsr strout .byte " Colour",0 @dispsize: jsr NLStrOut .byte "Size:",0 jsr DispWidHei jsr NLStrOut .byte "Offset: ",0 jsr DispRowCol ; ********************************************* ; for debugging purposes ; jsr NLStrOut ; .byte "H Reduction: ",0 ; lda USEFILT ; jsr DispFilt ; jsr NLStrOut ; .byte "V Reduction: ",0 ; lda USEVFILT ; jsr DispFilt ; ********************************************* lda SKIPERR ; tell user about any decoding errors beq ShowHelp ; that were skipped while decoding jsr NLStrOut .byte 155, "*** CORRUPTED FILE ***",0 ShowHelp: ; display help information too jsr NLStrOut .byte 155,155,"Help:" .byte 155,'C'+128, "-Change options", 155 .byte 'E'+128, 's'+128, 'c'+128, "-Run again", 155 .byte 'S'+128, 'p'+128, 'a'+128, 'c'+128, 'e' + 128 .byte "-Back to image", 155 .byte 27,28+128, 27,29+128, 27,30+128, 27,31+128, "-Move image 1/2 screen", 155 .byte 'S'+128, 'h'+128, 'i'+128, 'f'+128, 't'+128, '+', 27,28+128, 27,29+128, 27,30+128, 27,31+128, "-Move image full screen", 155 .byte 'X'+128, "-Exit to DOS",155,0 jsr CanSave ; can image be saved in current graphics mode? beq @nomenu jsr strout .byte 'S'+128, "-Save",155,0 @nomenu: jsr GetKey ; now wait for user to press a key cmp #40 beq @redisp cmp #18 ; 'C' key bne @notr @redisp: inc repeat ; change parameters for current image @rerun: jsr Scr0off ; reset screen jsr HidePMGs jsr VBDLIOFF ; VBIs and DLIs jmp (RERUN) @notr: cmp #33 ; Space key - go back to image beq ShowImg cmp #28 ; Esc key - start again beq @rerun cmp #62 ; 'S' key - save beq @dosave cmp #22 ; 'X' key - exit to DOS bne @nomenu jmp DOS @dosave: jsr CanSave ; can image be saved in current mode? beq @nomenu ; no, go wait for another keypress jsr Scr0off jmp SaveImg ShowImg: ; return to image jsr Scr0off jmp waitkp ; and wait for user to press a key ;; ;; Show the current filter setting as readable text ;; ;; Acc - filter value ; ********************************************* ; for debugging purposes ; DispFilt: ; tax ; beq @half ; bmi @none ; jsr strout ; .byte "Quarter",0 ; rts ; @half: ; jsr strout ; .byte "Half",0 ; rts ; @none: ; jsr strout ; .byte "None",0 ; rts ; ********************************************* ; ; Can image be saved in the current graphics mode? ; Returns ; Zero flag set, image can't be saved ; Zero flag clear, image can be saved ; CanSave: ldx #0 lda RENDMODE cmp #'2' bcc @nosave ; APAC doesn't have a save option cmp #'3' ; GR8 doesn't have save, all others do beq @nosave inx @nosave: txa ; update zero flag rts ; ; Display "RIP32" on the screen ; DispRIP: jsr strout .byte "RIP32",0 rts ; ; Display "TIP" on the screen ; DispTIP: jsr strout .byte "TIP",0 rts ; ; Display "CIN" on the screen ; DispCIN: jsr strout .byte "CIN",0 rts ;; ;; Viewer start code, will be called immediately before ;; the image data is about to start arriving. Viewer ;; should open graphics mode, open output file, etc. here. ;; This is the first point that the WIDTH and HEIGHT information ;; is valid. Viewer should set up FirstCol, FirstRow, ;; LastCol, LastRow information here. ;; RafRendStart: ldx #0 lda COMPONENTS cmp #1 ; 1 component = greyscale image beq @notclr ; if greyscale, set USE2SCR to 0 inx ; if colour, set USE2SCR to 1 @notclr: stx USE2SCR lda WIDTH sta numcols lda WIDTH+1 ldy #3 ; divide pixels by 8 to get number of columns @colshft: lsr ror numcols bcc @noincx inc numcols ; round up any fractional parts @noincx: dey bne @colshft lda HEIGHT sta numrows lda HEIGHT+1 ldy #3 @rowshft: lsr ; divide pixels by 8 to convert into rows ror numrows bcc @noinrx inc numrows ; round up any fractional parts @noinrx: dey bne @rowshft lda #25 ; default to 200 lines (25*8) sta decdrrows ;; All modes except RIP uses the 16 colour data table, so default to ;; use the 16 colour data table lda #ColrGrid16 sta ColrGridAdr+2 ;; All modes except RIP use the 16 colour Cr and Cb tables lda #AtariCr sta CrAdr1+2 sta CrAdr2+2 lda #AtariCb sta CbAdr1+2 sta CbAdr2+2 ;; display menu lda #0 sta TABMAP sta TABMAP+1 sta COLOR4 lda #4 ; set position of tab stop for the menu sta TABMAP+2 ldx #1 @cpoff: lda FirstCol,x sta PrevColOff,x ; remember previous column offset dex bpl @cpoff jsr OpenKeyb lda prevopt ; default to previous display mode sta tmpcuropt askagain: ;; calculate filter values to display number of rows/cols this ;; image will use ldy tmpcuropt lda menuopts,y sta RENDMODE lda #255 ; default to no vertical filter sta USEVFILT lda @moderatio,y ; set up horizontal filter based Gr. mode sta USEFILT lda modecols,y ; set up number of cols this mode can do sta dispcols lda moderows,y sta tmpcalcbest jsr SubRowCol lda #40 ; Filter will automatically be set to give ; the equivalent of 40 columns ldx #1 @cmpcols: cmp tmpnumcols bcs @ckrows ; if all cols will fit on screen inx cpx #3 bcs @mult2 ; reached max filter value asl tmpcalcbest asl ; increase size and try again bcc @cmpcols @ckrows: lda tmpcalcbest @cmprows: cmp tmpnumrows bcs @mult2 ; all rows fit on screen too inx cpx #3 bcs @mult2 ; reached max filter value asl bcc @cmprows @mult2: dex beq @endflt lda USEFILT ; assumes USEFILT is always >= USEVFILT cmp #1 ; have we reached max reduction factor? beq @endflt inc USEVFILT inc USEFILT jmp @mult2 @endflt: lda repeat bpl @dispmenu jmp @setupmode @dispmenu: lda #0 sta repeat lda #125 ; clear the screen jsr PRINT1BYTE ;; start displaying the menu jsr strout .byte 'O'+128,"ffset Col:",0 lda ctrcol beq @ntcctr lda #'C' jsr PRINT1BYTE jmp @skcctr @ntcctr: lda FirstCol jsr AdjRedc ; adjust displayed offset if using filters @skcctr: ldy dispprev ; don't display previous col and row info bmi @firsttm ; the first time lda #0 ; display previous column info jsr DispPrevRowCol @firsttm: jsr strout .byte 127,"Row:",0 lda ctrrow beq @ntrctr lda #'C' jsr PRINT1BYTE jmp @skrctr @ntrctr: jsr AdjRedcRow ; adjust displayed offset if using filters @skrctr: ldy dispprev ; don't display previous col and row info bmi @firsttm2 ; the first time lda #1 ; display previous row info jsr DispPrevRowCol @firsttm2: jsr PrintNL ; lda #('S'*2) ; 'S' shifted left by 1 bit ; ldx #3 ; display 'S' in normal video if one of ; cpx tmpcuropt ; the save modes is selected since it won't ; ror ; toggle in those modes ; jsr PRINT1BYTE jsr strout .byte 'S' + 128, "creen: ",0 ; lda tmpcuropt ; cmp #4 ; screen is always off when saving ; bcs @dmaoff lda SAVEDM beq @dmaon @dmaoff: jsr strout .byte "Off",0 jmp @skon @dmaon: jsr strout .byte "On",0 @skon: jsr strout .byte 127, 'E'+128, 's'+128, 'c'+128, " Run again" .byte 155,155,155,0 ;; draw a horizontal line across the screen lda #82 ; control-r in internal code ldy #119 @horizlp: sta (SAVMSC),y dey cpy #80 bcs @horizlp jsr strout .byte "Screen: 40x25 Image:",0 jsr DispWidHei ; display # of cols + rows in image jsr PrintNL jsr DispFN lda #TOPLINE sta ROWCRS jsr NLStrOut .byte 'S'+128," TOGGLES SCREEN WHILE DECODING",155 .byte '1'+128,": APAC ",155 .byte '2'+128,": CIN ",155 .byte '3'+128,": C8 ",155 .byte '4'+128,": TIP ",155 .byte '5'+128,": RIP 32 ",155,0 jsr NLStrOut .byte "Choice?" .byte 0 lda RENDMODE ;; Set up ready to display prompt sta @displast ; display character for last menu selection lda ROWCRS sta tmpsvrowcrs ldx dispprev ; don't display last mode indicator if bmi @noprev ; this is the first time sec ; start from top of menu ;; ;; find position of previous mode on screen so that we can ;; display a '>' next to it ;; sbc #6 ; there are 5 items in the menu + 2 lines clc adc prevopt ; then find line of previous option ldx #1 ; position cursor and display a '>' next to jsr SetRowCol ; previous display mode jsr strout .byte ">",0 @noprev: lda USE2SCR ; is this a colour image? bne @iscolr ; yes, don't display greyscale message lda #16 ldx #2 jsr SetRowCol jsr strout .byte "Greyscale image, for better results",155,"use JpegView",0 @iscolr: ldx #9 ; position cursor at correct place to lda tmpsvrowcrs ; show selected mode jsr SetRowCol jsr strout @displast: .byte " ",30,0 @nextkp: jsr GetKpNoRet ; get user selection @not4: cmp #27 ; Esc key - run again bne @notesc jmp runagain @notesc: cmp #'S' ; S key - turn screen on/off bne @nots lda SAVEDM eor #1 sta SAVEDM @again: jmp askagain @nots: cmp #'O' ; O key - row/col offset bne @noto lda #0 sta ctrcol sta ctrrow jsr NLStrOut .byte 155,"Offset Col:C",30,0 jsr GetNum ; get number from user rol ctrcol ; if image to be centred, then carry set bne @ctrcl jsr AdjRowCol ; adjust row and column values to accout for sta FirstCol ; filters @ctrcl: jsr strout .byte "Offset Row:C",30,0 jsr GetNum ; get number from user rol ctrrow bne @again jsr AdjRowCol sta FirstRow jmp @again @noto: cmp #155 ; return key beq @retpress jsr GetAdr ; Returns address in Y, Acc untouched bcs @validsel jmp @nextkp ; invalid selection, get another keypress @validsel: tya ; valid selection, store mode lsr sta tmpcuropt ; remember index too jmp askagain ; redisplay screen ;; return key pressed, go display image @retpress: jsr CloseKeyb ; finished with keyboard @setupmode: ldx extraram lda TMPSCRADR,x sta scrpt2 lda TMPSCRADR+1,x sta scrpt2+1 jsr SetScrPt1 ; set up pointer to 1st screen lda tmpcuropt asl ; now jump to the correct routine for the tay ; selected mode iny lda @optadr,y pha dey lda @optadr,y pha rts @optadr: .word @apac-1, @c15-1, @c8-1, @tip-1, @rip32-1 ; horizontal x vertical colour pixel ratio ; 255 = width and height have same ratio, ; 0 = width is 2 x height ; 1 = width is 4 x height ; Horizontal/vertical ratio for modes APAC(x4),C15(x4),C8(x4), TIP(x2), RIP32(x4) @moderatio: .byte 1, 1, 1, 0, 1 @c8: jsr OpenGr15 ;; graphics 8 colours are in registers 1 and 2 lda #0 sta COLOR2 lda #8 sta COLOR1 bne exitstup ; forced branch ;; C15 mode uses GREY4 for one screen and colour for the other @c15: lda #14 ; Graphics 15 -> DL mode 14 jsr OpenGr ; open graphics mode lda #(GREY4-GREYTBL) ; set up grey levels for dithering sta MINGREYPOS ;; graphics 15 colours are 0, 1, and 2 + background (4) ldx #2 @setclr: lda micclrs+1,x sta COLOR0,x dex bpl @setclr bmi exitstup ; forced branch @tip: ;; don't need to set up dithering table here as that will be done ;; when rendering, TIP needs to use GREY8 for one screen ;; and GREY16 for the other jsr OpenGr15 lsr decdrrows ; TIP alternates between a colour row and a greyscale row ; this halves the number of available rows lda #65 sta GPRIOR ; enable gr.9 ldx #8 @sthipc: ; set colour registers up for HIP part of TIP screen txa ; these will be: asl ; 0,2,4,6,8,10,12,14,0 and #%00001111 sta PCOLR0,x dex bpl @sthipc bmi exitstup ; forced branch @rip32: ;; RIP uses the 9 colour data table lda #ColrGrid9 sta ColrGridAdr+2 ;; RIP uses the 9 colour Cr and Cb tables lda #RipCr sta CrAdr1+2 sta CrAdr2+2 lda #RipCb sta CbAdr1+2 sta CbAdr2+2 ; drops through to APAC setup @apac: ;; don't need to set up dithering table here as that will be done ;; when rendering, APAC needs to use GREY16 for one screen and colour ;; for the other, RIP 32 needs to use GREY16 for one screen and 9 ;; colour mode for the other jsr OpenGr15 @setgreys: lda #(GREY16-GREYTBL) ; APAC and RIP use 1 gr.9 screen with 16 greys sta MINGREYPOS lda #65 sta GPRIOR ; enable gr.9/10 exitstup: lda tmpcuropt sta prevopt ; remember current option for next time sta dispprev ; display prev row/col message next time lda dispcols sta decdrcols lda USEFILT ; check if horizontal filter is being used bmi @nofilt beq @filtx2 asl decdrcols ; it is, multiply number of columns by 2 @filtx2: asl decdrcols ; this is a x4 filter, mult cols by another 2 @nofilt: lda USEVFILT ; check if vertical filter is being used bmi @novfilt beq @vfiltx2 asl decdrrows ; it is, multiply number of rows by 2 @vfiltx2: asl decdrrows ; this is a x4 filter, mult rows by another 2 @novfilt: ldx #1 lda repeat ; if row and column values have already been set up bmi @skipcolsetup @centr: lda ctrcol,x ; if user wants to centre image beq @noctr sec lda numcols,x ; subtract display size from image size sbc decdrcols,x bcs @nor0 ; if image size >= display size, use difference lda #0 ; if image size < display size, use 0 @nor0: lsr sta FirstCol,x @noctr: dex bpl @centr @skipcolsetup: lda FirstCol ; calculate last column value from 1st column clc ; and display size adc decdrcols sta LastCol lda FirstRow ; calculate last row value from 1st row clc ; and display size adc decdrrows sta LastRow jsr CalcWidth lda #dithbuff sta nxtline+1 ; when dithering, 128 represents '0' lda #128 ; x < 128 represents negative values ldy #0 ; x > 128 represents positive values sty repeat ; clear repeat flag ldx #2 @clrlp: sta (nxtline),y iny bne @clrlp inc nxtline+1 dex bne @clrlp @clrlp2: sta (nxtline),y iny ; clear dither buffer and nxtline buffer cpy #<(DITHBUFLEN*4+NXTBUFLEN*3) bcc @clrlp2 lda SAVEDM beq @dmaon lda SDMCTL sta SAVEDM jsr SetDMAOff @dmaon: ldx #DMAToggle SetKeyIRQ: lda VKEYBD ; save current keyboard IRQ for later sta OSKEYBIRQ+1 lda VKEYBD+1 sta OSKEYBIRQ+2 sei ; inhibit IRQs stx VKEYBD ; set up keyboard IRQ to toggle DMA on/off sty VKEYBD+1 cli ; enable interrupts IRQrts: rts ;; ;; Draw image data. This gets called when a buffer full of image ;; data has been read and decoded from the image. Viewer ;; should display/save/etc the lines as it sees fit. Address ;; of data is in Acc (lo) and Y (hi). ;; ;; Data is stored in 3 bytes, there's 1 byte of greyscale data: ;; 256 levels (8 bit luminance), 1 byte per pixel; and 2 bytes ;; of colour data (8 bits for Cr, 8 bits for Cb). ;; ;; Data is arranged as BUFHEI * 8 * MAXBUFS lines of BUFWID * 8 pixels. ;; If WIDTH < BUFWID * 8 then remaining data in line will be empty, i.e. ;; if WIDTH = 10 then each line will be 10 bytes of image data, any bytes ;; beyond these should be ignored. ;; ;; The data buffers are ordered Y (greyscale), Cr (Colour), Cb (Colour) ;; RafRendDraw: sta rendpt ; save data buffer address sta crrendpt sta cbrendpt sty rendpt+1 tya clc adc #>(BUFWID * 8 * BUFHEI * 8 * MAXBUFS) sta crrendpt+1 ;; carry should still be clear here adc #>(BUFWID * 8 * BUFHEI * 8 * MAXBUFS) sta cbrendpt+1 stx rendline stx tmprendline jsr SaveScrPt jsr SetColCnt lda BUFCOUNT asl bne @ntfirst lda #128 ldy #NXTBUFLEN*3 @clrlp: dey sta nxtdith,y bne @clrlp ;; greyscale dither buffer lda #<(dithbuff-1) ; move back to start of dither buffer sta nxtvallo lda #>(dithbuff-1) sta nxtvalhi ;; Cr dither buffer lda #<(dithbuff + DITHBUFLEN * 2) sta nxtvallo+1 ; initial nxtline value for Cr buffer lda #>(dithbuff + DITHBUFLEN * 2) sta nxtvalhi+1 ;; Cb dither buffer lda #<(dithbuff + DITHBUFLEN * 3) sta nxtvallo+2 ; intial nxtline value for Cb buffer lda #>(dithbuff + DITHBUFLEN * 3) sta nxtvalhi+2 @ntfirst: lda RENDMODE ; jump to correct routine for current jsr GetAdr ; display mode lda @optadr2,y pha dey lda @optadr2,y pha rts ; jump to render routine @optadr2: .word rendapac-1, rendc15-1, rendc8-1, rendtip-1, rendrip32-1 ;; ;; Draw data in C.15 mode ;; rendc15: ; ; the colour screen needs to be reduced by 1/2 with respect to the ; greyscale screen, USEFILT and colcnt will have been set according to ; the colour screen's resolution, but we want to draw the greyscale ; screen at normal resolution so we multiply colcnt by 4 here to get the ; number of greyscale columns we need to process. This is messy and could ; be done better, but will do for now ; asl colcnt ; 8 pixels per column, GR.15 has 4 pixels per byte asl colcnt ; so we need to output colcnt*4 bytes of asl pixcnt ; greyscale? data rol pixcnt+1 ldy pixcnt dey sty pixcntm1 dec USEFILT @nextline: jsr Filter ldx #0 ; init dithering for Y buffer jsr SetDith ; initialise dither variables @gr15loop: lda #0 sta drawtemp @pixlp: ldy tmpy lda (rendpt),y ; get next pixel clc adc nxterr bcc @lt256 bpl @fndnr sec rol drawtemp sec rol drawtemp clc adc #1 bmi @ditpx ; forced branch @lt256: bpl @dodit ldx #0 .byte $2C @fndnr: ldx #2 jsr FNDNR ; find nearest available value to the cmp #2 ; requested one rol drawtemp ; move low 2 bits into drawtemp lsr rol drawtemp tya ldy tmpy jmp @ditpx @dodit: asl drawtemp asl drawtemp @ditpx: ldx #0 jsr DITHPIX ; distribute error amongst surrounding pixels inc nxtline ; move to next pixel bne @noincnx inc nxtline+1 @noincnx: bcc @noexit @rollop: tya ; this is the last byte of image, some pixels and #%00000011 ; may not be set beq @nojmpix asl drawtemp ; pad out any remaining pixels with 0's asl drawtemp iny bne @rollop ; forced branch @noexit: tya and #%00000011 sty tmpy bne @pixlp ; not finished current byte, go get next pixel @nojmpix: lda drawtemp sty tmpy ldy tmpx sta (scrpt1),y ; display pixel on screen lsr dithend bcs @skgrlp iny sty tmpx cpy colcnt ; is this the last column? bcc @gr15loop ; if not, got back and do some more @skgrlp: ; move screen pointer onto next line jsr add1scr40 jsr addtopt320 ; add 320 onto rendpt ldy rendline ; save last error value into buffer in lda nxterr ; case we have another horizontal block sta nxtdith-1,y ; of the image to come dec rendline ; have we done all the lines yet? beq @skiplp jmp @nextline ; if not then go do the rest @skiplp: lsr pixcnt+1 ror pixcnt ldy pixcnt dey sty pixcntm1 inc USEFILT jmp RendColr ; now draw the colour data ;; ;; Draw data in C.8 mode ;; rendc8: asl colcnt ; we are going to process colcnt*4 bytes asl colcnt asl pixcnt ; we need to process 4 times as many pixels rol pixcnt+1 asl pixcnt rol pixcnt+1 ldy pixcnt dey sty pixcntm1 ;; shouldn't need to change USEFILT here as we're not going to ;; user a filter for the greyscale data @dogr8: ldx #0 jsr SetDith ; initialise dithering variables @gr8loop: lda #0 sta drawtemp @pixlp: ldy tmpy lda (rendpt),y ; get next pixel clc adc nxterr ; add in the corresponding error php ; 128+128 = 256, so 256 is '0', hence ; carry determines if pixel is on or off rol drawtemp plp adc #0 @ditpx: ldx #0 jsr DITHPIX ; distribute error to surrounding pixels inc nxtline ; move to next pixel bne @noincnx inc nxtline+1 @noincnx: bcc @noexit .byte $24 @rollop: iny ; if we're reached the right edge of the tya ; image, but we haven't got a full byte and #%00000111 ; 8 pixels in 1 byte beq @nojmpix asl drawtemp ; then fill rest of byte with 0's jmp @rollop @noexit: tya and #%00000111 ; process 8 gr.8 pixels (1 byte worth) sty tmpy bne @pixlp @nojmpix: lda drawtemp sty tmpy ldy tmpx sta (scrpt1),y ; display pixel on 1st screen lsr dithend bcs @skgr8lp iny sty tmpx cpy colcnt ; is this the last column? bcc @gr8loop @skgr8lp: jsr add1scr40 jsr addtopt320 ldy rendline ; save last error value into buffer in lda nxterr ; case we have another horizontal block sta nxtdith-1,y ; of the image to come dec rendline bne @dogr8 lsr pixcnt+1 ror pixcnt lsr pixcnt+1 ror pixcnt ldy pixcnt dey sty pixcntm1 jmp RendColr ; now draw the colour data ;; ;; Draw in TIP mode ;; rendtip: asl colcnt ; there are 4 bytes for every column in TIP asl colcnt asl pixcnt rol pixcnt+1 ldy pixcnt dey sty pixcntm1 dec USEFILT @nextline: jsr Filter ; reduce pixels horizontally ldx #0 ; init dithering for Y buffer jsr SetDith ; initialise dithering variables @tiploop: lda #0 sta drawtemp sta drawtemp+1 jsr DithPix16 php ; save carry bit for later ;; acc will return with current value of drawtemp asl asl asl asl sta drawtemp plp bcs @drawpix ; reached end of line, leave last pixel blank jsr DithPix8 php asl asl asl asl sta drawtemp+1 plp bcs @drawpix jsr DithPix16 bcs @drawpix jsr DithPix8 @drawpix: ldy tmpx lda drawtemp sta (scrpt1),y ; put pixel on screen tya ; store both halves of the greyscale data interlaced tax ; on the same screen, this will then be re-arranged clc ; when the colour data is copied out of extended memory adc #40 tay lda drawtemp+1 sta (scrpt1),y lsr dithend bcs @sktiplp inx stx tmpx cpx colcnt ; is this the last column? bcc @tiploop @sktiplp: jsr add1scr40 jsr add1scr40 ; move data pointer onto next line jsr addtopt320 ; add 320 onto rendpt ldy rendline ; save last error value into buffer in lda nxterr ; case we have another horizontal block sta nxtdith-1,y ; of the image to come dec rendline ; have all lines been drawn? bne @nextline lsr pixcnt+1 ror pixcnt ldy pixcnt dey sty pixcntm1 inc USEFILT jmp RendColr ;; ;; Draw data in APAC / RIP 32 mode ;; rendapac: rendrip32: asl colcnt ; there are 4 bytes for every column in APAC asl colcnt @nextline: jsr Filter ; reduce pixels horizontally ldx #0 ; init dithering for Y buffer jsr SetDith ; initialise dithering variables @apacloop: lda #0 sta drawtemp jsr DithPix16B php ; save carry bit for later ;; acc will return with current value of drawtemp asl asl asl asl sta drawtemp plp bcs @drawpix ; reached end of line, leave last pixel blank jsr DithPix16B @drawpix: ldy tmpx sta (scrpt1),y ; put pixel on screen lsr dithend bcs @skapaclp iny sty tmpx cpy colcnt ; is this the last column? bcc @apacloop @skapaclp: jsr add1scr40 ; move data pointer onto next line jsr addtopt320 ; add 320 onto rendpt ldy rendline ; save last error value into buffer in lda nxterr ; case we have another horizontal block sta nxtdith-1,y ; of the image to come dec rendline ; have all lines been drawn? bne @nextline ;; now render the colour part of the image RendColr: lda nxtline sta nxtvallo lda nxtline+1 sta nxtvalhi lda USE2SCR ; is this a colour image? bne @docolr ; if so process colour data too jmp SetScrPos @docolr: .if ( * >= $4000 && * < $8000 ) .error .concat("Error - extended RAM overlap ", .string(*)) .endif ;; now process the colour data jsr_osramon @apacbuflp: lda tmprendline ; restore rendline back to original value sta rendline ;; now do the next buffer lda crrendpt ; update rendpt to point at next buffer sta rendpt ;; set up to filter the Cr buffer lda crrendpt+1 sta rendpt+1 jsr Filter ; run filter on Cr buffer lda rendpt sta crrendpt lda rendpt+1 sta crrendpt+1 lda cbrendpt ; update rendpt to point at next buffer sta rendpt lda cbrendpt+1 sta rendpt+1 lda tmprendline ; restore rendline back to original value sta rendline jsr Filter ; run filter on Cb buffer lda rendpt sta cbrendpt lda rendpt+1 sta cbrendpt+1 ldx #1 ; initialise dithering the Cr buffer jsr SetDith ldx #2 ; initialise dithering the Cb buffer jsr SetDith ;; rendpt now points to the correct location in the Cb buffer lda crrendpt+1 ; now set up filtpt to point to the sta filtpt+1 ; correct positon in the Cr buffer lda crrendpt sta filtpt lda nxtvallo+1 sta nxtvalcr lda nxtvallo+2 sta nxtvalcb lda nxtvalhi+1 sta nxtvalcr+1 lda nxtvalhi+2 sta nxtvalcb+1 @apacclrloop: lda #0 sta drawtemp jsr DithColr php ; save carry bit for later ;; acc will return with current value of drawtemp asl asl asl asl sta drawtemp plp bcs @clrdrawpix ; reached end of line, leave last pixel blank jsr DithColr @clrdrawpix: ;; read atari colour pixel here and store it on the colour screen ;; acc should contain drawtemp here ldy tmpx sta (scrpt2),y ; put pixel on screen lsr dithend bcs @skapacclrlp iny sty tmpx cpy colcnt ; is this the last column? bcc @apacclrloop @skapacclrlp: jsr add2scr40 ; move data pointer onto next line jsr addtopt320 ; add 320 onto rendpt lda #(BUFWID*8) jsr addtofl ldy rendline ; save last error value into buffer in lda nxterr+1 ; case we have another horizontal block sta nxtdith-1+NXTBUFLEN,y ; of the image to come lda nxterr+2 sta nxtdith-1+NXTBUFLEN*2,y dey sty tmprendline beq @rts lda rendpt sta cbrendpt lda rendpt+1 sta cbrendpt+1 lda filtpt sta crrendpt lda filtpt+1 sta crrendpt+1 jmp @apacbuflp ; no, go back and do the rest @rts: lda nxtvalcr sta nxtvallo+1 lda nxtvalcb sta nxtvallo+2 lda nxtvalcr+1 sta nxtvalhi+1 lda nxtvalcb+1 sta nxtvalhi+2 .if ( * >= $4000 && * < $8000 ) .error .concat("Error - extended RAM overlap ", .string(*)) .endif jsr_osramoff jmp SetScrPos crrendpt: .res 2 ; pointer to Cr buffer - keep with cbrendpt!! cbrendpt: .res 2 ; pointer to Cb buffer - keep with crrendpt!! tmprendline: .res 1 ; copy of rendline nxtvallo: .res 3 nxtvalhi: .res 3 nxtvalcr: .res 2 ; temp storage for Cr nxtline value nxtvalcb: .res 2 ; temp storage for Cb nxtline value ; offset into dither buffer carry over value nxtbufoff: .byte 0,(BUFHEI*8*MAXBUFS), (BUFHEI*8*MAXBUFS*2) ;; ;; TABLE OF GREY SCALE VALUES, SPECIFYING THE COLOUR VALUES ;; AVAILABLE. USED FOR DITHERING ;; GREYTBL: GREY4: ; KEEP GREY4 & GREYTBL TOGETHER .byte 49, 105, 160, 218 GREY8: .byte 0,36,73,109,146,182,219,255 GREY16: .byte 49, 62, 76, 91, 105, 120, 135, 150, 160 .byte 175, 190, 205, 218, 234, 249, 255 ;; ;; TABLE OF CUTOFF POINTS FOR GREYS IN GREYTBL. TO FIND THE CLOSEST ;; GREY TO THE ONE SPECIFIED, WE SEARCH THROUGH THE CUTOFF TABLE FOR ;; THE 1ST VALUE THAT IS GREATER THAN THE VALUE WE ARE LOOKING FOR. ;; THIS IS QUICKER THAN HAVING TO CALCULATE THE DISTANCE BETWEEN THE ;; VALUE WE ARE LOOKING FOR AND THE VALUES IN GREYTBL TO FIND ;; SHORTEST DISTANCE ;; CUTOFF: ;; cutoff values for GREY4 .byte 77, 132, 189, 255 ;; cutoff values for GREY8 .byte 18,54,91,127,164,200,237,255 ;; cutoff values for GREY16 .byte 55, 69, 83, 98, 112, 127, 142, 155, 167 .byte 182, 197, 211, 226, 241, 252, 255 ;; Cr component of Atari colours AtariCr: .byte 128, 157, 165, 171, 160, 152, 140 .byte 116, 105, 97, 84, 99, 111, 128, 145 ;; Cb component of Atari colours AtariCb: .byte 128, 75, 89, 113, 160, 171, 179 .byte 178, 170, 160, 113, 76, 64, 60, 64 ;; table of squares from 0 to 101 Squares: .word 0, 1, 4, 9, 16, 25, 36, 49 .word 64, 81, 100, 121, 144, 169, 196, 225 .word 256, 289, 324, 361, 400, 441, 484, 529 .word 576, 625, 676, 729, 784, 841, 900, 961 .word 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521 .word 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209 .word 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025 .word 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969 .word 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041 .word 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241 .word 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569 .word 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025 .word 9216, 9409, 9604, 9801, 10000, 10201, 10404, 10609 .word 10816, 11025, 11236, 11449, 11664, 11881, 12100, 12321 ;; Cr component of the 9 colours used for RIP RipCr: ; 0 2 3 5 7 9 10 12 14 .byte 128, 165, 171, 152, 116, 97, 84, 111, 145 ;; Cb component of the 9 colours used for RIP RipCb: ; 0 2 3 5 7 9 10 12 14 .byte 128, 89, 113, 171, 178, 160, 113, 64, 64 ;; ;; Get next colour pixel to draw and apply dithering to it ;; ;; returns C set for end of line, clear otherwise ;; Acc = Atari colour value ;; DithColr: ldy tmpy lda (rendpt),y ; get value from cb buffer ldx #2 ;; check if value is still in range, if not we need to bring it ;; into range jsr CheckRange sta cbValue lsr lsr lsr lsr sta colrIndx lda (filtpt),y ; get value from cr buffer dex jsr CheckRange sta crValue and #$F0 ora colrIndx tax ColrGridAdr: lda ColrGrid16,x ; gets changed after graphics mode has been selected ;; this will either be a colour index or a group of possible ;; closest colour values cmp #16 bcc @gotindex ; colour index, we can use this directly tax jsr FindNrClr ; find nearest colour @gotindex: ;; we've now got the colour index of the nearest Atari colour ;; work out error values tax stx DithTmpIdx ora drawtemp sta drawtemp lda crValue sec CrAdr1: sbc AtariCr,x eor #128 ldx nxtvalcr stx nxtline ldx nxtvalcr+1 stx nxtline+1 ldy tmpy ldx #1 jsr DITHPIX inc nxtvalcr ; move to next pixel bne @noincr inc nxtvalcr+1 @noincr: lda cbValue ldx DithTmpIdx sec CbAdr1: sbc AtariCb,x eor #128 ldx nxtvalcb stx nxtline ldx nxtvalcb+1 stx nxtline+1 ldy tmpy ldx #2 jsr DITHPIX ;; state of C is returned, don't change between here and the end inc nxtvalcb ; move to next pixel bne @noincb inc nxtvalcb+1 @noincb: lda drawtemp inc tmpy rts DithTmpIdx: .byte 0 ; temporary storage for index into colour table colrIndx: .byte 0 ; index into colour grid crValue: .byte 0 ; Cr value of pixel cbValue: .byte 0 ; Cb value of pixel ;; ;; Check value is inside the valid range ;; ;; parameters ;; Acc = value to check ;; X = 1 for Cr, 2 for Cb ;; ;; returns ;; Acc = checked value, guaranteed to be within valid range CheckRange: clc adc nxterr,x bcc @ckmin ;; carry is set, result must be >= 256 bmi @max ; value is -ve, result must be >= 384 @eor128: eor #128 rts @ckmin: bmi @eor128 lda #0 ; value is less than minimum so set to 0 rts @max: lda #255 ; value is greater than maximum, set to 255 rts ;; ;; Find nearest Atari colour ;; ;; params: ;; X = index of group to use plus 16 ;; ;; returns: ;; Acc = nearest colour index to requested colour ;; FindNrClr: lda #255 ; set minimum distance to maximum possible sta FindNrMinDist ; value sta FindNrMinDist+1 lda GroupTbl-16,x tay ; number of colours in this group inx stx FindNrTmpX FindNrLoop: ldx FindNrTmpX lda GroupTbl-16,x tax sta FindNrColrIndx lda cbValue sec CbAdr2: sbc AtariCb,x bcs @nonegcb ;; result is negative, convert it into it's positive equivalent eor #$ff ; 2's complement adc #1 ; carry will already be clear @nonegcb: asl tax lda Squares,x ; now square the difference sta FindNrDistance lda Squares+1,x sta FindNrDistance+1 ldx FindNrColrIndx lda crValue sec CrAdr2: sbc AtariCr,x bcs @nonegcr ;; result is negative, convert it into it's positive equivalent eor #$ff ; 2's complement adc #1 ; carry will already be clear @nonegcr: asl tax lda Squares,x ; now square the difference clc adc FindNrDistance sta FindNrDistance lda Squares+1,x adc FindNrDistance+1 sta FindNrDistance+1 ;; is distance to this point lower than distance to ;; previous point? lda FindNrDistance cmp FindNrMinDist lda FindNrDistance+1 sbc FindNrMinDist+1 bcs @notmin ; current distance is > minimum distance lda FindNrDistance sta FindNrMinDist lda FindNrDistance+1 sta FindNrMinDist+1 lda FindNrColrIndx sta @mincolrindx ;; otherwise either num1 is bigger or they are equal ;; code for num1 being bigger or both being equal goes here @notmin: inc FindNrTmpX dey ; need to check this will do correct number ; of loops bne FindNrLoop lda @mincolrindx rts @mincolrindx: .byte 0 FindNrTmpX: .byte 0 FindNrDistance: .word 0 FindNrMinDist: .word 0 FindNrColrIndx: .byte 0 ;; ;; Table of colour groups for 16 colour modes (APAC, C8, C15, TIP) ;; ;; these are used to reduce the number of colours we need to check ;; against. The first byte in a line is the number of colour indeces ;; in the group, the remaining bytes are the colour indeces ;; to check against ;; GroupTbl: .byte 2,10,11 ; group id 16 .byte 2,9,10 ; group id 19 .byte 2,8,9 ; group id 22 .byte 2,11,12 ; group id 25 .byte 2,7,8 ; group id 28 .byte 2,12,13 ; group id 31 .byte 3,0,10,11 ; group id 34 .byte 2,0,10 ; group id 38 .byte 3,0,9,10 ; group id 41 .byte 3,7,8,9 ; group id 45 .byte 3,11,12,13 ; group id 49 .byte 4,0,11,12,13 ; group id 53 .byte 2,0,11 ; group id 58 .byte 4,0,7,8,9 ; group id 61 .byte 2,6,7 ; group id 66 .byte 2,13,14 ; group id 69 .byte 3,1,13,14 ; group id 72 .byte 5,0,1,2,13,14 ; group id 76 .byte 3,0,1,2 ; group id 82 .byte 5,0,4,5,6,7 ; group id 86 .byte 3,5,6,7 ; group id 92 .byte 2,1,14 ; group id 96 .byte 2,1,2 ; group id 99 .byte 3,0,2,3 ; group id 102 .byte 2,0,3 ; group id 106 .byte 3,0,3,4 ; group id 109 .byte 2,4,5 ; group id 113 .byte 3,4,5,6 ; group id 116 .byte 2,5,6 ; group id 120 .byte 2,2,3 ; group id 123 .byte 2,3,4 ; group id 126 .byte 2,0,6 ; group id 129 .byte 3,0,5,6 ; group id 132 .byte 2,0,7 ; group id 136 .byte 3,0,6,7 ; group id 139 .byte 3,0,4,5 ; group id 143 .byte 3,0,1,8 ; group id 147 .byte 2,0,1 ; group id 151 .byte 2,1,8 ; group id 154 .byte 2,0,2 ; group id 157 ;; ;; Grid of closest colours/groups to a pixel in 16 colour modes ;; (APAC, C8, C15, TIP). ;; ;; First pixels are divided into blocks of 16x16. The closest colours ;; to all pixels in each block are pre-calculated. If there is ;; only one colour that is closest to all pixels in the block ;; then that colour index is stored in this grid. If there is ;; more than 1 closest colour for a block, then a group is ;; created in GroupTbl: and the offset+16 for that groups is ;; stored here. This way all colours indeces have values < 16 ;; and all groups have values >= 16 ;; ColrGrid16: .byte 11,11,11,16,10,10,10,10,10,19,19,9,9,9,22,22 .byte 11,11,11,11,16,10,10,10,10,19,9,9,9,22,22,8 .byte 25,11,11,11,16,10,10,10,10,19,9,9,9,22,8,8 .byte 25,25,11,11,16,16,10,10,10,19,9,9,22,8,8,28 .byte 12,25,25,11,11,16,10,10,19,9,9,22,22,28,28,7 .byte 12,12,25,25,11,16,10,10,19,9,22,22,28,28,7,7 .byte 31,31,12,25,25,11,34,38,41,22,45,28,28,7,7,7 .byte 13,31,31,31,49,53,58,0,0,61,28,7,66,66,66,66 .byte 13,13,69,69,72,76,82,0,0,86,92,66,6,6,6,6 .byte 69,69,14,96,96,99,102,106,109,113,116,120,120,6,6,6 .byte 14,14,96,96,99,99,123,3,126,4,113,113,120,120,6,6 .byte 14,96,96,1,99,2,123,3,126,4,4,113,5,120,120,120 .byte 96,96,1,99,99,123,3,3,126,126,4,4,113,5,5,120 .byte 96,1,99,99,2,123,3,3,3,126,4,4,113,113,5,5 .byte 1,1,99,2,2,123,3,3,3,126,4,4,4,113,113,5 .byte 1,99,99,2,2,123,3,3,3,126,4,4,4,4,113,5 ;; Grid of closest colours/groups to a pixel in 9 colour modes (RIP) ColrGrid9: .byte 7,7,66,6,6,6,6,6,6,120,120,5,5,5,5,5 .byte 7,7,66,66,6,6,6,6,6,120,5,5,5,5,5,113 .byte 7,7,7,66,6,6,6,6,6,120,5,5,5,5,113,113 .byte 7,7,7,66,66,6,6,6,6,120,5,5,5,113,113,4 .byte 7,7,7,7,66,6,6,6,120,5,5,5,113,113,4,4 .byte 7,7,7,7,66,66,6,6,120,5,5,113,113,4,4,4 .byte 7,7,7,7,7,66,129,129,132,5,113,113,4,4,4,4 .byte 7,7,7,7,7,136,139,0,0,143,113,4,4,4,4,4 .byte 8,8,8,8,8,147,151,0,0,109,126,126,126,126,4,4 .byte 8,8,8,8,154,154,82,157,102,106,3,3,3,3,126,126 .byte 8,8,8,154,154,1,99,2,123,123,3,3,3,3,3,3 .byte 8,8,8,154,1,1,99,2,2,123,3,3,3,3,3,3 .byte 8,8,154,1,1,99,2,2,2,123,3,3,3,3,3,3 .byte 8,154,154,1,1,99,2,2,2,123,123,3,3,3,3,3 .byte 154,154,1,1,1,99,2,2,2,2,123,3,3,3,3,3 .byte 154,1,1,1,1,99,2,2,2,2,123,3,3,3,3,3 ;; ;; Get next pixel to draw and apply dithering to it using 16 colours ;; ;; returns C set for end of line, clear otherwise ;; Acc = Atari greyscale value ;; DithPix16: ldy #(GREY16-GREYTBL) sty MINGREYPOS DithPix16B: ldy tmpy lda (rendpt),y ; get next pixel clc adc nxterr bcc @lt256 ;; carry is set, result must be >= 256 bpl @fndnr ; if positive: 256 <= result < 384 ;; value is negative, so result must be >= 384 tax lda #15 ; value is greater than maximum, so set ora drawtemp ; pixel to max value sta drawtemp ; X >= 128 and error is X+1 this is because: ; if X=128 (0), result=256, value used=255, err=1 ; if X=129 (+1), result=257, value used=255, err=2 ; if X=130 (+2), result=258, value used=255, err=3 ; etc. inx txa bmi @ditpx ; forced branch @lt256: bpl @ditpx ; value is less than minimum, pixel will be ; 0, acc already contains error value @fndnr: ; find nearest value to the requested one tay ; calculate hash value into colour table eor #128 lsr lsr lsr lsr clc adc #(GREY16-GREYTBL) ; use 16 colour table tax tya jsr FNDNR ora drawtemp ; store bottom 4 bits into current pixel sta drawtemp tya ldy tmpy @ditpx: ldx #0 ; dither the Y buffer jsr DITHPIX ; distribute error to surrounding pixels ;; don't modify carry below this point inc nxtline ; move to next pixel bne @noincnx inc nxtline+1 @noincnx: lda drawtemp inc tmpy rts ;; ;; Get next pixel to draw and apply dithering to it using 9 colours ;; ;; returns C set for end of line, clear otherwise ;; DithPix8: ldy #(GREY8-GREYTBL) sty MINGREYPOS ldy tmpy lda (rendpt),y ; get next pixel clc adc nxterr bcc @lt256 bpl @fndnr tax lda #7 ; value is greater than maximum, so set ora drawtemp+1 ; pixel to max value sta drawtemp+1 inx txa bmi @ditpx ; forced branch @lt256: bpl @ditpx @fndnr: tay ; calculate hash value into colour table eor #128 asl rol rol rol and #%00000111 clc adc #(GREY8-GREYTBL) ; use 9 colour table tax tya jsr FNDNR ; find nearest value to the requested one ora drawtemp+1 ; store bottom 4 bits into current pixel sta drawtemp+1 tya ldy tmpy @ditpx: ldx #0 ; dither the Y buffer jsr DITHPIX ; distribute error to surrounding pixels ;; don't modify carry below this point inc nxtline ; move to next pixel bne @noincnx inc nxtline+1 @noincnx: lda drawtemp+1 inc tmpy rts tmpx: .byte 0 tmpy: .byte 0 dithend: .byte 0 ; end of line flag for dithering widhi: .byte 0 ;; keep decdrcols and decdrrows together decdrcols: .byte 0 ; width of display in columns decdrrows: .byte 0 ; height of display in rows dispcols: .byte 0 ; number of columns passed to decoder ;; ;; Copy TIP screen data back down again from OS RAM ;; CopyTipScr: .if ( * >= $4000 && * < $8000 ) .error .concat("Error - extended RAM overlap ", .string(*)) .endif jsr_osramon ldx extraram lda TMPSCRADR,x sta rendpt lda TMPSCRADR+1,x sta rendpt+1 lda #SCR2ADR sta filtpt+1 ;; flicker modes alternate lines (i.e GR.9/10/9/10) to reduce flicker lda #SCRADR sta drawtemp+1 lda #<(SCR2ADR+40) sta scrpt1 lda #>(SCR2ADR+40) sta scrpt1+1 ldx #100 ; copy 100 lines worth, 2 at a time @tiplp: ;; copy even greyscale lines (0,2,4...) on first screen over to odd ;; lines (1,3,5...) on screen 2, this makes space for colour data on ;; screen 1 ldy #39 @tipcp: lda (drawtemp),y sta (scrpt1),y dey bpl @tipcp jsr add1scr40 jsr add1scr40 ;; copy colour data across to both screen 1 and screen 2, leaving a ;; blank line in between each line of colour data ldy #39 @tipcp2: lda (rendpt),y sta (filtpt),y sta (drawtemp),y dey bpl @tipcp2 lda #80 jsr addtofl jsr addtopt40 ; increase rendpt pointer by 40 bytes lda drawtemp clc adc #80 sta drawtemp bcc @nodhi inc drawtemp+1 @nodhi: dex bne @tiplp .if ( * >= $4000 && * < $8000 ) .error .concat("Error - extended RAM overlap ", .string(*)) .endif jmp_osramoff .include "common.asm" ; include common routines reslt: ; use first 5 bytes of dithering buffer as temp storage for ; conversion to decimal routine decfirst = reslt + 5 declowb = decfirst + 1 deciocb = declowb + 1 tmpsavmsc = deciocb + 1 tmpsdlstl = tmpsavmsc + 2 tmpnmien = tmpsdlstl + 2 tmpgprior = tmpnmien + 1 tmpwaitkeyp = tmpgprior + 1 tmppcolr0 = tmpwaitkeyp + 1 ; 9 bytes tmpsavval = tmppcolr0 + 9 tmpsavdisp = tmpsavval + 1 ; 0 if image is being saved while decoding ; 1 if image is saved from screen tmpsavept1 = tmpsavdisp + 1 tmpsavept2 = tmpsavept1 + 1 tmpsvmode = tmpsavept2 + 1 tmphipoff = tmpsvmode + 1 tmpscron = tmphipoff + 1 tmpcalcwid = tmpscron + 1 ; 2 bytes tmpheight = tmpcalcwid + 2 ; 2 bytes tmpcuropt = tmpheight + 2 tmpcalcbest = tmpcuropt + 1 tmpsvrowcrs = tmpcalcbest + 1 tmpnumcols = tmpsvrowcrs + 1 ; keep tmpnumcols and tmpnumrows together! tmpnumrows = tmpnumcols + 1 ; keep tmpnumcols and tmpnumrows together!!! RDBUF = tmpnumrows + 1 dithbuff: ;; size of dither buffer is the maximum width in pixels of the data ;; received from the decoder, plus some overscan, times the number ;; of components (Y*2, Cr, Cb) = 4. We need twice the size for Y ;; because in GR.8 modes, we need to be able to store 320 pixels ;; horizontally ;; we need to keep track of the rightmost column of pixels when ;; we have multiple horizontal buffers arriving from the decoder ;; so that the error value from the previous buffer can be carried ;; over into the next buffer. This will be the maximum height ;; in pixels of a decoder buffer times the number of components nxtdith = dithbuff + DITHBUFLEN * 4 segcodeend: .org nxtdith + NXTBUFLEN * 3 ;; make sure code doesn't run into the screen area, if it does ;; things will go badly wrong .if ( * >= (SCRADR/256)*256) .error ("Error - Code overrun into Screen area ", .string(*)) .else .out .concat("Code ends at ",.string(*)," Max=",.string((SCRADR/256)*256-1)) .endif ;; header for init coder .addr initcode .addr initcodeend-1 ;; We only need to do this once at the start, so it is ok to ;; overwrite this code later .org $B000 initcode: ;; ;; Check if there is any RAM under the OS ;; OSRTST = 65000 ; check this address to see if it is RAM or not XRAMTSTADR = $4000 ; check this address to see if there is any extended RAM DOSTSTADR = 3889 ; check this address to determine which DOS is being used Init: lda #0 sta FirstCol sta FirstRow ;lda #0 ; index of mode '1' (APAC) in menuopts sta prevopt ; default display mode for >= 64K machines sta extraram ; check for RAM under OS jsr_osramon ldy OSRTST ; PICK LOC. IN OS TO TEST ldx #255 stx OSRTST ; store $FF to test if it is RAM cpx OSRTST bne @NTRAM ; not RAM inx stx OSRTST ; store 0 to test if it is RAM cpx OSRTST bne @NTRAM ; not RAM sty OSRTST ; restore original value ldy #1 ; 1 FLAGS IT IS RAM bne @ISRAM @NTRAM: sty OSRTST ; RESTORE JUST IN CASE lda #2 ; mode '3' GR.9 sta prevopt ; set default display mode for 48K ldy #0 @ISRAM: sty DO2SCR jsr_osramoff ; now check if there is any extended ram too lda #2 sta extraram jsr_osramon lda XRAMTSTADR ; PICK LOC. IN OS TO TEST pha lda #255 sta XRAMTSTADR ; store $FF to test if it is RAM jsr_osramoff lda XRAMTSTADR pha lda #0 sta XRAMTSTADR jsr_osramon lda XRAMTSTADR beq @noextended lda #1 bne @staextended ; forced branch ; we have some extended ram available @noextended: ; no extended ram available lda #0 @staextended: sta hasextraram jsr_osramoff pla sta XRAMTSTADR jsr_osramon pla sta XRAMTSTADR lda #0 ; default to RAM under OS sta extraram jsr_osramoff ; detect which DOS is being used to determine whether to default to ; OS RAM or extended RAM lda DOSTSTADR beq @usexram ; Sparta 2.3e is 0 cmp #89 ; Sparta 3.2d and 3.2f beq @usexram cmp #68 ; Sparta 3.2g beq @usexram cmp #136 beq @usexram ; Sparta 3.3a and 3.3b cmp #244 ; DOS XE beq @usexram bne @exit @usexram: lda #2 sta extraram @exit: rts initcodeend: .addr 738 .addr 739 .word initcode ; run init code as soon as it is loaded