simplify bmx loading

This commit is contained in:
Irmen de Jong 2023-11-29 21:44:04 +01:00
parent 5d9caef45f
commit afe521b0c9
3 changed files with 76 additions and 159 deletions

View File

@ -1,12 +1,9 @@
; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1. ; Routines to load and save "BMX" files (commander X16 bitmap format) Version 1.
; Only uncompressed images, of width 320 or 640, are supported for now. ; Only uncompressed images are supported for now.
; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945 ; BMX Specification: https://cx16forum.com/forum/viewtopic.php?t=6945
; TODO: refactor loading: bmx.open() - reads header . bmx.continue_load() - reads the palette+bitmap. bmx.close() -cleanup bmx.save() stays the same
; TODO: ability to save "stamps" , bitmaps that are only a region of the screen. ; TODO: ability to save "stamps" , bitmaps that are only a region of the screen.
; TODO: load 4bpp/2bpp/1bpp images ; TODO: ability to just load the palette from a BMX file
; TODO: save 4bpp/2bpp/1bpp images
%import diskio %import diskio
@ -20,52 +17,31 @@ bmx {
uword width uword width
uword height uword height
ubyte border ubyte border
ubyte palette_entries ; 0 means 256, all of them uword palette_entries ; 1-256
ubyte palette_start ubyte palette_start
ubyte compression ubyte compression
uword error_message ; pointer to error message, or 0 if all ok
uword max_width = 0 ; should you want load() to check for this
uword max_height = 0 ; should you want load() to check for this
uword palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram uword palette_buffer_ptr = 0 ; should you want to load or save the palette into main memory instead of directly into vram
sub load(ubyte drivenumber, str filename, ubyte vbank, uword vaddr) -> bool { uword error_message ; pointer to error message, or 0 if all ok
; Loads a BMX bitmap image and palette into vram. (and Header info into the bmx.* variables) ubyte old_drivenumber
; Parameters:
; the drive number and filename to load, sub open(ubyte drivenumber, str filename) -> bool {
; and the vram bank and address where the bitmap data should go, ; Open a BMX bitmap file and reads the header information.
; You can set the max_width and max_height variables first, if you want this routine to check those. ; Returns true if all is ok, false otherwise + error_message will be set.
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns: success status. If false, error_message points to the error message string.
error_message = 0 error_message = 0
ubyte old_drivenumber = diskio.drivenumber old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber diskio.drivenumber = drivenumber
if diskio.f_open(filename) { if diskio.f_open(filename) {
diskio.reset_read_channel() diskio.reset_read_channel()
if read_header() { if read_header() {
if parse_header() { if parse_header() {
if max_width and width>max_width { if palette_entries>0 {
error_message = "image too large" if width<=640 {
goto load_end return true
} } else
if max_height and height>max_height { error_message = "image too large"
error_message = "image too large"
goto load_end
}
if width!=320 and width!=640 {
; note: use load_stamp() to read other sizes
error_message = "width not 320 or 640"
goto load_end
}
if compression {
error_message = "compression not supported"
goto load_end
}
if read_palette() {
if not read_bitmap(vbank, vaddr)
error_message = "bitmap error"
} else } else
error_message = "palette error" error_message = "invalid bmx file"
} else } else
error_message = "invalid bmx file" error_message = "invalid bmx file"
} else } else
@ -73,110 +49,60 @@ bmx {
} else } else
error_message = diskio.status() error_message = diskio.status()
load_end: close()
return false
}
sub close() {
; if you want to close the file before actually loading palette or bitmap data.
diskio.f_close() diskio.f_close()
diskio.drivenumber = old_drivenumber diskio.drivenumber = old_drivenumber
}
sub continue_load(ubyte vbank, uword vaddr) -> bool {
; Continues loading the palette and bitmap data from the opened BMX file.
; Parameters: the vram bank and address where the bitmap data should go.
; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0
diskio.reset_read_channel()
if width==320 or width==640 {
if compression==0 {
if read_palette() {
if not read_bitmap(vbank, vaddr)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "compression not supported"
} else
error_message = "width not 320 or 640" ; note: use continue_load_stamp() to read other sizes
close()
return error_message==0 return error_message==0
} }
sub load_stamp(ubyte drivenumber, str filename, ubyte vbank, uword vaddr, uword screenwidth) -> bool { sub continue_load_stamp(ubyte vbank, uword vaddr, uword screenwidth) -> bool {
; Loads a BMX bitmap "stamp" image and palette into vram. (and Header info into the bmx.* variables) ; Continues loading the palette and bitmap "stamp" data from the opened BMX file.
; "Stamp" means: load an image that is smaller than the screen (so we need to pad around it) ; "Stamp" means: load an image that is smaller than the screen (so we need to pad around it)
; Parameters: ; Parameters:the vram bank and address where the bitmap data should go,
; the drive number and filename to load, ; and the screen width that the stamp image is loaded into.
; and the vram bank and address where the bitmap data should go, ; You can set palette_buffer_ptr if you want the palette buffered rather than directly into vram.
; finally the screen width that the stamp image is loaded into.
; You can set the max_width and max_height variables first, if you want this routine to check those.
; Note: does not change vera screen mode or colordepth! You have to do that yourself! ; Note: does not change vera screen mode or colordepth! You have to do that yourself!
; Returns: success status. If false, error_message points to the error message string. ; Returns true if all is ok, false otherwise + error_message will be set.
error_message = 0 error_message = 0
ubyte old_drivenumber = diskio.drivenumber diskio.reset_read_channel()
diskio.drivenumber = drivenumber if compression==0 {
if diskio.f_open(filename) { if read_palette() {
diskio.reset_read_channel() if not read_bitmap_padded(vbank, vaddr, screenwidth)
if read_header() { error_message = "bitmap error"
if parse_header() {
if max_width and width>max_width {
error_message = "image too large"
goto load_end
}
if max_height and height>max_height {
error_message = "image too large"
goto load_end
}
if width>screenwidth {
error_message = "image too large"
goto load_end
}
if compression {
error_message = "compression not supported"
goto load_end
}
if read_palette() {
if not read_bitmap_padded(vbank, vaddr, screenwidth)
error_message = "bitmap error"
} else
error_message = "palette error"
} else
error_message = "invalid bmx file"
} else } else
error_message = "invalid bmx file" error_message = "palette error"
} else } else
error_message = diskio.status() error_message = "compression not supported"
load_end: close()
diskio.f_close()
diskio.drivenumber = old_drivenumber
return error_message==0
}
sub load_header(ubyte drivenumber, str filename) -> bool {
; Loads just the header data from a BMX bitmap image into the bmx.* variables.
; Parameters: the drive number and filename to load.
; Returns: success status. If false, error_message points to the error message string.
error_message = 0
ubyte old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber
if diskio.f_open(filename) {
diskio.reset_read_channel()
if read_header() {
if not parse_header()
error_message = "invalid bmx file"
} else
error_message = "invalid bmx file"
} else
error_message = diskio.status()
load_end:
diskio.f_close()
diskio.drivenumber = old_drivenumber
return error_message==0
}
sub load_palette(ubyte drivenumber, str filename) -> bool {
; Loads just the palette from a BMX bitmap image into vram or the palette buffer.
; (and Header info into the bmx.* variables).
; Parameters: the drive number and filename to load.
; Returns: success status. If false, error_message points to the error message string.
error_message = 0
ubyte old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber
if diskio.f_open(filename) {
diskio.reset_read_channel()
if read_header() {
if parse_header() {
if not read_palette()
error_message = "palette error"
} else
error_message = "invalid bmx file"
} else
error_message = "invalid bmx file"
} else
error_message = diskio.status()
load_end:
diskio.f_close()
diskio.drivenumber = old_drivenumber
return error_message==0 return error_message==0
} }
@ -197,7 +123,7 @@ load_end:
error_message = "width not 320 or 640" error_message = "width not 320 or 640"
goto save_end goto save_end
} }
ubyte old_drivenumber = diskio.drivenumber old_drivenumber = diskio.drivenumber
diskio.drivenumber = drivenumber diskio.drivenumber = drivenumber
if diskio.f_open_w(filename) { if diskio.f_open_w(filename) {
cx16.r0 = diskio.status() cx16.r0 = diskio.status()
@ -223,13 +149,8 @@ save_end:
} }
sub set_bpp(ubyte bpp) { sub set_bpp(ubyte bpp) {
bitsperpixel = bpp ubyte[8] depths = [0,1,1,2,2,2,2,3]
vera_colordepth = 0 vera_colordepth = depths[bpp-1]
when bpp {
2 -> vera_colordepth = 1
4 -> vera_colordepth = 2
8 -> vera_colordepth = 3
}
} }
sub set_vera_colordepth(ubyte depth) { sub set_vera_colordepth(ubyte depth) {
@ -253,7 +174,7 @@ save_end:
; otherwise it is read directly into the palette in vram. ; otherwise it is read directly into the palette in vram.
cx16.vaddr(1, $fa00+palette_start*2, 0, 1) cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
cx16.r3 = palette_buffer_ptr cx16.r3 = palette_buffer_ptr
cx16.r2L = palette_entries cx16.r2L = lsb(palette_entries)
do { do {
cx16.r4L = cbm.CHRIN() cx16.r4L = cbm.CHRIN()
cx16.r4H = cbm.CHRIN() cx16.r4H = cbm.CHRIN()
@ -319,7 +240,7 @@ save_end:
; if palette_buffer_ptr is not 0, the palette data is read from that memory buffer, ; if palette_buffer_ptr is not 0, the palette data is read from that memory buffer,
; otherwise it is read directly from the palette in vram. ; otherwise it is read directly from the palette in vram.
cx16.r3 = palette_buffer_ptr cx16.r3 = palette_buffer_ptr
cx16.r2L = palette_entries cx16.r2L = lsb(palette_entries)
cx16.vaddr(1, $fa00+palette_start*2, 0, 1) cx16.vaddr(1, $fa00+palette_start*2, 0, 1)
do { do {
if cx16.r3 { if cx16.r3 {
@ -374,6 +295,8 @@ save_end:
width = peekw(&header+6) width = peekw(&header+6)
height = peekw(&header+8) height = peekw(&header+8)
palette_entries = header[10] palette_entries = header[10]
if palette_entries==0
palette_entries=256
palette_start = header[11] palette_start = header[11]
; the data offset is not needed: data_offset = peekw(&header+12) ; the data offset is not needed: data_offset = peekw(&header+12)
compression = header[14] compression = header[14]
@ -388,9 +311,7 @@ save_end:
; build the internal BMX header structure ; build the internal BMX header structure
; normally you don't have to call this yourself ; normally you don't have to call this yourself
sys.memset(header, sizeof(header), 0) sys.memset(header, sizeof(header), 0)
uword data_offset = 512 ; full palette of 256 entries uword data_offset = palette_entries*$0002
if palette_entries
data_offset = palette_entries*$0002
data_offset += sizeof(header) data_offset += sizeof(header)
header[0] = FILEID[0] header[0] = FILEID[0]
header[1] = FILEID[1] header[1] = FILEID[1]
@ -402,7 +323,7 @@ save_end:
header[7] = msb(width) header[7] = msb(width)
header[8] = lsb(height) header[8] = lsb(height)
header[9] = msb(height) header[9] = msb(height)
header[10] = palette_entries header[10] = lsb(palette_entries)
header[11] = palette_start header[11] = palette_start
header[12] = lsb(data_offset) header[12] = lsb(data_offset)
header[13] = msb(data_offset) header[13] = msb(data_offset)

View File

@ -138,9 +138,9 @@ asmsub FREADUY (ubyte value @Y) {
asmsub parse_f(str value @AY) -> float @FAC1 { asmsub parse_f(str value @AY) -> float @FAC1 {
; -- parse a string value of a number to float in FAC1 ; -- parse a string value of a number to float in FAC1
; warning: uses an internal BASIC routine that may be rom version dependent ; warning: on older <R47 kernals it uses an internal BASIC routine that is ROM version dependent,
; ($deb6 is inside the routine for VAL at $deb3) See basic.sym from x16-rom ; ($deb6 is inside the routine for VAL at $deb3) See basic.sym from x16-rom
; If at any time in the future the official VAL_1() routine from the kernal starts working, we should use that here! ; TODO add a check to see if the VAL_1 kernal jump entry is valid if so, then use that instead
%asm {{ %asm {{
sta $a9 sta $a9
sty $aa sty $aa

View File

@ -1,8 +1,4 @@
; Viewer program for BMX image files. ; Viewer program for BMX image files.
; This program shows *one* way to do it, by checking the header upfront,
; and loading the palette into system ram first. The simplest way to load
; a BMX file is to just read everything into vram directly using a single bmx.load() call.
;
; BMX file format: see https://cx16forum.com/forum/viewtopic.php?t=6945 ; BMX file format: see https://cx16forum.com/forum/viewtopic.php?t=6945
%import textio %import textio
@ -19,13 +15,15 @@ main {
txt.print("\nenter bmx image filename: ") txt.print("\nenter bmx image filename: ")
if txt.input_chars(&filename) { if txt.input_chars(&filename) {
if bmx.load_header(8, filename) { if bmx.open(8, filename) {
txt.print("\nsize: ") txt.print("\nsize: ")
txt.print_uw(bmx.width) txt.print_uw(bmx.width)
txt.chrout('*') txt.chrout('*')
txt.print_uw(bmx.height) txt.print_uw(bmx.height)
txt.print(" bpp: ") txt.print(" bpp: ")
txt.print_uw(bmx.bitsperpixel) txt.print_uw(bmx.bitsperpixel)
txt.print(" num colors: ")
txt.print_uw(bmx.palette_entries)
txt.nl() txt.nl()
sys.wait(100) sys.wait(100)
@ -35,8 +33,6 @@ main {
bmx.palette_buffer_ptr = memory("palette", 512, 0) bmx.palette_buffer_ptr = memory("palette", 512, 0)
sys.memset(bmx.palette_buffer_ptr, 512, 0) sys.memset(bmx.palette_buffer_ptr, 512, 0)
palette.set_rgb(bmx.palette_buffer_ptr, 256) palette.set_rgb(bmx.palette_buffer_ptr, 256)
bmx.max_width = 320
bmx.max_height = 240
; switch to bitmap screen mode and color depth: 320*240 ; switch to bitmap screen mode and color depth: 320*240
void cx16.screen_mode($80, false) ; we're lazy and just use a kernal routine to set up the basics void cx16.screen_mode($80, false) ; we're lazy and just use a kernal routine to set up the basics
@ -45,7 +41,7 @@ main {
; now load the image ; now load the image
if bmx.width==320 { if bmx.width==320 {
; can use the fast, full-screen load routine ; can use the fast, full-screen load routine
if bmx.load(8, filename, 0, 0) { if bmx.continue_load(0, 0) {
if bmx.height<240 { if bmx.height<240 {
; fill the remaining bottom part of the screen ; fill the remaining bottom part of the screen
cx16.GRAPH_set_colors(bmx.border, bmx.border, 99) cx16.GRAPH_set_colors(bmx.border, bmx.border, 99)
@ -61,7 +57,7 @@ main {
; need to use the slower load routine that does padding ; need to use the slower load routine that does padding
; center the image on the screen nicely ; center the image on the screen nicely
uword offset = (320-bmx.width)/2 + (240-bmx.height)/2*320 uword offset = (320-bmx.width)/2 + (240-bmx.height)/2*320
if bmx.load_stamp(8, filename, 0, offset, 320) { if bmx.continue_load_stamp(0, offset, 320) {
activate_palette() activate_palette()
void txt.waitkey() void txt.waitkey()
} }
@ -85,7 +81,7 @@ main {
cx16.VERA_DC_BORDER = bmx.border cx16.VERA_DC_BORDER = bmx.border
cx16.r1 = bmx.palette_buffer_ptr cx16.r1 = bmx.palette_buffer_ptr
cx16.r2L = bmx.palette_start cx16.r2L = bmx.palette_start
cx16.r3L = bmx.palette_entries cx16.r3L = lsb(bmx.palette_entries)
do { do {
palette.set_color(cx16.r2L, peekw(cx16.r1)) palette.set_color(cx16.r2L, peekw(cx16.r1))
cx16.r1+=2 cx16.r1+=2