;; ;; a8jdpeg Image viewer, interfaces to jpeg decoder and displays image ;; in several different modes, with option to save image to ;; file in various formats ;; ;; Copyright 2002, Raphael Espino ;; last updated 14-Sep-02 ;; ;; to assemble: ;; assemble the decoder first to create jpy1223-8.o, then ;; ca65 a8jdpeg.asm ;; ld65 a8jdpeg.o jpy1223a8.o -o a8jdpeg -t atari ;; ;; ---------- renderer zero page addresses, 192 and up are available rendpt = 192 ; pointer to image data - 2 bytes filtpt = 194 ; filter pointer - 2 bytes 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 dithering ;; ---------- end of renderer zero page addresses ;; ------------- decoder information addresses ------------------ DISPCOLS = $600 ; (# of columns to display)/8 must be <= 40 DISPROWS = $601 ; (# of rows to display)/8 coloff = $602 ; Column offset to display image at rowoff = $603 ; Row offset to display image at numcols = $604 ; image width, 1 column = 8 pixels (pixels/8) numrows = $605 ; image height 1 row = 8 pixels (pixels/8) IOCBNUM = $606 ; IOCB to read jpeg data from STACKPT = $607 ; stack pointer at program start, use to return ; to DOS at any point ERROR = $608 ; non 0 if error ocurred decoding jpeg ; error codes are as defined in decoder RERUN = $609 ; 2 bytes - restart address, use to rerun decoder VERSION = $60B ; decoder version number MemTop = $60C ; End of decoder memory, memory >= MemTop is ; free for viewer when RendStart is called ; MemTop guaranteed to be below $8C00 width = $60E ; width of image in pixels (2 bytes) SkipErr = $610 ; Error in an image with restart markers ; decoder will continue, and set this to non 0 ;; page 6 addresses from $630 up are available to renderer ;; ------------- end of decoder info 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 ABORTCD = 128 ; code to tell decoder to abort MAXLEN = 64 ; max file name length ROWCRS = 84 ; current cursor row COLCRS = 85 ; current cursor column SAVMSC = 88 OLDCHR = 93 ; character under cursor at previous position OLDADR = 94 ; address of previous cursor position VDSLST = 512 ; DLI vector VKEYBD = 520 ; keyboard IRQ vector VVBLKI = 546 ; VBI routine vector SDMCTL = 559 ; shadow DMA control address SDLSTL = 560 ; shadow display list pointer 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 CH = 764 ; last keypress shadow address ;; IOCB addresses ICCOM = 834 ICSTA = 835 ICBAL = 836 ICBAH = 837 ICBLL = 840 ICBLH = 841 ICAX1 = 842 ICAX2 = 843 HPOSP0 = 53248 ; Player 0 horiz pos HPOSM0 = 53252 ; Missile 0 horiz pos SIZEP0 = 53256 ; Player 0 size SIZEM = 53260 ; Missile 0 size GRACTL = 53277 ; enable/disable PMGs COLPF1 = 53271 ; playfield 1 colour register COLPF2 = 53272 ; playfield 2 colour register PRIOR = 53275 ; priority register, enable GR.9/10 KBCODE = 53769 ; hardware keyboard code address PORTB = 54017 ; OS RAM control on XL/XE DMACTL = 54272 ; DMA control PMBASE = 54279 ; PMG base address WSYNC = 54282 ; wait for scan line sync NMIEN = 54286 ; NMI enable CIOV = 58454 ; CIO vector SETVBV = 58460 ; Set VBI vector EXITIM = 58463 ; Exit Immediate VBI vector lastfn = $580 ;; ICCOM values 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 DLADR = $630 ; display list address SCRADR = $A010 ; 1st screen address SCR2ADR = $5010 ; 2nd screen address PMGBASE = $70 ; put PMG's at $7000 SCR2TMP = $E000 ; temporary storage for screen 2 until ; decoder finishes ;; set up viewer jmp vectors .addr segvector .addr segvectorend-1 .ORG $0620 segvector: ;; next 12 bytes should be JMP's to renderer's Init, Start, Draw ;; and End code JMP RafRendInit ; init renderer 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: ;; DOS binary header for viewer .addr segcode .addr segcodeend-1 ;; renderer has area from $7900 upwards for itself and screen .ORG $7900 ;; ;; init code, this will be called when decoder starts or ;; when it is re-run. Renderer 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 ($606) ;; The rowoff and coloff values can optionally be set here ;; or in RendDraw. If they are not set they will default to 0 ;; DISPCOLS and DISPROWS can also optionally be set here or in ;; RendDraw. If not set they default to DISPCOLS = 40 (320 pixels) ;; DISPROWS = 24 (192 pixels) ;; *** DISPCOLS should NEVER be set to > 40 *** ;; segcode: RafRendInit: lda #64 sta NMIEN ; make sure DLIs are disabled lda #0 sta ROWCRS sta ctrrow sta ctrcol ;; display renderer information jsr strout .byte "a8jdpeg 0.8(14Sep02) Raphael Espino",155,155,155,0 jsr RestFN ; restore previous filename lda repeat ; don't ask for filename if we are redisplaying beq @newfn jsr strout .byte "Displaying ",0 jsr DispFN ; display filename too lda #0 ; ask user for filename next time round sta repeat ; by default beq chkext ; forced branch @newfn: jsr strout .byte "Add extra ':' at end for directory",155 .byte "File:D:",0 lda SAVMSC ; calculate position of prompt on screen clc adc #FNOFF ; TAB key sta rendpt+1 lda #FNOFF/40 ; tell tabirq which line prompt is on sta drawtemp jsr RestFN ldx #tabirq ; tab key, then display old filename jsr SetKeyIRQ clc jsr RDLINE ; get filename from user jsr ClrKeyIRQ ; remove keyboard IRQ lda RDBUF+2 ; return to environment if no cmp #155 ; filename entered bne cont DOS: ldx STACKPT txs ldy #0 rtscmd: rts cont: ;; make sure IOCB is available jsr CloseInFile ;; get ready to open the file stx IOCBNUM ; tell decoder what IOCB to use jsr SaveFN chkext: jsr CheckExt ; check if extension needs to be added to name ; also checks for ':' for directory lda wild ; does the user want the directory displaying? bne dirop openfl: lda #READ ; open the file .byte $2C ; ignore next 2 bytes dirop: lda #READDIR ; do a directory ldx #(LODCHN*16) jsr OpenFile bmi @error lda wild ; does user want to see directory? beq rtscmd jsr DispDir jsr CloseInFile jsr strout ; clear screen ready for next dir listing .byte 125,0 lsr redodir ; redo directory bcs chkext lda repeat beq @rerun jsr SaveFN ; filename selected from directory list @rerun: jmp (RERUN) ; so remember it and restart ;; an error occured, display error code and restart @error: lda ICSTA + (LODCHN*16) ; read status value pha ; remember error code jsr CloseInFile ; close file jsr strout .byte 155, "Error ",0 pla tax jsr DecOut ; display error code ; display file name too jsr strout .byte " - ",0 jsr DispFN ; display file name jmp WaitandRun ;; ;; renderer start code, will be called immediately before ;; the image data is about to start arriving. Renderer ;; should open graphics mode, open output file, etc. here. ;; This is the first point that numcols and numrows information ;; is valid. Renderer can optionally setup DISPCOLS, DISPROWS, ;; coloff, rowoff information here. ;; *** DISPCOLS should NEVER be set to > 40 *** ;; RafRendStart: lda #25 ; default to 200 lines sta DISPROWS jsr strout .byte 155,0 jsr dispwidhei ; display # of cols + rows in image ;; get column and row offsets from user ldy dispprev bmi @firsttm ; don't display previous col and row info jsr strout ; the first time .byte 155, "Prev ", 0 jsr disprowcol @firsttm: jsr strout .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 sta coloff ; so move carry into ctrcol to set it @ctrcl: jsr strout .byte "Offset row:C",30,0 jsr getnum ; get number from user rol ctrrow bne askagain sta rowoff askagain: ;; display menu lda #0 sta TABMAP ; set TAB stops lda #8 sta TABMAP+1 jsr strout .byte 155,'S'+128," TOGGLES SCREEN WHILE DECODING",155 .byte "1) GR.8",127,"40x25 2g 1/1", 155 .byte "2) GR.15",127,"20x25 4g 2/1", 155 .byte "3) GR.15",127,"40x25 4g 1/1", 155 .byte "4) GR.9",127,"10x25 16g 4/1", 155 .byte "5) GR.9",127,"20x25 16g 2/1", 155 .byte "6) GR.9",127,"20x12 16g 1/1", 155 .byte 0 lda #7 ; 8 options available on 48K machines sta optlen lda OSRAM bne @doram ; if so then display 64K options too jmp @noram @doram: jsr strout .byte "7) GR.8",127,"40x25 4", 0 jsr Disp64K1 jsr strout .byte "8) GR.15",127,"20x25 9", 0 jsr Disp64K2 jsr strout .byte "9) GR.15",127,"40x25 9", 0 jsr Disp64K1 jsr strout .byte "A) GR.9",127,"10x25 31", 0 jsr Disp64K4 jsr strout .byte "B) GR.9",127,"20x25 31", 0 jsr Disp64K2 jsr strout .byte "C) GR.9",127,"20x12 31", 0 jsr Disp64K1 jsr strout .byte "D) HIP",127,"20x25 30", 0 jsr Disp64K2 jsr strout .byte "E) HIP",127,"40x25 30", 0 jsr Disp64K1 lda #15 ; 16 options available on 64K machines sta optlen @noram: jsr strout .byte "N) Run again",155 .byte "S) Save",155 .byte "Choice?" .byte 0 ldy prevopt ; if first time, then no previous selection lda menuopts,y jsr SetPrmt ;; ;; find position of previous mode on screen so that we can ;; display a '>' next to it ;; ldx OSRAM bne @is64K sbc #8 ; 48K menu is 9 lines high .byte $2C @is64K: sbc #16 ; 64K menu is 17 lines high clc adc prevopt ; then find line of previous option ldx OSRAM beq @no64k ; position is already right for 48K mode cmp #15 ; options >=15 (64K options) are 2 lines higher bcc @ls15 sbc #2 ; carry is already set bne @no64k ; forced branch @ls15: cmp #13 ; options at pos < 13 are same 48K & 64K bcc @no64k clc ; options at pos 13-14 are 8 lines lower adc #8 ; there are 8 64K options @no64k: jsr DispPrmt @noprev: lda #0 sta USE2SCR sta USEFILT jsr getmenu ; get user selection sta RENDMODE ; remember user selection jsr getaddr bcs @optfnd jmp askagain @optfnd: tya lsr cmp #6 ; don't remember last option for rerun beq @nosav sta prevopt sta dispprev ; display prev row/col message next time @nosav: cpy #16 ; options >= 7-9 and A-E 2 screens rol USE2SCR lda @optadr,y pha dey lda @optadr,y pha lda RENDMODE ; ask user if they want to dither image cmp #'A' ; if the mode supports it bcs @n2askd jsr AskDither @n2askd: rts @optadr: .word @gr8-1, @gr15-1, @gr15a-1, @gr9-1, @gr9a-1, @gr9a-1 .word rerun-1, savetofile-1 .word @gr8-1, @gr15-1, @gr15a-1, @gr9-1, @gr9a-1, @gr9a-1 .word sethip-1, sethipa-1 @gr9a: inc USEFILT lda #20 .byte $2C @gr9: lda #10 ; 80/8 = 10 columns sta DISPCOLS LDA #(GREY16-GREYTBL) STA MINGREYPOS jsr OpenGr15 lda #SCR2TMP sta gr9scr22+2 ldx #SCRADR sty gr9scr+2 sty gr9scr2+2 lda RENDMODE ; check if image is to be doubled vertically cmp #'6' ; modes 6 and 13 are beq @dogrd cmp #'C' bne @skgrd @dogrd: txa clc adc #40 sta gr9scrd+1 ; set up addresses for doubled data sta gr9scr2d+1 bcc @noiny iny @noiny: sty gr9scrd+2 sty gr9scr2d+2 ldy gr9scr22+2 lda gr9scr22+1 clc adc #40 sta gr9scr22d+1 bcc @no92d iny @no92d: sty gr9scr22d+2 lda #$9D ; enable doubling of data sta gr9scrd sta gr9scr2d sta gr9scr22d lda #80 sta LINEBYTES lda #12 sta DISPROWS bne @sknop @skgrd: lda #$EA ; NOP operation ldy #2 @ea1: sta gr9scrd,y ; disable doubling of data dey bpl @ea1 ldy #2 @ea2: sta gr9scr2d,y dey bpl @ea2 ldy #2 @ea3: sta gr9scr22d,y dey bpl @ea3 lda #40 sta LINEBYTES @sknop: lda #65 sta GPRIOR ; enable gr.9 bne exitstup @gr8: jsr OpenGr15 ; open graphics mode lda #SCRADR sta gr8scr+2 lda #SCR2TMP sta gr8scr2+2 LDA #(GREY4-GREYTBL) STA MINGREYPOS ;; graphics 8 colours are 0 and 1 lda #0 sta COLOR2 lda #8 sta COLOR1 bne exitstup ; forced branch @gr15: lda #20 ; 160/8 = 20 columns sta DISPCOLS bne @skipfl @gr15a: ; leave DISPCOLS at default inc USEFILT @skipfl: LDX #(GREY4-GREYTBL) LDA RENDMODE cmp #'4' bcc @g4 LDX #(GREY9-GREYTBL) @g4: STX MINGREYPOS lda #14 ; Graphics 15 -> DL mode 14 jsr OpenGr ; open graphics mode lda #SCRADR sta gr15scr+2 sta gr15scr2+2 lda #SCR2TMP sta gr15scr2b+2 ;; graphics 15 colours are 0, 1, and 2 + background (4) ldx #2 @setclr: lda micclrs+1,x sta COLOR0,x dex bpl @setclr exitstup: ldx #1 @centr: lda ctrcol,x ; if user wants to centre image beq @noctr sec lda numcols,x ; subtract display size from image size sbc DISPCOLS,x bcs @nor0 ; if image size >= display size, use difference lda #0 ; if image size < display size, use 0 @nor0: lsr sta coloff,x @noctr: dex bpl @centr jsr CalcWidth lda dither beq @noinid lda #dithbuff sta nxtline+1 lda #128 ; 128 is the zero value, x < 128 is negative ldy #0 ; x > 128 is positive @clrlp: sta (nxtline),y iny bne @clrlp inc nxtline+1 @clrlp2: sta (nxtline),y iny cpy #66 bcc @clrlp2 @noinid: ;; set up our own keyboard IRQ to toggle DMA if key pressed lda #0 sta SAVEDM 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 rts ; ask user if they want the image dithered AskDither: lda dither clc adc #78 ; convert 0 to 'N' and 11 to 'Y' sta @lstdth @askdit: jsr strout .byte 155,"Dither (Y/N):" @lstdth: .byte 'Y',30,0 jsr getmenu sec sbc #78 ; convert 'N' to 0 and 'Y' to 11 beq @nodth cmp #11 bne @askdit ; invalid input, ask user again @nodth: sta dither rts ;; set up for drawing in HIP mode sethip: lda #20 ; 160/8 = 20 columns sta DISPCOLS bne nohipfilt sethipa: inc USEFILT nohipfilt: jsr OpenGr15 lda #SCR2TMP sta hipscr2+2 lda #SCRADR sta hipscr+2 lda #65 sta GPRIOR ; enable gr.9/10 ldx #8 @sthipc: ; set colour registers up for HIP txa ; these will be: asl ; 0,2,4,6,8,10,12,14,0 and #%00001111 sta 704,x dex bpl @sthipc jmp exitstup SVFNOFF=567 ; offset to start of save filename ;; ;; save the data to a file ;; savetofile: ; save image as data is being decoded lda #0 jsr SavePt1 jmp exitstup ;; ;; get information from user, open file and write file header to it ;; if C=clear then ask user for mode to save in ;; if C=set then don't ask user for mode to save in - this will be ;; set up before this routine is called ;; SavePt1: sta tmpsavtype SavePt2: jsr strout .byte 125,155,"1) Micropainter 2/1",155 .byte "2) Micropainter 1/1",155 .byte "3) HIP 2/1",155 .byte "4) HIP 1/1",155 .byte "5) PGM",155 .byte "N) Run again", 155 .byte "Choice?" .byte 0 ldy prevsave ; if first time, then no previous selection lda svmenuopts,y jsr SetPrmt sbc #6 ; menu is 7 lines high clc adc prevsave ; then find line of previous option jsr DispPrmt lda tmpsavtype beq @askuser ; image that is already on the screen ; user didn't press return key, add it here jsr PrintNL ; to position next prompt correctly ldy prevsave lda svmenuopts,y bne @naskd ; forced branch @askuser: jsr getmenu ; get user selection cmp #'N' ; 'N' run program again bne @save jmp rerun @save: cmp #'3' bcs @naskd pha jsr AskDither pla @naskd: sta SAVEMODE ; remember save mode for next time jsr getsvaddr bcs @svoptfnd ; save option found jmp savetofile ; unknown keypress, ask for another one @svoptfnd: tya lsr cmp #6 ; don't remember last option for rerun beq @svnosav sta prevsave @svnosav: jsr RestFN jsr PrintNL jsr DispFN lda #12 ; make sure prompt is always on the same line sta ROWCRS ; otherwise TAB completion won't work correctly jsr strout .byte 155,"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 prevsave ; figure out which extension to use tya and #%11111110 ; 1 and 2 (MIC), 3 and 4 (HIP), 5 (PGM) asl tay lda #3 sta tmpsavval @extlp2: lda @micext,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 sec JSR RDLINE ; get filename from user 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 lda tmpsavtype ; if saving from screen, go back to image bne @rts jmp SavePt2 ; otherwise, show save menu again @rts: rts @cont: ;; make sure IOCB is available JSR CloseOutFile BPL @ok @jmsver2: jmp saveerr2 ;; default extensions for save file formats @micext: .byte ".MIC" @hipext: .byte ".HIP" @pgmext: .byte ".PGM" @ok: ;; now open the file LDA #WRITE jsr OpenFile bmi @jmsver2 lda tmpsavtype ; if saving from image already on screen bne @noclr ; then don't clear it, or switch DMA off jsr SetDMAOff lda #>SCRADR jsr clrscr lda #40 ; 320/8 = 40 columns sta DISPCOLS jsr CalcWidth @noclr: ldx SAVEMODE ; if lsbit is clear, then use filter cpx #53 ; if PGM mode beq @savpgm dex ; set lsb for filter modes (2 and 4) txa ; it will be clear for rest (1, 3 and 5) lsr ; move lsb into carry rol USEFILT ; and then into USEFILT bne @dofilt lsr DISPCOLS ; if no filter, then only 1/2 as many columns @dofilt: sta savecount ; 24*8=192 for mic ; 25*8=200 for HIP cmp #24 ; ((49 or 50)-1)/2 = 24 -> Micropainter beq @savmic cmp #25 ; ((51 or 52)-1)/2 = 25 -> HIP beq @savhip @savpgm: lda dispwidth+1 sta putadr2+3 ; set number of bytes to save per line lsr ldx dispwidth stx putadr2+2 jsr DecOutRes ; convert width to ASCII ldy #4 @cprw: lda reslt,y ; copy width in ASCII to PGM header sta pgmwdt,y dey bpl @cprw lda numrows ; 1 row = 8 pixels, max of 25 rows cmp #26 ; multiplying a number > 25 might result bcc @mult8 ; in a value > 256, since only low byte is lda #200 ; used, force max value to 200 lines bne @decout @mult8: asl asl asl @decout: tax clc jsr DecOutRes ; convert height to ASCII ldy #4 @cprh: lda reslt,y ; copy height in ASCII to PGM header sta pgmhgt,y dey bpl @cprh ldy #46 ; save PGM bne @savhdr ; forced branch @savhip: lda #SCRADR sta hipscr+2 ldy #8 ; save HIP header 1 @savhdr: ldx #(SAVECHN*16) jsr SetICBPut bpl @xit jmp @jmsver2 @xit: clc rts @savmic: ;; save in micropainter format sta DISPROWS ; Acc should already be 24 lda #SCRADR sta gr15scr+2 clc rts ;; ;; Save Micropainter format colour information ;; SaveMicClrs: ldx #(SAVECHN*16) ldy #16 jmp SetICBPut ; save colours (grey levels) for image ;; ;; 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 ; pass key press to OS 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 FNOFF = 167 ; offset from screen top left to start of filename 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 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? LINEBYTES: .byte 0 ; number of bytes to skip for next GR.9 line 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 prevsave: .byte 255 ; previous save option selected ctrcol: .byte 0 ; centre image horizontally ctrrow: .byte 0 ; centre image vertically, should always be ; immediately after ctrcol E1: .byte 0 dither: .byte 11 ; should image be dithered? ; 0=N-78 11=Y-78 nxterr: .byte 128 ; used for calculating error value in dithering dispwidth: .word 0 ; width of image display in pixels, smallest of ; display width and image width (after filter) dispwidthm1: .byte 0 ; (low byte of dispwidth) - 1 ;; ;; draw image data. This gets called when 8 lines of image ;; data have been read and decoded from jpeg image. Renderer ;; should display/save/etc the 8 lines as it sees fit. Address ;; of data is in Acc (lo) and Y (hi). Data is 256 levels of ;; greyscale (8 bit luminance), 1 byte per pixel. Data is arranged ;; as 8 lines of 320 pixels ($A00 consecutive bytes). ;; If DISPCOLS < 40 then remaining data in line will be empty, i.e. ;; if DISPCOLS = 10 then each line will be 80 (10*8) bytes of image ;; data followed by 240 (30*8) bytes that should be ignored ;; *** DISPCOLS should NEVER be set to > 40 *** ;; RafRendDraw: sta rendpt ; save data buffer address sty rendpt+1 ldy #8 ; draw 8 lines each time we are called sty rendline lda RENDMODE cmp #'S' bne @getadr lda SAVEMODE ; if saving, then jump to save routines jsr getsvaddr lda @optsvadr2,y ; select correct save routine for selected mode pha ; by pushing its address onto the stack dey lda @optsvadr2,y pha rts ; jump to save routine @getadr: jsr getaddr lda @optadr2,y ; select correct routine for render mode pha ; by pushing its address onto the stack dey lda @optadr2,y pha rts ; jump to render routine @optadr2: .word rendgr8-1, rendgr15-1, rendgr15-1, rendgr9-1, rendgr9-1 .word rendgr9-1, 0, 0 .word rendgr8-1, rendgr152-1, rendgr152-1 .word rendgr92-1, rendgr92-1, rendgr92-1, rendhip-1, rendhip-1 @optsvadr2: .word rendgr15-1, rendgr15-1, rendhip-1, rendhip-1, savefile-1 ;; ;; save data to a PGM file ;; savefile: @nxtline: lda rendpt ; set address to save data from sta putadr2 lda rendpt+1 sta putadr2+1 ldy #38 ; save 1 line of data at a time ldx #(SAVECHN*16) ; IOCB should already be open jsr SetICBPut bmi sverr jsr addtopt320 dec rendline ; until all lines in current block are saved bne @nxtline UnusedVec: rts sverr: jmp saveerr ; error saving data ;; ;; draw data in gr.9 mode ;; rendgr9: jsr filter ; reduce pixels horizontally jsr SetDith ; initialise dithering variables gr9loop: lda dither beq @nodith ; render without dithering stx tmpx @pixlp: STY tmpy lda (rendpt),y ; get next pixel clc adc nxterr bcc @lt256 bpl @fndnr sec ; value is greater than maximum, so set rol drawtemp ; pixel to max value sec rol drawtemp sec rol drawtemp sec rol drawtemp clc adc #1 bmi @ditpx ; forced branch @lt256: bpl @dodit ; value is less than minimum @fndnr: ; find nearest value to the requested one ldx #(GREY16-GREYTBL) jsr FNDNR lsr ; now shift bottom 4 bits into drawtemp ror ; in the correct order ror ror rol drawtemp asl rol drawtemp asl rol drawtemp asl rol drawtemp tya jmp @ditpx @dodit: asl drawtemp ; set pixel (4 bits) to 0 asl drawtemp asl drawtemp asl drawtemp @ditpx: jsr DITHPIX ; distribute error to surrounding pixels bcc @noexit @rollop: tya lsr bcc @nojmpix asl drawtemp asl drawtemp asl drawtemp asl drawtemp iny bne @nojmpix ; forced branch @noexit: tya lsr bcs @pixlp ; go do next pixel @nojmpix: lda drawtemp ldx tmpx jmp gr9scr ; display byte (2 pixels) on screen @nodith: lda (rendpt),y ; get 1st pixel and #%11110000 ; use top 4 bits for grey level sta drawtemp iny lda (rendpt),y ; get 2nd pixel lsr ; shift top 4 bits to the low 4 bits lsr ; of pixel data lsr lsr ora drawtemp iny gr9scr: sta 30000,x ; put pixel on screen gr9scrd: sta 30000,x ; double pixel vertically inx cpx #40 ; do 40 bytes worth of data at a time bcs @skjg9 ; 40 bytes = 80 pixels jmp gr9loop @skjg9: lda gr9scr+1 ; move screen pointer onto next line clc adc LINEBYTES sta gr9scr+1 bcc @nogr9hi inc gr9scr+2 @nogr9hi: lda RENDMODE cmp #'6' bne @nog92 lda gr9scrd+1 ; move screen pointer onto next line clc adc #80 sta gr9scrd+1 bcc @nog92 inc gr9scrd+2 @nog92: ; move data pointer onto next line skip the other 240 bytes as they ; are empty jsr addtopt320 dec rendline ; have all 8 lines been drawn? beq @rts jmp rendgr9 ; no, go back and do the rest @rts: rts ;; ;; draw data in HIP mode ;; rendhip: lda RENDMODE cmp #'S' ; if we are saving, then use last 160 bytes bne @dodraw ; of 1st line and first 160 bytes of 2nd line clc ; for temporary disk buffer. This will work lda rendpt ; 'cos 1 line of disk buffer = 40 (8*40)=320 adc #160 ; and HIP only uses first 160 bytes (after sta svrend+1 sta hipsvclr+1 sta hipscr2+1 ; filter) of every line, so by the time we use lda rendpt+1 ; space for disk buffer, data will already have adc #0 ; been extracted sta svrend2+1 sta hipsvclr+2 sta hipscr2+2 lda #%00010001 bne @sethipsv ; forced branch @dodraw: jsr OSRAMON lda #0 @sethipsv: sta hipsvoff dohip: jsr filter ; reduce pixels horizontally ldy #0 ldx #0 hiploop: lda (rendpt),y ; get 1st pixel and #%11110000 ; use top 4 bits for grey level sta drawtemp iny iny lda (rendpt),y ; get 2nd pixel lsr ; shift top 4 bits to the low 4 bits lsr ; of pixel data lsr lsr ora drawtemp hipscr: sta 30000,x ; put pixel on screen dey lda (rendpt),y ; get 1st pixel lsr ; only 9 colour registers in gr.10 so and #%01110000 ; use 3 bits for grey level (8 colours) sta drawtemp iny iny lda (rendpt),y ; get 2nd pixel lsr ; shift top 3 bits to the low 3 bits lsr ; of pixel data lsr lsr lsr ora drawtemp clc ; HIP viewers have colours shifted 1 position adc hipsvoff ; to this implementation, so shift back if ; we are saving to HIP format hipscr2: sta 30000,x ; put pixel on screen iny inx cpx #40 ; do 40 bytes worth of data at a time bcc hiploop lda hipscr+1 ; move screen pointer onto next line clc adc #40 sta hipscr+1 bcc @nohiphi inc hipscr+2 @nohiphi: lda hipscr2+1 ; move screen pointer onto next line clc adc #40 sta hipscr2+1 bcc @nohiphi2 inc hipscr2+2 @nohiphi2: jsr addtopt320 ; add 320 onto rendpt, moving it onto next line dec rendline ; have all 8 lines been drawn? bne dohip ; no, go back and do the rest lda RENDMODE cmp #'S' bne hipend dec savecount svrend: lda #0 ; gets changed, save block address lo svrend2: ldy #0 ; gets changed, save block address hi jsr saveblkadr bpl @lneclear jmp saveerr @lneclear: ; now clear out data saved in first 160 bytes lda #0 ; of second line in buffer. This needs to be tay ; done 'cos if jpeg is less than 160 pixels tax hipsvlp: dey ; wide, then this data won't be overwritten hipsvclr: sta 30000,y ; by next buffer full of data and will get bne hipsvlp ; saved to file as part of the image txa bne @rts inc hipsvclr+2 ldy #64 inx bne hipsvlp ; forced branch @rts: rts hipend: jmp OSRAMOFF hipsvoff: .byte 0 ;; ;; draw data in gr.15 mode ;; rendgr15: jsr filter ; apply filter to image jsr SetDith ; initialise dither variables gr15loop: lda dither beq @nodith ; no dithering for this image stx tmpx @pixlp: sty 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 jmp @ditpx @dodit: asl drawtemp asl drawtemp @ditpx: jsr DITHPIX ; distribute error amongst surrounding pixels 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 bne @pixlp ; not finished current byte, go get next pixel @nojmpix: lda drawtemp ldx tmpx jmp gr15scr ; display byte (4 pixels) @nodith: lda (rendpt),y ; get 1st pixel and #%11000000 ; use top 2 bits for grey level sta drawtemp iny lda (rendpt),y ; get 2nd pixel and #%11000000 ; use top 2 bits and shift into position lsr lsr ora drawtemp sta drawtemp iny lda (rendpt),y ; get 3rd pixel and #%11000000 lsr lsr lsr lsr ora drawtemp sta drawtemp iny lda (rendpt),y ; get 4th pixel rol rol rol and #%00000011 ora drawtemp iny gr15scr: sta 30000,x ; display on screen inx cpx #40 ; do 40 bytes worth of data bcs @skgj jmp gr15loop @skgj: lda gr15scr+1 ; move screen pointer onto next line clc adc #40 sta gr15scr+1 bcc @nogr15hi inc gr15scr+2 @nogr15hi: jsr addtopt320 ; add 320 onto rendpt dec rendline ; have we done 8 lines yet? beq @chksv jmp rendgr15 ; if not then go do the rest @chksv: lda RENDMODE cmp #'S' bne @rts dec savecount lda #SCRADR sta gr15scr+2 jsr saveblock ; dump 8 lines of data to disk bpl @rts jmp saveerr @rts: rts ;; put a return char (newline) on the screen PrintNL: lda #155 ;; use CIO to put 1 byte on screen PRINT1BYTE: ldx #PUTBUF stx ICCOM ldx #0 ; use CIO's put one byte routine stx ICBLL stx ICBLH jmp CIOV ;; ;; FIND NEAREST AVAILABLE GREY ;; X = INITIAL HASH INTO GREY TABLE ;; A = GREY VALUE TO SEARCH FOR ;; FNDNR: eor #128 cmp CUTOFF,X bcc @FLOWR beq @FNDCSET @FHIGHR: inx cmp CUTOFF,X beq @FNDCSET bcs @FHIGHR @FNDCANY: ; CARRY CAN BE ANYTHING 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 ;; ;; TABLE OF GREY SCALE VALUES, SPECIFYING THE COLOUR VALUES ;; AVAILABLE. USED FOR DITHERING GREYTBL: GREY4: ; KEEP GREY4 & GREYTBL TOGETHER .byte 0,85,170,255 ; 4 COLOUR MODE VALUES GREY9: .byte 0,32,64,96,128,160,192,224,255 GREY16: .byte 0,17,34,51,68,85,102,119,136,153 .byte 170,187,204,221,238,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: .byte 42,127,212,255 .byte 26,60,94,128,162,196,230,247,255 .byte 9,6,43,60,77,94,111,128,145,162 .byte 179,196,213,230,247,255 ;; ;; 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*1/16 E*5/16 E*3/16 ;; ;; 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. We 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) ;; 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 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 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 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 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 dispwidth+1 bcc @notend cpy dispwidth bcc @notend ; return with C clear inc dithend ; end of line reached sec ; return with C set @notend: rts ;; ;; initialise variables to dither next line ;; SetDith: ldx #0 stx dithend stx widhi lda #<(dithbuff-1) sta nxtline lda #>(dithbuff-1) sta nxtline+1 ldy #1 lda (nxtline),y sta nxterr lda #128 sta (nxtline),y dey rts tmpx: .byte 0 tmpy: .byte 0 dithend: .byte 0 ; end of line flag for dithering widhi: .byte 0 ;; ;; draw data in gr.8 mode ;; rendgr8: lda USE2SCR ; are we using 2 screens? beq dogr8 jsr OSRAMON ; if so, turn on the RAM under the OS dogr8: jsr SetDith ; initialise dithering variables gr8loop: stx tmpx lda #0 sta drawtemp sta drawtemp+1 @pixlp: lda dither bne @dodith ; do dithering for this image ; No dithering for this image, ; pixel is on if value >= 128 otherwise it is off @nodpixlp: lda (rendpt),y ; get next pixel rol ; set GR.8 pixel if value >= 128 rol drawtemp ; store pixels here until we have all 8 rol rol drawtemp+1 ; do this anyway, it will be ignored iny ; if only using 1 screen tya and #%00000111 ; there are 8 pixels in each byte bne @nodpixlp ; go get next pixel cpx numcols bcc @ntmwi inc dithend ; reached end of line jmp @nojmpix @ntmwi: tya bne @nojmpix inc widhi inc rendpt+1 jmp @nojmpix ;; do dithering for this image @dodith: sty tmpy lda (rendpt),y ; get next pixel clc adc nxterr ; add in the corresponding error php ; 128+128 = 256, so 256 is '0', hence ldx USE2SCR ; carry determines if pixel is on or off beq @onepix ; not using 2 screens, 1 bit per pixel plp bcc @lt256 bpl @fndnr sec ; requested value is greater than max rol drawtemp ; set pixel to max value sec rol drawtemp+1 clc adc #1 bmi @ditpx ; forced branch @lt256: bpl @ddit ldx #0 .byte $2C @fndnr: ldx #2 jsr FNDNR ; find nearest value to the one requested lsr rol drawtemp+1 ; shift low 2 bits into position lsr rol drawtemp tya jmp @ditpx @ddit: asl drawtemp ; requested value was less than minimum asl drawtemp+1 ; set to 0 jmp @ditpx @onepix: rol drawtemp plp adc #0 @ditpx: jsr DITHPIX ; distribute error to surrounding pixels 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 rest of byte with 0's asl drawtemp+1 jmp @rollop @noexit: tya and #%00000111 ; process 8 gr.8 pixels (1 byte worth) beq @nojmpix jmp @pixlp @nojmpix: lda drawtemp ldx tmpx gr8scr: sta 30000,x ; set 8 pixels on screen lda USE2SCR beq gr8noram lda drawtemp+1 gr8scr2: sta 30000,x ; if using 2 screens set 8 pixels on 2nd screen gr8noram: lsr dithend bcs @skgr8lp inx cpx #40 ; do 40 bytes worth of data bcs @skgr8lp jmp gr8loop @skgr8lp: lda gr8scr+1 ; move screen pointer onto next line clc adc #40 sta gr8scr+1 bcc @nogr8hi inc gr8scr+2 @nogr8hi: lda gr8scr2+1 ; move screen pointer onto next line clc adc #40 sta gr8scr2+1 bcc @nogr8hi2 inc gr8scr2+2 @nogr8hi2: lda #64 jsr addtopt ; move data pointer onto next line lda widhi bne @noirp inc rendpt+1 @noirp: dec rendline beq @exit jmp dogr8 @exit: lda USE2SCR ; are we using 2 screens? beq @rts jsr OSRAMOFF ; yes, so disable OS RAM @rts: rts ;; ;; save error occured while decoding, need to reset interrupts ;; 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 ldx #255 jsr WAITKEYP ; wait for key press jmp SavePt2 ; go back to save options ;; ;; display save error message, and clean up ;; SvErrMsg: jsr strout .byte 155, "Save error ",0 ldx ICSTA + (SAVECHN*16) jsr DecOut jsr strout .byte 155,0 jsr DispFN jmp CloseOutFile ; close output file ;; ;; do a 1-2-1 filter on pixels to keep aspect ratio in gr.9 and 15 ;; this will reduce image width by half ;; filter: lda USEFILT beq @nofilt lda rendpt ; start at beginning of current line sta filtpt sta @destadr+1 lda rendpt+1 sta filtpt+1 sta @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 2 clc adc drawtemp+1 ; and add to pixel 2 cpx #159 ; make sure we don't go over right edge beq @destadr sta drawtemp+1 iny lda (filtpt),y ; get 3rd pixel lsr ; divide it by 4 lsr clc adc drawtemp+1 ; and add to pixel 1 + pixel 2 cpx dispwidthm1 ; stop at edge of image bcc @destadr lda #0 ; delete remaining pixels, these have already ; been used @destadr: sta 30000,x ; store result back at original pos lda #2 ; and skip a pixel jsr addtofl @noinchi: inx cpx #160 ; DISPCOLS*8 bcc @filtlp @nofilt: rts ;; ;; copy the second gr.15 screen to under the OS ;; rendgr152: jsr OSRAMON ; must be using 2 screens, os enable OS RAM dogr152: jsr filter jsr SetDith ; initialise dithering variables sty tmpx gr152loop: lda dither beq @nodith ; no dithering for this image @pixlp: sty tmpy lda (rendpt),y ; get next pixel clc adc nxterr bcc @lt256 bpl @fndnr sec ; value is greater than max, so set pixel rol drawtemp ; to max value sec rol drawtemp sec rol drawtemp+1 sec rol drawtemp+1 clc adc #1 bmi @ditpx ; forced branch @lt256: bpl @dodit @fndnr: tay ; calculate hash value into colour table eor #128 asl rol rol rol and #%00000111 clc adc #(GREY9-GREYTBL) ; use 9 colour table tax tya jsr FNDNR ; find nearest value to the requested one jsr getclr cmp #2 ; shift low 2 bits into drawtemp rol drawtemp lsr rol drawtemp txa cmp #2 ; shift low 2 bits into drawtemp+1 rol drawtemp+1 lsr rol drawtemp+1 tya jmp @ditpx @dodit: asl drawtemp ; value less than minimum, set pixel to 0 asl drawtemp asl drawtemp+1 asl drawtemp+1 @ditpx: jsr DITHPIX ; distribute error amongst surrounding pixels bcc @noexit @rollop: tya and #%00000011 ; 4 pixels per byte beq @nojmpix asl drawtemp ; if this is last byte, then pad any extra asl drawtemp ; bits with 0 asl drawtemp+1 asl drawtemp+1 iny bne @rollop @noexit: tya and #%00000011 ; if not last pixel in this byte bne @pixlp ; then go and get another one @nojmpix: lda drawtemp pha lda drawtemp+1 jmp @ddisp ; display bytes on screen ;; no dithering for this image @nodith: jsr getpixel ; get 1st pixel lsr ror ror sta drawtemp ; take 2 bits from value for 1st screen txa lsr ror ror sta drawtemp+1 ; take 2 bits from value for 2nd screen jsr getpixel ; get 2nd pixel asl asl asl asl ora drawtemp sta drawtemp txa asl asl asl asl ora drawtemp+1 sta drawtemp+1 jsr getpixel ; get 3rd pixel asl asl ora drawtemp sta drawtemp txa asl asl ora drawtemp+1 sta drawtemp+1 jsr getpixel ; get 4th pixel ora drawtemp pha txa ora drawtemp+1 @ddisp: ldx tmpx gr15scr2b: sta 30000,x ; display byte on screen pla gr15scr2: sta 30000,x inx stx tmpx cpx #40 ; do 40 bytes worth of data bcs @skjg2 jmp gr152loop @skjg2: lda gr15scr2+1 ; move screen pointer onto next line clc adc #40 sta gr15scr2+1 bcc @nogr152hi inc gr15scr2+2 @nogr152hi: lda gr15scr2b+1 ; move screen pointer onto next line clc adc #40 sta gr15scr2b+1 bcc @nogr152bhi inc gr15scr2b+2 @nogr152bhi: jsr addtopt320 ; add 320 onto rendpt, moving it onto next line dec rendline ; have we done 8 lines yet? beq @skpjmp jmp dogr152 ; if not then go do the rest @skpjmp: jsr OSRAMOFF @rts: rts ;; ;; get next pixel, we've got 9 grey levels to distribute over ;; 4 bits (0-15), with 2 bits for each screen ;; getpixel: lda (rendpt),y ; get 1st pixel iny lsr lsr lsr lsr cmp #15 adc #0 lsr getclr: tax lda @lowclrtab,x pha lda @hiclrtab,x tax pla rts @lowclrtab: .byte 0,0,1,1,2,2,3,2,3 @hiclrtab: .byte 0,1,1,2,1,2,2,3,3 ;; ;; copy the second gr.9 screen to under the OS ;; rendgr92: jsr OSRAMON dogr92: jsr filter ; reduce pixels horizontaly ldy #0 ldx #0 gr9loop2: lda #0 sta drawtemp sta drawtemp+1 lda (rendpt),y ; get 1st pixel and #%11110000 sta drawtemp sta drawtemp+1 lda (rendpt),y ; with 2 gr.9 screens we have 31 grey levels and #%00001000 ; or 5 bits worth. If 5th bit is clear then beq @noinc ; both screens have same colour lda drawtemp+1 cmp #240 ; if at max brightness, leave alone beq @noinc clc ; otherwise bump 2nd screen grey level up 1 adc #16 sta drawtemp+1 @noinc: iny lda (rendpt),y ; get 2nd pixel lsr lsr lsr lsr pha ora drawtemp sta drawtemp pla pha ora drawtemp+1 sta drawtemp+1 pla bcc @noinc2 and #%00001111 cmp #15 bcs @noinc2 inc drawtemp+1 @noinc2: lda drawtemp gr9scr2: sta 30000,x ; put pixel on screen gr9scr2d: sta 30000,x ; double pixel vertically lda drawtemp+1 gr9scr22: sta 30000,x ; put second pixel into temp memory gr9scr22d: sta 30000,x ; double pixel vertically iny inx cpx #40 ; do 40 bytes worth of data at a time bcc gr9loop2 ; 40 bytes = 80 pixels lda gr9scr2+1 ; move screen pointer onto next line clc adc LINEBYTES sta gr9scr2+1 bcc @nogr92hi inc gr9scr2+2 @nogr92hi: lda gr9scr22+1 ; move screen pointer onto next line clc adc LINEBYTES sta gr9scr22+1 bcc @nogr92hi2 inc gr9scr22+2 @nogr92hi2: lda RENDMODE cmp #'C' bne @nogr92hi4 @doscr2d: lda gr9scr2d+1 ; move screen pointer onto next line clc adc #80 sta gr9scr2d+1 bcc @nogr92hi3 inc gr9scr2d+2 @nogr92hi3: lda gr9scr22d+1 ; move screen pointer onto next line clc adc #80 sta gr9scr22d+1 bcc @nogr92hi4 inc gr9scr22d+2 @nogr92hi4: ; move data pointer onto next line skip the other 240 bytes as they ; are empty jsr addtopt320 ; add 320 onto rendpt dec rendline ; have all 8 lines been drawn? beq @exit jmp dogr92 ; no, go back and do the rest @exit: jmp OSRAMOFF rendline: .byte 0 ;; ;; end of data. Gets called when image has finished. Renderer ;; 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 @endsave lda RENDMODE cmp #'S' beq @dosave ; we were saving to disk, need to tidy up and #%11111110 cmp #'D' bne @nhip ; only enable PMGs in HIP mode jsr ShowPMGs ldy #0 lda #255 @setpmg: ; initialise Player and Missile 0's data area dey sta PMGBASE*256+384,y bne @setpmg @nhip: jmp notsave @dosave: ldx SAVEMODE dex txa lsr cmp #24 ; saving in Micropainter format beq @issav cmp #25 ; saving in HIP format beq @hipsave jmp @endsave ; must be mode 5 save @hipsave: lda savecount ; make sure we've saved enough data beq @svhip2 lda #>SCR2ADR ldx #2 ; clear 2 pages worth jsr clrarea ; clear temporary data area for save @hiplp: lda #SCR2ADR jsr saveblkadr bmi @sverr dec savecount bne @hiplp @svhip2: ldx #(SAVECHN*16) ldy #12 ; save HIP header 2 jsr SetICBPut bmi @sverr ldy #0 jsr SetICBPut ; save 2nd screen to disk bmi @sverr bpl @endsave @issav: lda savecount ; make sure we've saved enough data beq @saveclr lda #>SCRADR ldx #2 ; clear 2 pages worth jsr clrarea ; clear temporary data area for save @savlp: jsr saveblock bpl @ok @sverr: jmp saveerr @ok: dec savecount bne @savlp @saveclr: jsr SaveMicClrs ; save colours (grey levels) for image bmi @sverr @endsave: ;; we are saving to disk, so close file jsr CloseOutFile lda ERROR bne waitmsg jsr OpenGr0 jsr strout .byte 155,"Saved!",0 waitmsg: jsr strout .byte 155,0 lda ERROR beq WRunMenu ; save completed cmp #ABORTCD ; user aborted, start over beq beqrerun ;; handle any other error code here WaitandRun: ldx #0 .byte $2c WRunMenu: ldx #1 ;; drops down into WAITKEYP ;; ;; wait for key press ;; WAITKEYP: stx tmpwaitkeyp txa beq @noredis bpl @nocont jsr strout .byte 155,'A'+128, "-Try again",0 @nocont: jsr strout .byte 155,'C'+128,"-Change image options",0 @noredis: jsr strout .byte 155,'S'+128,'p'+128,'a'+128,'c'+128,'e'+128, "-Run again",155 .byte 'X'+128, "-Exit to DOS",155,0 nomenu: jsr GetKey cmp #33 ; Space key beqrerun: beq rerun cmp #22 ; X key bne @notdos jsr CloseInFile jmp DOS @notdos: ldx tmpwaitkeyp beq nomenu ; no A or C options on menu bpl @notcont ; no A option on menu cmp #63 ; A key bne @notcont rts @notcont: cmp #18 ; 'C' key bne nomenu inc repeat ; redisplay current image rerun: jsr CloseInFile jmp (RERUN) ; rerun decoder notsave: JSR VBION ; set up immediate VBI to switch screens lda #64 sta NMIEN ; make sure DLIs are disabled sta tmpnmien lda USE2SCR beq waitkp ; if we copied data to OS RAM jsr copyscr ; then copy it back down again lda RENDMODE cmp #'7' ; is this GR.8 mode? beq @gr8dli and #%11111110 cmp #'D' ; check for HIP mode (D and E) beq @hipdli cmp #'8' ; check for GR.15 modes (8 and 9) bne waitkp lda #2 ; this is a GR.15 mode sta COLOR0 asl ; Acc = 4 sta dlicol1 asl ; Acc = 8 sta COLOR2 lda #6 sta COLOR1 lda #10 sta dlicol2 lda #GR15DLI bne @stupdl @gr8dli: lda #2 sta COLOR2 sta dlicol2 asl ; Acc = 4 sta dlicol1 lda #6 sta COLOR1 lda #GR8DLI bne @stupdl @hipdli: lda #HIPDLI @stupdl: sta VDSLST stx VDSLST+1 jsr setdli ; set up DLI's on alternate lines lda #192 sta NMIEN ; enable DLIs sta tmpnmien ;; get key press waitkp: jsr GetKey cmp #28 ; ESC key - rerun decoder beq @exit cmp #33 ; SPACE BAR - display image info/help bne @notinf @infsc: jmp InfoScr @notinf: cmp #17 ; HELP key - display image info/help beq @infsc cmp #39 ; Inverse vid/Atari key - disp image info/help beq @infsc cmp #38 beq @infsc ; '/' key (shifted is '?' key) image info/help cmp #22 ; 'X' key - exit to DOS beq @exit cmp #18 ; 'C' change options for this image beq @repeat cmp #40 ; 'R' change options for this image beq @repeat cmp #62 ; 'S' save to file beq SaveScr cmp #57 beq @infsc cmp #6 ; + key beq @incclr cmp #14 ; - key bne waitkp lda COLOR4 ; decrease colour number used sec ; leave luminance unchanged sbc #16 jmp @dorest @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: rts @incclr: lda COLOR4 ; increase colour number used clc ; leave luminance unchanged adc #16 @dorest: sta COLOR4 inc changeclr bnewaitkp: bne waitkp ; forced branch SaveScr: ; check if we can save in this mode lda RENDMODE tax and #%11111110 cmp #'2' ; modes '2' and '3' (GR.15) beq @dosave cmp #'D' ; modes 'D' and 'E' (HIP) bne bnewaitkp txa sec sbc #('D'-'3') tax .byte $24 ; ignore next byte @dosave: dex txa sec sbc #'1' sta prevsave ; set up save mode jsr Scr0on lda #1 jsr SavePt1 ; open file for write and save header info @contsav: bcs @shwimg ; if file not created, C will be set jsr SetDMAOff jsr SwapScr ldx #(SAVECHN*16) lda prevsave and #%11111110 cmp #2 bcs @svhip ; saving in HIP format ;; saving in MIC format ldy #50 ; save screen data jsr SetICBPut bmi @notsvd jsr SaveMicClrs ; save colour data bmi @notsvd @saved: jsr CloseOutFile ; finished save jsr SwapScr lda #34 ; re-enable DMA jsr SetDMA jsr strout .byte 155,"File Saved!",0 @showhlp: jmp ShowHelp ; display help screen @notsvd: ; error while saving jsr SwapScr lda #34 ; re-enable DMA jsr SetDMA jsr saveerr2 ; display error message jmp @contsav ; and try again, decoder will restart if ; user selected anything other than try again @shwimg: jmp ShowImg ; go back to image @svhip: lda #1 ; HIP data is distributed over 2 screens sta tmpsavept2 ; with data alternating between them lda #SCR2ADR ; at a time from the correct screen sta rendpt+1 lda #<(SCRADR+40) sta filtpt lda #>(SCRADR+40) sta filtpt+1 lda #%00010001 sta tmphipoff @hiplp2: lda #100 ; save 100 lines from each screen (200 total) sta tmpsavept1 @hiplp: ldy #39 ; process next line (40 bytes worth) @hiplp4: lda (rendpt),y clc adc tmphipoff ; shift pixel values up by one for save sta tmphipbuf,y dey bpl @hiplp4 ldy #39 @hiplp3: lda (filtpt),y clc adc tmphipoff sta tmphipbuf+40,y dey bpl @hiplp3 ldy #54 jsr SetICBPut ; save 80 bytes (one line) of data to file bmi @notsvd ; error occured during save lda #80 ; move ahead 2 lines, skipping 1 jsr addtopt ; the skipped lines will be processed on lda #80 ; the other pass jsr addtofl dec tmpsavept1 bne @hiplp ldy #12 ; save header for 2nd HIP screen jsr SetICBPut lda #SCRADR sta rendpt+1 lda #<(SCR2ADR+40) sta filtpt lda #>(SCR2ADR+40) sta filtpt+1 lda #0 sta tmphipoff dec tmpsavept2 bpl @hiplp2 jmp @saved ; finished saving ;; ;; display image information screen ;; InfoScr: jsr Scr0on jsr strout .byte "Image: ",0 ; display all info for this image jsr RestFN jsr DispFN jsr strout .byte 155,"Mode: ",0 lda RENDMODE jsr PRINT1BYTE jsr strout .byte 155, "Dither: ", 0 lda RENDMODE cmp #'A' bcs @nodith ; force Dither to 'N' if mode doesn't lda dither ; support dithering .byte $2C @nodith: lda #0 clc adc #78 jsr PRINT1BYTE jsr strout .byte 155,"Size: ",0 jsr dispwidhei jsr strout .byte 155,"Offset: ",0 jsr disprowcol lda SkipErr ; tell user about any decoding errors beq @noerr ; that were skipped while decoding jsr strout .byte 155, 155, "*** CORRUPTED FILE ***",0 @noerr: ShowHelp: ; display help information too jsr strout .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 'S'+128, "-Save (modes 2, 3",0 lda OSRAM beq @not64K jsr strout .byte ", D, E",0 @not64K: jsr strout .byte " only)",155 .byte 'X'+128, "-Exit to DOS",155 .byte '+'+128, '/', '-'+128, "-Change image colours",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 ; PMGs 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' save to file bne @ntsv jmp SaveScr @ntsv: cmp #22 ; X key - exit to DOS bne @nomenu jmp DOS ShowImg: ; return to image jsr Scr0off jmp waitkp ; and wait for user to press a key ;; ;; reset IRQ and DMA ;; reset: lda VKEYBD+1 cmp #>dmatoggle ; if IRQ was not put in place, bne resrts ; then leave it alone lda VKEYBD cmp #EXITIM bne setimvbi VBION: ldy #IMVBI setimvbi: lda #6 jmp SETVBV ;; ;; swap 1K worth of image data down/up to/from lower RAM, so that ;; a GR.0 screen can be opened and information/help displayed without ;; overwriting the image data ;; Acc = source address ;; X = destination address ;; SwapScr: lda #$4b ; save data at page $4B, this should be sta rendpt+1 ; in the buffer area used by decoder lda #$bc ; save top 4 pages of screen RAM sta filtpt+1 lda #0 sta rendpt sta filtpt tay ldx #4 ; save 4 pages (1K) of data @cplp: lda (rendpt),y pha lda (filtpt),y sta (rendpt),y pla sta (filtpt),y dey bne @cplp inc rendpt+1 inc filtpt+1 dex bne @cplp rts ;; ;; restore Keyboard IRQ back to default, disabling our IRQ ;; ClrKeyIRQ: sei lda OSKEYBIRQ+1 sta VKEYBD lda OSKEYBIRQ+2 sta VKEYBD+1 cli rts ;; ;; immediate VBI code to switch between 2 screens ;; IMVBI: txa pha ;; update other colour registers based on colour in COLOR4 ;; leave luminance values unchanged do this in VBI to make ;; sure DLI doesn't swap values on us half way through lda changeclr beq @nochng ldx #7 @clrloop: lda PCOLR0,x and #%00001111 ora COLOR4 sta PCOLR0,x dex bpl @clrloop ldx #1 @clr2loop: lda dlicol1,x and #%00001111 ora COLOR4 sta dlicol1,x dex bpl @clr2loop lda #0 sta changeclr @nochng: lda USE2SCR beq @exit ; if only using 1 screen, then we've finished @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: and #%11111110 cmp #'D' ; check for HIP mode (modes D and E) bne @swclr lda #65 ; HIP mode, so change DLI sta hipgr1+1 lda #129 sta hipgr2+1 bne @exit @swclr: 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 and #%11111110 cmp #'D' ; check for HIP mode (modes D and E) bne @swclr lda #129 ; HIP mode, so change DLI sta hipgr1+1 lda #65 sta hipgr2+1 jmp @exit changeclr: .byte 0 ;; ;; display embeded string on screen ;; string data should follow strout call and be terminated ;; with a 0 ;; 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 #42 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 ;; ;; use CIO to read a line of input from user ;; RDLINE: php lda COLCRS ; remember current cursor pos - 2 sec sbc #2 sta @hpos lda ROWCRS sta @vpos jsr GetLine lda SAVMSC sta devpt lda SAVMSC+1 sta devpt+1 ldy @vpos lda devpt @dvloop: dey ; calculate position on screen of bmi @vdone ; device name clc adc #40 bcc @dvloop inc devpt+1 bne @dvloop @vdone: sta devpt lda @hpos clc adc devpt sta devpt bcc @nopt1 inc devpt+1 @nopt1: ldy #1 @cpdev: lda (devpt),y ; get device name from screen jsr int2asc ; convert char to ATASCII sta RDBUF,y ; add device name to buffer dey bpl @cpdev plp bcs @rts ; don't update filename length if saving ldx ICBLL stx fnlen inx lda #155 sta RDBUF,x dex ; find position of last ':' or '>' in filename @fndcln: ; not counting last character in filename ; anything before this will be device dex ; and directory name bmi @nocoln ; make sure we don't get into infinite loop ; if no ':' in filename lda RDBUF,x and #%11111011 ; converts 62 to 58 cmp #':' ; is this a ':' or a '>' ? bne @fndcln dex @nocoln: inx ; if no colon, set devlen to 0 stx devlen ; remember position for later stx savedevlen @rts: rts @hpos: .byte 0 @vpos: .byte 0 ;; ;; get a line worth of data ;; GetLine: ldx #0 lda #GETLNE ; read line of data into buffer at RDBUF+2 ldy #4 jmp SetICBICC ;; ;; 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 ;; ;; close IOCB ;; CloseInFile: ldx #(LODCHN*16) .byte $2C CloseOutFile: ldx #(SAVECHN*16) CloseChX: lda #CLOSE jmp SetICBICC ;; ;; get a key press without waiting for the return key to be pressed ;; GetKey: lda #255 sta CH @waitkpd: lda CH cmp #255 beq @waitkpd ldx #255 stx CH and #%00111111 ; ignore shift and control modifiers rts ;; ;; switch interrupts off and enable OS RAM ;; 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 lda #64 sta NMIEN cli 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 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 ; which will be same colour as background lda dispwidth ; right edge depends on width of image clc ; so calculate positon of player 0 from that adc #48 tax lda RENDMODE cmp #'E' bne @skdec dex @skdec: stx HPOSP0 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 ;; ;; 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 ;; 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 ;; clear 8K worth of RAM for screen clrscr: ldx #32 ; 8K worth of screen data clrarea: sta filtpt+1 ; screen starting page ldy #0 tya sta filtpt @clrlp1: dey sta (filtpt),y bne @clrlp1 inc filtpt+1 dex bne @clrlp1 rts1: 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 # open file for read ;; Acc = 6 -> open for directory read ;; Acc = 8 -> open file for write ;; OpenFile: sta ICAX1,X ldy #24 ; open file, filename in RDBUF jmp SetICBOpen OSRAM: .byte 255 ;; ;; copy screen data back down again from OS RAM ;; copyscr: jsr OSRAMON lda #SCR2TMP sta rendpt+1 lda #SCR2ADR sta filtpt+1 ;; 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: ;; 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 lda drawtemp clc adc #80 sta drawtemp bcc @nodhi inc drawtemp+1 @nodhi: jsr addtopt40 ; increase rendpt by 40 bytes dex bne @hiplp jmp OSRAMOFF ;; ;; check if extension needs to be added to filename ;; CheckExt: lda #0 sta wild ; no ':' found yet ; find position of last ':' or '>' in filename ldx fnlen ; not counting last character in filename dex ; anything before this will be device dex ; and directory name lda RDBUF+2,x ; if last char is ':' this is a dir command cmp #':' ; find position of last ':' in filename bne @chkdt ; not a dir command, check if extension needed dec fnlen inc wild ; this is a dir command lda RDBUF+1,x and #%11111011 ; check for '>' or ':' cmp #':' beq @dodir ; if two ':' are together at end, do *.JPG lda RDBUF+1 ; if last and 2nd char are both ':' and #%11111011 cmp #':' ; then this is a dir command beq @gt1 cpx #1 ; if only ':' in filename, then do *.JPG bcs @gt1 ; if more than 1 char, this is a dir command jmp DOS ; otherwise go to DOS @gt1: lda #155 ; so replace that ':' with a return sta RDBUF+2,x @defd: dex bpl @chkdt inx @dodir: inc fnlen lda #'*' sta RDBUF+2,x bne @addext ; forced branch @chkdt: lda #'.' cmp RDBUF+2,x ; if last char is '.' no extension needed bne @skpdt lda #155 ; so replace that '.' with a return sta RDBUF+2,x rts @skpdt: ldy #4 ; check last 4 characters @extlp: dex beq @addext ; end of name, add extension cmp RDBUF+2,x beq @rts ; extension is already there, ignore dey bne @extlp @addext: ldx fnlen ldy #0 dex @cpextlp: lda @extname,y ; add extension to filename buffer beq @rts sta RDBUF+2,x inx iny bne @cpextlp @rts: rts @extname: .byte ".JPG",155,0 devlen: .byte 0 ; length of device + directory name in filename savedevlen: .byte 0 ; length of device + dir name in saved filename ;; ;; 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 ;; address, length icbdat: .word SCRADR, 40*200 ; y=0 .word RDBUF+2, MAXLEN-6 ; y=4 .word hiphdr10, 6 ; y=8 .word hiphdr9, 6 ; y=12 .word micclrs, 4 ; y=16 .word K ; y=20 - 2 bytes .word E ; y=22 - 2 bytes .word RDBUF ; y=24 - 2 bytes, keep 24 and 26 together .word 0 ; y=26 - 2 bytes, keep 24 and 26 together .word 40 ; y=28 - 2 bytes .word RDBUF, MAXLEN ; y=30 putadr1: .word 0, 40*8 ; y=34 putadr2: .word 0, 320*8 ; y=38 putadr3: .word 0, 0 ; y=42 .word pgmhdr,pgmen-pgmhdr ; y=46 .word SCRADR, 40*192 ; y=50 .word tmphipbuf, 80 ; y=54 wild: .byte 0 ; display directory flag ;; ;; display the disk directory using an open IOCB ;; FNCHAR1 = 4 ; position of first character in filename DispDir: ldx #(KEYBCHN*16) ; open keyboard so we can read single keypress jsr CloseChX lda #4 sta ICAX1,x lda #0 sta dirend ; directory end not found yet sta dircount ; not filenames read from directory yet ldy #20 jsr SetICBOpen ; this will set ICBLL/H as well, but they are ; ignored anyway 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 #28 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 #'X' ; menu letters run from A-W bcc @nxtline @dirend: @prompt: jsr strout .byte 'S'+128, 'p'+128, 'a'+128, 'c'+128, 'e'+128, "Next " .byte '-'+128, "Prev " .byte '<'+128, "Up Dir " .byte 'E'+128, 's'+128, 'c'+128, "Quit",0 @getagain: ldx #(KEYBCHN*16) lda #GETBUF ldy #24 ; set ICBLL/H to 0, don't care about ICBAL/H jsr SetICBICC ; get keypress and #%01111111 ; ignore inverse video cmp #'<' bne @notudir ldx devlen cpx #3 ; we're not in a subdirectory, so can't move bcc @getagain ; up one level @fnddv: dex lda RDBUF,x ; find start of current subdirectory name and #%11111011 ; converts 62 to 58 cmp #':' bne @fnddv ldy savedevlen iny stx devlen ; update current device/directory name position inx 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 #<920 ; update current top of screen position sta rendpt ; this should be on the previous screenful now txa sbc #>920 sta rendpt+1 clc lda svdircount adc #23 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 @jmexit @nextscr: jsr strout ; clear screen ready for next screenfull .byte 125,0 jmp @doagain @ntspc: cmp #27 ; is this the Esc key? bne @skjmpx @jmexit: jmp @exit ; yes, so exit @skjmpx: and #%01011111 ; make lowercase same as uppercase cmp #'A' ; did user press a menu key? bcc @getagain ; unknown key, go get another one cmp lastdirc ; key is above last valid keypress bcs @getagain ; go get another one 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: ldx devlen inx 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 ; an extension cmp #'D'+128 bne @ntdir inc redodir ldy savedevlen stx devlen lda #'>' bne @cpdrgt @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 ldy savedevlen stx devlen @cpdrct: lda lastfn,y @cpdrgt: sta RDBUF,x iny inx cmp #155 bne @cpdrct dex bpl @endcpdr @norddir: lda #155 ; end last filename with RETURN char sta RDBUF,x inc repeat @endcpdr: dex stx fnlen @exit: ldx #(KEYBCHN*16) ; close keyboard and exit jmp CloseChX ;; display row and column information disprowcol: jsr strout .byte "Col=", 0 ldx coloff jsr DecOut jsr strout .byte " Row=", 0 ldx rowoff jmp DecOut ;; display image width and height information dispwidhei: jsr strout ; display image size .byte "Width=",0 ldx numcols jsr DecOut jsr strout .byte " Height=",0 ldx numrows jmp DecOut ;; ;; 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 DISPCOLS ; calculate displayed image size in pixels asl asl ; max display widths is DISPCOLS*8 asl ; (1 COL = 8 pixels) sta dispwidth lda #0 sta tmpcalcwid+1 rol ; DISPCOLS must be <= 40, so only third asl sta dispwidth+1 ; can cause carry to be set ldx #3 lda coloff ; convert horizontal image offset from @mult8: ; colums to pixels (1 col = 8 pixels) asl rol tmpcalcwid+1 dex bne @mult8 sta tmpcalcwid lda width ; subtract offset in pixels from image sec ; width sbc tmpcalcwid tax lda width+1 sbc tmpcalcwid+1 ; get largest of image width and display width ldy USEFILT ; if using a filter, then displayed image beq @nfilt ; width is half of actual image width lsr tay txa ror tax tya @nfilt: 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 dex stx dispwidthm1 ; dispwidth minus 1 rts K: .byte "K:" 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 ;; ;; 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 #30 jsr SetICBL jmp addtopt40 ;; 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 ;; ;; save filename for later ;; SaveFN: ldx #MAXLEN dex @cpflnam: lda RDBUF,x ; copy filename from buffer to storage area sta lastfn,x dex bpl @cpflnam rts ;; ;; restore previously saved file name ;; RestFN: ldx #MAXLEN dex @cpflnam: lda lastfn,x ; copy filename from buffer to storage area sta RDBUF,x dex bpl @cpflnam rts repeat: .byte 0 ;; ;; display file name ;; DispFN: ldy #30 lda #PUTLNE ; write out file name ldx #0 jmp SetICBICC ;; ;; set up ready to display prompt ;; SetPrmt: sta displast ; display character for last menu selection lda ROWCRS sta svrowcrs sec ; start from top of menu rts ;; ;; display prompt ;; DispPrmt: sta ROWCRS lda #1 sta COLCRS jsr strout .byte ">",0 lda #9 sta COLCRS lda svrowcrs sta ROWCRS jsr strout displast: .byte " ",30,0 rts svrowcrs: .byte 0 ;; ;; display text at end of line in menu, this saves a few bytes ;; Disp64K1: lda #'1' .byte $2C ; ignore next 2 bytes Disp64K2: lda #'2' .byte $2C ; ignore next 2 bytes Disp64K4: lda #'4' sta ratiochr jsr strout .byte "g " ratiochr: .byte " /1 (64K)",155,0 rts ;; ;; open screen for GR.0 ;; OpenGr0: ldx #0 jsr CloseChX lda #12 sta ICAX1 ldy #22 ; open E: for Gr.0 jmp SetICBOpen E: .byte "E:" ;; ;; save one block of data for Micropainter format ;; X = IOCB number * 16 ;; A = data address low byte ;; Y = data address high byte ;; saveblock: lda #SCRADR saveblkadr: ldx #(SAVECHN*16) sta putadr1 sty putadr1+1 ldy #34 jmp SetICBPut ;; ;; get address of save routine for selected mode ;; getsvaddr: pha lda #svmenuopts ldy #6 bne skipmn ; forced branch ;; ;; get address of render routine for selected mode ;; getaddr: pha lda #menuopts ldy optlen skipmn: sta menupt stx menupt+1 pla @optloop: cmp (menupt),y ; check if key is on menu beq @optfnd dey bpl @optloop clc rts @optfnd: tya asl tay iny sec rts ;; recognised options for main menu menuopts: .byte "1", "2", "3", "4", "5", "6", "N", "S" .byte "7", "8", "9", "A", "B", "C", "D", "E" ;; recognised options for save to file menu svmenuopts: .byte "1", "2", "3", "4", "5", "N" optlen: .byte 0 ;; ;; add value onto 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 value onto pointer ;; addtofl40: lda #40 ;; add value in Acc onto pointer addtofl: clc adc filtpt sta filtpt bcc @noinhi inc filtpt+1 @noinhi: 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 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 ;; PGM file format header information pgmhdr: .byte "P5 #a8jdpeg-0.8",10 pgmwdt: .byte "00000 " ; image width (pixels) pgmhgt: .byte "00000 " ; image height (pixels) pgmlevs: .byte "255",10 ; number of grey levels pgmen: 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 tmpsavtype = tmpsavval + 1 ; 0 if image is being saved while decoding ; 1 if image is saved from screen tmpsavept1 = tmpsavtype + 1 tmpsavept2 = tmpsavept1 + 1 tmphipoff = tmpsavept2 + 1 tmpscron = tmphipoff + 1 tmpcalcwid = tmpscron + 1 ; 2 bytes tmphipbuf = tmpcalcwid + 2 ; 80 byte buffer for saving HIP files RDBUF = tmphipbuf + 80 dithbuff: ; .RES 325 - there are 325 bytes here for dithering line buffer ; don't put the .RES in though, as that adds 325 bytes to ; executable file! Could add it to BSS segment, but that ; causes problems with detecting an overrun into screen area segcodeend: .org dithbuff + 325 ;; 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 code .addr initcode .addr initcodeend-1 ;; we only need to do this once at the start, so it is ok to ;; overwrite it 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 Init: lda #'D' sta lastfn lda #':' sta lastfn+1 lda #155 sta lastfn+2 sta lastfn+3 lda #0 sta coloff sta rowoff lda #15 ; mode 'E' (HIP 1/1) sta prevopt ; default display mode for >= 64K machines lda #3 ; HIP 1/1 sta prevsave ; default save option 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 that it is RAM bne @ISRAM @NTRAM: sty OSRTST ; restore just in case lda #4 ; mode '5' GR.9 2/1 sta prevopt ; set default display mode for 48K ldy #0 @ISRAM: sty OSRAM jmp OSRAMOFF initcodeend: .addr 738 .addr 739 .word initcode ; run init code as soon as it is loaded