mirror of
https://github.com/irmen/prog8.git
synced 2025-01-03 06:29:54 +00:00
added examples/cx16/balloonflight.p8
This commit is contained in:
parent
01a38a0b11
commit
03412cacba
@ -116,6 +116,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
"amiga",
|
||||
"audioroutines",
|
||||
"automatons",
|
||||
"balloonflight",
|
||||
"bdmusic",
|
||||
"bobs",
|
||||
"bubbleuniverse",
|
||||
|
@ -11,7 +11,7 @@ Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly)
|
||||
- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does.
|
||||
- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy.
|
||||
- should we add a cleararray builtin function that can efficiently set every element in the array to the given value
|
||||
|
||||
- improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance)
|
||||
|
@ -1,8 +1,9 @@
|
||||
%import syslib
|
||||
%import textio
|
||||
%import math
|
||||
%zeropage basicsafe
|
||||
|
||||
; C64 version of a balloon sprites flying over a mountain landscape.
|
||||
; There is also a X16 version of this in the examples.
|
||||
|
||||
main {
|
||||
|
||||
@ -37,11 +38,16 @@ main {
|
||||
upwards = false
|
||||
} else {
|
||||
; determine new height for next mountain
|
||||
target_height = 9 + math.rnd() % 15
|
||||
if upwards
|
||||
ubyte old_height = target_height
|
||||
if upwards {
|
||||
mountain = 233
|
||||
else
|
||||
while target_height >= old_height
|
||||
target_height = 9 + math.rnd() % 15
|
||||
} else {
|
||||
mountain = 223
|
||||
while target_height <= old_height
|
||||
target_height = 9 + math.rnd() % 15
|
||||
}
|
||||
}
|
||||
|
||||
while not do_char_scroll {
|
||||
|
252
examples/cx16/balloonflight.p8
Normal file
252
examples/cx16/balloonflight.p8
Normal file
@ -0,0 +1,252 @@
|
||||
%import syslib
|
||||
%import textio
|
||||
%import sprites
|
||||
%import palette
|
||||
%import math
|
||||
|
||||
; X16 version of a balloon sprites flying over a mountain landscape.
|
||||
; There is also a C64 version of this in the examples.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
ubyte target_height = txt.DEFAULT_HEIGHT - 10
|
||||
ubyte active_height = txt.DEFAULT_HEIGHT
|
||||
bool upwards = true
|
||||
ubyte draw_column = txt.DEFAULT_WIDTH
|
||||
word moon_x = 640
|
||||
word balloon_y = 120
|
||||
|
||||
; clear the screen (including all the tiles outside of the visible area)
|
||||
cx16.vaddr(txt.VERA_TEXTMATRIX_BANK, txt.VERA_TEXTMATRIX_ADDR, 0, 1)
|
||||
repeat 128 * txt.DEFAULT_HEIGHT {
|
||||
cx16.VERA_DATA0 = sc:' '
|
||||
cx16.VERA_DATA0 = $00
|
||||
}
|
||||
|
||||
; activate balloon and moon sprites
|
||||
sprites.init(1, 0, $0000, sprites.SIZE_32, sprites.SIZE_64, sprites.COLORS_16, 1)
|
||||
sprites.init(2, 0, $0400, sprites.SIZE_32, sprites.SIZE_32, sprites.COLORS_16, 2)
|
||||
spritedata.copy_to_vram()
|
||||
|
||||
; Scroll!
|
||||
; Unlike the C64 version, there is no need to copy the whole text matrix 1 character to the left
|
||||
; every 8 pixels. The X16 has a much larger soft scroll register and the displayed tiles wrap around.
|
||||
|
||||
repeat {
|
||||
sys.waitvsync()
|
||||
cx16.VERA_L1_HSCROLL ++
|
||||
|
||||
if cx16.VERA_L1_HSCROLL & 7 == 0 {
|
||||
|
||||
; set balloon pos
|
||||
if math.rnd() & 1 != 0
|
||||
balloon_y++
|
||||
else
|
||||
balloon_y--
|
||||
sprites.pos(1, 100, balloon_y)
|
||||
|
||||
; set moon pos
|
||||
moon_x--
|
||||
if moon_x < -64
|
||||
moon_x = 640
|
||||
sprites.pos(2, moon_x, 20)
|
||||
|
||||
; update slope height
|
||||
ubyte mountain = 223 ; slope upwards
|
||||
if active_height < target_height {
|
||||
active_height++
|
||||
upwards = true
|
||||
} else if active_height > target_height {
|
||||
mountain = 233 ; slope downwards
|
||||
active_height--
|
||||
upwards = false
|
||||
} else {
|
||||
; determine new height for next mountain
|
||||
ubyte old_height = target_height
|
||||
if upwards {
|
||||
mountain = 233
|
||||
while target_height >= old_height
|
||||
target_height = 28 + (math.rnd() & 31)
|
||||
} else {
|
||||
mountain = 223
|
||||
while target_height <= old_height
|
||||
target_height = 28 + (math.rnd() & 31)
|
||||
}
|
||||
}
|
||||
|
||||
; draw new mountain etc.
|
||||
draw_column++
|
||||
ubyte yy
|
||||
for yy in 0 to active_height-1 {
|
||||
txt.setcc(draw_column, yy, 32, 2) ; clear top of screen
|
||||
}
|
||||
txt.setcc(draw_column, active_height, mountain, 8) ; mountain edge
|
||||
for yy in active_height+1 to txt.DEFAULT_HEIGHT-1 {
|
||||
txt.setcc(draw_column, yy, 160, 8) ; draw filled mountain
|
||||
}
|
||||
|
||||
ubyte clutter = math.rnd()
|
||||
if clutter > 100 {
|
||||
; draw a star
|
||||
txt.setcc(draw_column, clutter % (active_height-1), sc:'.', math.rnd())
|
||||
}
|
||||
|
||||
if clutter > 200 {
|
||||
; draw a tree
|
||||
ubyte tree = sc:'↑'
|
||||
ubyte treecolor = 5
|
||||
if clutter & %00010000 != 0
|
||||
tree = sc:'♣'
|
||||
else if clutter & %00100000 != 0
|
||||
tree = sc:'♠'
|
||||
if math.rnd() > 130
|
||||
treecolor = 13
|
||||
txt.setcc(draw_column, active_height, tree, treecolor)
|
||||
}
|
||||
|
||||
if clutter > 235 {
|
||||
; draw a camel
|
||||
txt.setcc(draw_column, active_height, sc:'π', 9)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spritedata {
|
||||
sub copy_to_vram() {
|
||||
cx16.vaddr(0, $0000, 0, 1)
|
||||
for cx16.r0 in 0 to 32*64/2-1
|
||||
cx16.VERA_DATA0 = @(&balloonsprite + cx16.r0)
|
||||
|
||||
cx16.vaddr(0, $0400, 0, 1)
|
||||
for cx16.r0 in 0 to 32*32/2-1
|
||||
cx16.VERA_DATA0 = @(&moonsprite + cx16.r0)
|
||||
|
||||
for cx16.r1L in 0 to 15 {
|
||||
palette.set_color(cx16.r1L + 16, balloon_pallette[cx16.r1L])
|
||||
palette.set_color(cx16.r1L + 32, moon_pallette[cx16.r1L])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uword[] balloon_pallette = [
|
||||
$f0f, $312, $603, $125,
|
||||
$717, $721, $332, $a22,
|
||||
$268, $d31, $764, $488,
|
||||
$d71, $997, $ba7, $eb3
|
||||
]
|
||||
|
||||
uword[] moon_pallette = [
|
||||
$f0f, $444, $444, $555,
|
||||
$555, $555, $555, $666,
|
||||
$777, $777, $888, $888,
|
||||
$999, $aaa, $bbb, $ccc
|
||||
]
|
||||
|
||||
balloonsprite:
|
||||
%asm {{
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $60, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $99, $92, $55, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $05, $ac, $fc, $cf, $c9, $95, $e3, $25, $11, $40, $00, $00, $00
|
||||
.byte $00, $00, $08, $3c, $ff, $cf, $ff, $c9, $9c, $c8, $37, $21, $33, $00, $00, $00
|
||||
.byte $00, $00, $6b, $af, $ec, $ff, $ff, $9c, $9c, $fa, $33, $97, $23, $30, $00, $00
|
||||
.byte $00, $08, $bd, $ef, $fc, $ff, $fc, $99, $99, $fc, $33, $27, $72, $33, $30, $00
|
||||
.byte $00, $0b, $be, $ff, $cf, $ef, $fc, $9c, $99, $cf, $23, $32, $77, $13, $30, $00
|
||||
.byte $00, $6b, $ee, $ff, $9f, $ef, $fc, $c9, $97, $cf, $d3, $32, $29, $73, $31, $00
|
||||
.byte $00, $f8, $fe, $fc, $ff, $ef, $f9, $9c, $99, $ff, $d8, $31, $27, $72, $33, $00
|
||||
.byte $3b, $fd, $ee, $fc, $ff, $ef, $f9, $9c, $99, $fe, $d3, $32, $24, $72, $33, $40
|
||||
.byte $8b, $bd, $fe, $fc, $ff, $ef, $c7, $c7, $97, $ce, $d8, $33, $22, $72, $33, $30
|
||||
.byte $b8, $bd, $ee, $d7, $db, $b8, $2d, $88, $32, $2b, $83, $33, $33, $33, $33, $10
|
||||
.byte $18, $b8, $bb, $b8, $be, $bb, $3b, $b8, $38, $bb, $b8, $83, $33, $33, $33, $34
|
||||
.byte $1b, $8b, $bb, $8b, $eb, $83, $3b, $b8, $33, $bb, $88, $33, $33, $33, $33, $33
|
||||
.byte $8b, $b8, $8b, $88, $be, $88, $38, $88, $38, $8b, $b8, $33, $33, $33, $33, $33
|
||||
.byte $8b, $88, $88, $8b, $b8, $83, $38, $88, $38, $8b, $88, $33, $33, $33, $33, $33
|
||||
.byte $88, $83, $88, $88, $88, $83, $38, $83, $38, $8b, $83, $33, $33, $33, $33, $33
|
||||
.byte $3b, $b8, $88, $88, $bb, $83, $38, $83, $38, $bb, $83, $33, $33, $33, $33, $33
|
||||
.byte $6b, $bb, $88, $33, $bb, $b3, $38, $33, $33, $8b, $83, $33, $33, $33, $33, $30
|
||||
.byte $1b, $b3, $73, $37, $78, $83, $27, $92, $42, $75, $83, $11, $33, $33, $33, $30
|
||||
.byte $0c, $ff, $79, $97, $9f, $f9, $79, $92, $42, $79, $72, $12, $22, $11, $13, $10
|
||||
.byte $04, $9c, $c9, $97, $9f, $fc, $79, $97, $44, $c7, $72, $12, $12, $12, $22, $00
|
||||
.byte $00, $9c, $c9, $99, $59, $c9, $97, $92, $42, $7c, $21, $21, $22, $12, $21, $00
|
||||
.byte $00, $59, $cc, $99, $59, $9c, $79, $97, $22, $75, $21, $41, $22, $14, $10, $00
|
||||
.byte $00, $19, $cc, $99, $99, $9c, $97, $94, $22, $97, $21, $12, $11, $22, $10, $00
|
||||
.byte $00, $01, $cc, $c9, $95, $99, $97, $77, $47, $92, $11, $22, $12, $21, $00, $00
|
||||
.byte $00, $00, $79, $cc, $97, $7c, $c7, $99, $47, $72, $22, $11, $12, $14, $00, $00
|
||||
.byte $00, $00, $07, $cc, $99, $7c, $fc, $99, $42, $91, $14, $21, $22, $00, $00, $00
|
||||
.byte $00, $00, $01, $7c, $c9, $97, $cc, $79, $27, $72, $21, $12, $10, $00, $00, $00
|
||||
.byte $00, $00, $00, $1a, $c5, $95, $fc, $77, $44, $22, $21, $21, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $04, $a7, $5c, $fe, $f7, $9c, $55, $52, $20, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $01, $57, $fe, $f9, $9c, $c5, $21, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $01, $5e, $c9, $95, $11, $10, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $12, $ff, $f2, $10, $00, $60, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $01, $ff, $f1, $10, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $01, $12, $11, $00, $0a, $40, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $06, $11, $11, $00, $06, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $15, $15, $10, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $5e, $ee, $ee, $e5, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $01, $7d, $de, $dd, $d2, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $cc, $55, $55, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $cc, $cc, $55, $51, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $cc, $cc, $55, $55, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $5c, $cc, $51, $51, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $cc, $c5, $56, $54, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $05, $55, $55, $15, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
|
||||
}}
|
||||
|
||||
moonsprite:
|
||||
%asm {{
|
||||
.byte $00, $00, $00, $00, $00, $00, $78, $88, $87, $50, $00, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $ba, $99, $9a, $a9, $88, $76, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $b9, $99, $77, $67, $79, $78, $89, $93, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $76, $69, $ca, $bc, $3b, $99, $78, $66, $99, $60, $00, $00, $00
|
||||
.byte $00, $00, $04, $46, $cb, $64, $67, $77, $cb, $bb, $97, $99, $96, $00, $00, $00
|
||||
.byte $00, $00, $62, $39, $93, $33, $36, $67, $ac, $cc, $79, $99, $98, $60, $00, $00
|
||||
.byte $00, $06, $23, $47, $63, $43, $46, $79, $9b, $c6, $97, $96, $aa, $86, $00, $00
|
||||
.byte $00, $06, $34, $36, $63, $43, $67, $37, $a9, $64, $46, $87, $ba, $98, $30, $00
|
||||
.byte $00, $62, $46, $47, $76, $63, $66, $b7, $c9, $73, $46, $3a, $bc, $c9, $60, $00
|
||||
.byte $00, $13, $39, $73, $33, $74, $36, $99, $b9, $34, $43, $67, $cb, $87, $44, $00
|
||||
.byte $06, $43, $36, $37, $96, $76, $76, $3b, $d7, $34, $63, $48, $9a, $b9, $22, $00
|
||||
.byte $03, $22, $63, $83, $79, $78, $99, $9a, $77, $77, $96, $33, $62, $9c, $22, $10
|
||||
.byte $a3, $32, $36, $77, $bb, $ca, $a7, $63, $63, $97, $92, $33, $22, $9c, $22, $20
|
||||
.byte $a6, $32, $37, $a7, $7b, $eb, $a7, $79, $b7, $7c, $93, $23, $22, $38, $86, $10
|
||||
.byte $cb, $33, $37, $a7, $9b, $99, $33, $7a, $9d, $dc, $93, $33, $23, $36, $67, $10
|
||||
.byte $cb, $23, $33, $77, $79, $99, $67, $76, $8e, $ee, $b7, $33, $63, $43, $22, $10
|
||||
.byte $99, $33, $23, $36, $37, $39, $79, $ca, $bd, $dd, $dd, $a4, $a9, $62, $22, $20
|
||||
.byte $b6, $72, $73, $37, $a7, $99, $7d, $ce, $de, $dd, $df, $97, $ca, $42, $22, $20
|
||||
.byte $bb, $93, $33, $33, $b3, $6b, $37, $d9, $dd, $ed, $df, $c9, $bc, $92, $26, $30
|
||||
.byte $0d, $d7, $39, $33, $73, $77, $77, $9b, $cd, $de, $ed, $ec, $96, $aa, $44, $10
|
||||
.byte $0c, $cb, $9a, $b3, $39, $67, $76, $bc, $dd, $ee, $dd, $fb, $76, $77, $32, $00
|
||||
.byte $0b, $dc, $cb, $33, $ca, $a7, $83, $dd, $cd, $dd, $dd, $dd, $a8, $a7, $a3, $00
|
||||
.byte $00, $de, $dc, $73, $76, $73, $37, $ac, $ed, $ed, $dd, $ec, $bc, $b9, $63, $00
|
||||
.byte $00, $bd, $ed, $b7, $b9, $c7, $99, $ee, $fd, $ec, $de, $ed, $cc, $cb, $70, $00
|
||||
.byte $00, $0c, $ce, $d9, $c7, $9e, $ef, $ff, $ff, $ed, $ed, $db, $ca, $db, $30, $00
|
||||
.byte $00, $0b, $9c, $c7, $cd, $de, $ef, $ff, $fe, $ed, $cc, $bc, $ba, $a7, $00, $00
|
||||
.byte $00, $00, $bd, $9d, $dd, $de, $ff, $ff, $fe, $ee, $ec, $cc, $c9, $60, $00, $00
|
||||
.byte $00, $00, $0b, $ca, $ac, $ee, $ff, $ff, $fe, $cc, $cd, $cd, $96, $00, $00, $00
|
||||
.byte $00, $00, $00, $ba, $9d, $cc, $fe, $ff, $fd, $cc, $cc, $97, $60, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $9c, $da, $dd, $dc, $cd, $db, $a9, $83, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $08, $aa, $bd, $dc, $ac, $a9, $96, $00, $00, $00, $00, $00
|
||||
.byte $00, $00, $00, $00, $00, $00, $59, $7b, $99, $60, $00, $00, $00, $00, $00, $00
|
||||
}}
|
||||
}
|
298
scripts/cx16images.py
Normal file
298
scripts/cx16images.py
Normal file
@ -0,0 +1,298 @@
|
||||
"""
|
||||
Tools to convert bitmap images to an appropriate format for the Commander X16.
|
||||
This means: indexed colors (palette), 12 bits color space (4 bits per channel, for a total of 4096 possible colors)
|
||||
There are no restrictions on the size of the image.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - Code is in the Public Domain.
|
||||
|
||||
Requirements: Pillow (pip install pillow)
|
||||
"""
|
||||
|
||||
from PIL import Image, PyAccess
|
||||
from typing import TypeAlias
|
||||
|
||||
RGBList: TypeAlias = list[tuple[int, int, int]]
|
||||
|
||||
# the 256 default colors of the Commander X16's color palette in (r,g,b) format
|
||||
default_colors = []
|
||||
|
||||
_colors="""000,fff,800,afe,c4c,0c5,00a,ee7,d85,640,f77,333,777,af6,08f,bbb
|
||||
000,111,222,333,444,555,666,777,888,999,aaa,bbb,ccc,ddd,eee,fff
|
||||
211,433,644,866,a88,c99,fbb,211,422,633,844,a55,c66,f77,200,411
|
||||
611,822,a22,c33,f33,200,400,600,800,a00,c00,f00,221,443,664,886
|
||||
aa8,cc9,feb,211,432,653,874,a95,cb6,fd7,210,431,651,862,a82,ca3
|
||||
fc3,210,430,640,860,a80,c90,fb0,121,343,564,786,9a8,bc9,dfb,121
|
||||
342,463,684,8a5,9c6,bf7,120,241,461,582,6a2,8c3,9f3,120,240,360
|
||||
480,5a0,6c0,7f0,121,343,465,686,8a8,9ca,bfc,121,242,364,485,5a6
|
||||
6c8,7f9,020,141,162,283,2a4,3c5,3f6,020,041,061,082,0a2,0c3,0f3
|
||||
122,344,466,688,8aa,9cc,bff,122,244,366,488,5aa,6cc,7ff,022,144
|
||||
166,288,2aa,3cc,3ff,022,044,066,088,0aa,0cc,0ff,112,334,456,668
|
||||
88a,9ac,bcf,112,224,346,458,56a,68c,79f,002,114,126,238,24a,35c
|
||||
36f,002,014,016,028,02a,03c,03f,112,334,546,768,98a,b9c,dbf,112
|
||||
324,436,648,85a,96c,b7f,102,214,416,528,62a,83c,93f,102,204,306
|
||||
408,50a,60c,70f,212,434,646,868,a8a,c9c,fbe,211,423,635,847,a59
|
||||
c6b,f7d,201,413,615,826,a28,c3a,f3c,201,403,604,806,a08,c09,f0b"""
|
||||
|
||||
for line in _colors.splitlines():
|
||||
for rgb in line.split(","):
|
||||
r = int(rgb[0], 16)
|
||||
g = int(rgb[1], 16)
|
||||
b = int(rgb[2], 16)
|
||||
default_colors.append((r, g, b))
|
||||
|
||||
|
||||
class BitmapImage:
|
||||
def __init__(self, filename: str, image: Image = None) -> None:
|
||||
"""Just load the given bitmap image file (any format allowed)."""
|
||||
if image is not None:
|
||||
self.img = image
|
||||
else:
|
||||
self.img = Image.open(filename)
|
||||
self.size = self.img.size
|
||||
self.width, self.height = self.size
|
||||
|
||||
def save(self, filename: str) -> None:
|
||||
"""Save the image to a new file, format based on the file extension."""
|
||||
self.img.save(filename)
|
||||
|
||||
def get_image(self) -> Image:
|
||||
"""Gets access to a copy of the Pillow Image class that holds the loaded image"""
|
||||
return self.img.copy()
|
||||
|
||||
def crop(self, x, y, width, height) -> "BitmapImage":
|
||||
"""Returns a rectangle cropped from the original image"""
|
||||
cropped = self.img.crop((x, y, x + width, y + height))
|
||||
return BitmapImage("", cropped)
|
||||
|
||||
def has_palette(self) -> bool:
|
||||
"""Is it an indexed colors image?"""
|
||||
return self.img.mode == "P"
|
||||
|
||||
def get_palette(self) -> RGBList:
|
||||
"""Return the image's palette as a list of (r,g,b) tuples"""
|
||||
return flat_palette_to_rgb(self.img.getpalette())
|
||||
|
||||
def get_vera_palette(self) -> bytes:
|
||||
"""
|
||||
Returns the image's palette as GB0R words (RGB in little-endian), suitable for the Vera palette registers.
|
||||
The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value.
|
||||
"""
|
||||
return rgb_palette_to_vera(self.get_palette())
|
||||
|
||||
def show(self) -> None:
|
||||
"""Shows the image on the screen"""
|
||||
if self.img.mode == "P":
|
||||
self.img.convert("RGB").convert("P").show()
|
||||
else:
|
||||
self.img.show()
|
||||
|
||||
def get_pixels_8bpp(self, x: int, y: int, width: int, height: int) -> bytearray:
|
||||
"""
|
||||
For 8 bpp (256 color) images:
|
||||
Get a rectangle of pixel values from the image, returns the bytes as a flat array
|
||||
"""
|
||||
assert self.has_palette()
|
||||
try:
|
||||
access = PyAccess.new(self.img, readonly=True)
|
||||
except AttributeError:
|
||||
access = self.img
|
||||
data = bytearray(width * height)
|
||||
index = 0
|
||||
for py in range(y, y + height):
|
||||
for px in range(x, x + width):
|
||||
data[index] = access.getpixel((px, py))
|
||||
index += 1
|
||||
return data
|
||||
|
||||
def get_all_pixels_8bpp(self) -> bytes:
|
||||
"""
|
||||
For 8 bpp (256 color) images:
|
||||
Get all pixel values from the image, returns the bytes as a flat array
|
||||
"""
|
||||
assert self.has_palette()
|
||||
return self.img.tobytes()
|
||||
# try:
|
||||
# access = PyAccess.new(self.img, readonly=True)
|
||||
# except AttributeError:
|
||||
# access = self.img
|
||||
# data = bytearray(self.width * self.height)
|
||||
# index = 0
|
||||
# for py in range(self.height):
|
||||
# for px in range(self.width):
|
||||
# data[index] = access.getpixel((px, py))
|
||||
# index += 1
|
||||
# return data
|
||||
|
||||
def get_pixels_4bpp(self, x: int, y: int, width: int, height: int) -> bytearray:
|
||||
"""
|
||||
For 4 bpp (16 color) images:
|
||||
Get a rectangle of pixel values from the image, returns the bytes as a flat array.
|
||||
Every byte encodes 2 pixels (4+4 bits).
|
||||
"""
|
||||
assert self.has_palette()
|
||||
try:
|
||||
access = PyAccess.new(self.img, readonly=True)
|
||||
except AttributeError:
|
||||
access = self.img
|
||||
data = bytearray(width // 2 * height)
|
||||
index = 0
|
||||
for py in range(y, y + height):
|
||||
for px in range(x, x + width, 2):
|
||||
pix1 = access.getpixel((px, py))
|
||||
pix2 = access.getpixel((px + 1, py))
|
||||
data[index] = pix1 << 4 | pix2
|
||||
index += 1
|
||||
return data
|
||||
|
||||
def get_all_pixels_4bpp(self) -> bytearray:
|
||||
"""
|
||||
For 4 bpp (16 color) images:
|
||||
Get all pixel values from the image, returns the bytes as a flat array.
|
||||
Every byte encodes 2 pixels (4+4 bits).
|
||||
"""
|
||||
assert self.has_palette()
|
||||
try:
|
||||
access = PyAccess.new(self.img, readonly=True)
|
||||
except AttributeError:
|
||||
access = self.img
|
||||
data = bytearray(self.width // 2 * self.height)
|
||||
index = 0
|
||||
for py in range(self.height):
|
||||
for px in range(0, self.width, 2):
|
||||
pix1 = access.getpixel((px, py))
|
||||
pix2 = access.getpixel((px + 1, py))
|
||||
data[index] = pix1 << 4 | pix2
|
||||
index += 1
|
||||
return data
|
||||
|
||||
def quantize_to(self, palette_rgb12: RGBList, dither: Image.Dither = Image.Dither.FLOYDSTEINBERG) -> None:
|
||||
"""
|
||||
Convert the image to one with the supplied palette.
|
||||
This palette must be in 12 bits colorspace (4 bits so 0-15 per channel)
|
||||
The resulting image will have its palette extended to 8 bits per channel again.
|
||||
If you want to display the image on the actual Commander X16, simply take the lower (or upper) 4 bits of every color channel.
|
||||
Dithering is applied as given (default is Floyd-Steinberg).
|
||||
"""
|
||||
palette_image = Image.new("P", (1, 1))
|
||||
palette = []
|
||||
for r, g, b in palette_rgb12:
|
||||
palette.append(r << 4 | r)
|
||||
palette.append(g << 4 | g)
|
||||
palette.append(b << 4 | b)
|
||||
palette_image.putpalette(palette)
|
||||
self.img = self.img.quantize(dither=dither, palette=palette_image)
|
||||
|
||||
def quantize(self, bits_per_pixel: int, preserve_first_16_colors: bool,
|
||||
dither: Image.Dither = Image.Dither.FLOYDSTEINBERG) -> None:
|
||||
"""
|
||||
Convert the image to one with indexed colors (12 bits colorspace palette extended back into 8 bits per channel).
|
||||
If you want to display the image on the actual Commander X16, simply take the lower (or upper) 4 bits of every color channel.
|
||||
There is support for either 8 or 4 bits per pixel (256 or 16 color modes).
|
||||
Dithering is applied as given (default is Floyd-Steinberg).
|
||||
"""
|
||||
if bits_per_pixel == 8:
|
||||
num_colors = 240 if preserve_first_16_colors else 256
|
||||
elif bits_per_pixel == 4:
|
||||
num_colors = 16
|
||||
if preserve_first_16_colors:
|
||||
return self.quantize_to(default_colors[:16])
|
||||
elif bits_per_pixel == 2:
|
||||
assert preserve_first_16_colors==False, "bpp is too small for 16 default colors"
|
||||
num_colors = 4
|
||||
elif bits_per_pixel == 1:
|
||||
assert preserve_first_16_colors==False, "bpp is too small for 16 default colors"
|
||||
num_colors = 2
|
||||
else:
|
||||
raise ValueError("only 8,4,2,1 bpp supported")
|
||||
image = self.img.convert("RGB")
|
||||
palette_image = image.quantize(colors=num_colors, dither=Image.Dither.NONE, method=Image.Quantize.MAXCOVERAGE)
|
||||
if len(palette_image.getpalette()) // 3 > num_colors:
|
||||
palette_image = image.quantize(colors=num_colors - 1, dither=Image.Dither.NONE, method=Image.Quantize.MAXCOVERAGE)
|
||||
palette_rgb = flat_palette_to_rgb(palette_image.getpalette())
|
||||
palette_rgb = list(reversed(sorted(set(palette_8to4(palette_rgb)))))
|
||||
if preserve_first_16_colors:
|
||||
palette_rgb = default_colors[:16] + palette_rgb
|
||||
self.img = image
|
||||
self.quantize_to(palette_rgb, dither)
|
||||
|
||||
def constrain_size(self, hires: bool = False) -> None:
|
||||
"""
|
||||
If the image is larger than the lores or hires screen size, scale it down so that it fits.
|
||||
If the image already fits, doesn't do anything.
|
||||
"""
|
||||
w, h = self.img.size
|
||||
if hires and (w > 640 or h > 480):
|
||||
self.img.thumbnail((640, 480))
|
||||
elif w > 320 or h > 240:
|
||||
self.img.thumbnail((320, 240))
|
||||
self.size = self.img.size
|
||||
self.width, self.height = self.size
|
||||
|
||||
|
||||
# utility functions
|
||||
|
||||
def channel_8to4(color: int) -> int:
|
||||
"""Accurate conversion of a single 8 bit color channel value to 4 bits"""
|
||||
return (color * 15 + 135) >> 8 # see https://threadlocalmutex.com/?p=48
|
||||
|
||||
|
||||
def palette_8to4(palette_rgb: RGBList) -> RGBList:
|
||||
"""Accurate conversion of a 24 bits palette (8 bits per channel) to a 12 bits palette (4 bits per channel)"""
|
||||
converted = []
|
||||
for ci in range(len(palette_rgb)):
|
||||
r, g, b = palette_rgb[ci]
|
||||
converted.append((channel_8to4(r), channel_8to4(g), channel_8to4(b)))
|
||||
return converted
|
||||
|
||||
|
||||
def reduce_colorspace(palette_rgb: RGBList) -> RGBList:
|
||||
"""
|
||||
Convert 24 bits color space (8 bits per channel) to 12 bits color space (4 bits per channel).
|
||||
The resulting color values are still full 8 bits but their precision is reduced.
|
||||
You can take either the upper or lower 4 bits of each channel byte to get the actual 4 bits precision.
|
||||
"""
|
||||
converted = []
|
||||
for r, g, b in palette_rgb:
|
||||
r = channel_8to4(r)
|
||||
g = channel_8to4(g)
|
||||
b = channel_8to4(b)
|
||||
converted.append((r << 4 | r, g << 4 | g, b << 4 | b))
|
||||
return converted
|
||||
|
||||
|
||||
def flat_palette_to_rgb(palette: list[int]) -> RGBList:
|
||||
"""Converts the flat palette list usually obtained from Pillow images to a list of (r,g,b) tuples"""
|
||||
return [(palette[i], palette[i + 1], palette[i + 2]) for i in range(0, len(palette), 3)]
|
||||
|
||||
|
||||
def rgb_palette_to_flat(palette: RGBList) -> list[int]:
|
||||
"""Convert a palette of (r,g,b) tuples to a flat list that is usually used by Pillow images"""
|
||||
result = []
|
||||
for r, g, b in palette:
|
||||
result.append(r)
|
||||
result.append(g)
|
||||
result.append(b)
|
||||
return result
|
||||
|
||||
|
||||
def flat_palette_to_vera(palette: list[int]) -> bytearray:
|
||||
"""
|
||||
Convert a flat palette list usually obtained from Pillow images, to GB0R words (RGB in little-endian), suitable for Vera palette registers.
|
||||
The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value.
|
||||
"""
|
||||
return rgb_palette_to_vera(flat_palette_to_rgb(palette))
|
||||
|
||||
|
||||
def rgb_palette_to_vera(palette_rgb: RGBList) -> bytearray:
|
||||
"""
|
||||
Convert a palette in (r,g,b) format to GB0R words (RGB in little-endian), suitable for Vera palette registers.
|
||||
The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value.
|
||||
"""
|
||||
data = bytearray()
|
||||
for r, g, b in palette_rgb:
|
||||
r = r >> 4
|
||||
g = g >> 4
|
||||
b = b >> 4
|
||||
data.append(g << 4 | b)
|
||||
data.append(r)
|
||||
return data
|
Loading…
Reference in New Issue
Block a user