Sometimes it is useful to be able to create a coloured sprite on the fly, eg to fill in a blank spot in a menu, or to present a bar of a certain colour in a menu, or for a whole lot of other reasons. Of course you could create a sprite in advance and just load that. But what if you didnt necessarily know the size required in advance, or if youd like the menu to have a variable size or colour?
Below is presented a method and three utilities to do so. The method is straightforward (although it can be fiddly to achieve). The first creates a mode 64 coloured sprite, the second a Native coloured sprite, and finally the latter is deployed to produce a Wman palette element coloured sprite:
The thing with mode 64 is that it is relatively easy to specify the colour. And thanks to the flexibility of the sprite format and implementation, it can be displayed in all GD2 modes: 16, 32, and 33, without change. A colour like $FF0000 is easier to remember than 248 (or internally, in Little Endian format, -2048. Long story..) which is what the same colour is in mode 32. Also, the code of a colour in mode 32 is different from the code of the same colour in mode 33, which further complicates matters.
The downside of mode 64 is that it takes rather a lot of space, in fact double as much as modes 32/33, and four times as much as mode 16. Luckily, a single colour rectangular sprite compresses very well! (although at some point it does have to be decompressed by the system before it can be displayed!) A 10 x 10 pixel mode 64 sprite, worth a nominal 400b of data, compresses nicely down to 5b of data, only 2b more than a corresponding mode 32/33! Additionally a sprite header is required; 24b for uncompressed or 32b for compressed.
I wont go into the details of compression etc here. See my RLE toolkit or the enRLE_bas utility for more information.
1000 rem + ------------------------------------------------------------------------ + 1002 rem |< MkBlk64 >| 1004 rem + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1006 rem | Make a mode 64 single colour, compressed, block sprite | 1008 rem | | 1010 rem | Save sprite (with overwrite!) if filename supplied, else keep memory | 1012 rem | No error checking! | 1014 rem + ------------------------------------------------------------------------ + 1016 rem | V0.01, pjw, 2021 Nov 14 | 1018 rem | V0.01, pjw, 2022 Nov 07, tidied | 1020 rem + ------------------------------------------------------------------------ + 1022 : 1024 DEFine FuNction MkBlk64(xs%, ys%, col, fnm$) 1026 LOCal i%, ad, tg, bp%, ez%, c$(5) 1028 LOCal d%, r%, sz, lz, cz 1030 : 1032 rem Work out compressed data size 1034 bp% = 4: rem mode 64 => 4 bpp 1036 ez% = bp% + 1: rem compressed element size 5b 1038 lz = xs% * ys%: rem logical size 1040 sz = lz * bp%: rem actual data size 1042 d% = lz DIV 128: rem number of max compressed blocks 1044 r% = lz MOD 128: rem remainder 1046 cz = ez% * (d% + (r% <> 0)) + 32: rem compressed sprite size 1048 ad = ALCHP(cz) 1050 : 1052 rem Convert RGB to count + colour 24 1054 POKE_L ad, col: POKE ad, 129: rem create element sample: 1056 c$ = PEEK$(ad, ez%): rem c$ = count.b + col24 1058 : 1060 rem Create sprite header 1062 POKE ad, 2, 64, 0: rem GD2 mode 64 sprite.. 1064 POKE ad + 3, %1000011: rem pattern compressed, dont cache 1066 POKE_W ad + 4, xs%, ys%, 0, 0: rem size, origen 1068 POKE_L ad + 12, 12, 0, 0: rem offset to data 1070 POKE$ ad + 24, 'RLE4': rem compression header 1072 POKE_L ad + 28, sz: rem expanded data size 1074 tg = ad + 32 : rem target address for sprite data 1076 : 1078 rem Now create sprite 1080 FOR i% = 1 TO d%: POKE$ tg, c$: tg = tg + ez% 1082 IF r%: POKE$ tg, c$: POKE tg, 257 - r%: rem remainder 1084 : 1086 rem Save sprite (if wanted) 1088 IF LEN(fnm$) THEN 1090 SBYTES_O fnm$, ad, cz: RECHP ad: RETurn 0: rem save and forget 1092 END IF 1094 RETurn ad: rem dont save and keep in mem 1096 END DEFine MkBlk64
Notwithstanding what was said above about the difficulty of specifying colours in a sane way in Native mode (ie DISP_TYPE = 16, 32, or 33) it is useful to be able to do so. You might want to produce a rectangle for similar purposes as above in the same colour as some other object displayed on screen or in the menu. You could use my toolkit command GETCOL% to get the correct colour code for the object, whatever the current display mode. More on that later. So here is the same method used for native colours.
However, native colours come not only in different flavours, but in two fundamentally different forms, namely as a single byte colour code, or as a word sized colour code. The word sized colour codes are split into two different formats: mode 32 - mainly for Intel-like systems, such as the emulators QPC2 and SMSQmulator, and mode 33, mainly for Motorola-like systems like Q40 and Q68.
Mode 32 has the extra complication of having two different forms. See Mode 32 for more details on this topic.
And then there is the padding, of course, which never bothers mode 64 sprites, where each pixel is 32 bits long although only the first three bytes are effective. With GD2 mode sprites each line (row) has to be divisible by four, so a mode 16 sprite with an effective x size of 22 pixels will have to be padded with two extra bytes at the end of each line. Thus the routine for native colour sprites require a little more legwork.
Note: in all modes any solid sprite with an odd x will have a black stripe down the rightmost edge, so this is best avoided. You can fix this by adding a mask or an alpha channel, with the rightmost column of pixels blotted out. I have not done this here.
1000 rem + ------------------------------------------------------------------------ + 1002 rem |< MkBlkNat >| 1004 rem + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1006 rem | Make a native, single colour, compressed, block sprite | 1008 rem | | 1010 rem | If mode (md%) given as -1 then current disp_type used | 1012 rem | Save sprite (with overwrite!) if filename supplied, else keep memory | 1014 rem | | 1016 rem | Limited error checking! But check return value before use! | 1018 rem | (Remember: The Native colour codes are different for each mode!) | 1020 rem + ------------------------------------------------------------------------ + 1022 rem | V0.01, pjw, 2021 Nov 14 | 1024 rem | V0.02, pjw, 2022 Nov 06, Correct NATIVE colour in mode 32; cache off | 1026 rem + ------------------------------------------------------------------------ + 1028 : 1030 DEFine FuNction MkBlkNat(xs%, ys%, col%, md%, fnm$) 1032 LOCal i%, x%, m%, bp%, c$(4), l$(xs% / 4) 1034 LOCal ad, tg, sz, cz 1036 : 1038 rem Create colour element, depending on mode 1040 rem Acccount for padding (but we dont care what it consists of) 1042 IF md% = -1: m% = DISP_TYPE: ELSE : m% = md% 1044 SELect ON m% 1046 = 16: IF col% > 255: RETurn -4 1048 bp% = 1: c$ = CHR$(col%) 1050 x% = ((xs% + 3) DIV 4) * 4 1052 = 32: bp% = 2: c$ = CHR$(col% MOD 256) & CHR$(col% DIV 256): rem LE -> BE 1054 x% = ((xs% + 1) DIV 2) * 2 1056 = 33: bp% = 2: c$ = CHR$(col% DIV 256) & CHR$(col% MOD 256) 1058 x% = ((xs% + 1) DIV 2) * 2 1060 = REMAINDER : RETurn -19 1062 END SELect 1064 : 1066 rem Uncompressed data size (w padding) 1068 sz = x% * ys% * bp% 1070 : 1072 rem Create a row, work out size, reserve memory 1074 l$ = '' 1076 FOR i% = 1 TO xs% DIV 128: l$ = l$ & CHR$(129) & c$: x% = x% - 128 1078 IF x%: l$ = l$ & CHR$(257 - x%) & c$ 1080 cz = LEN(l$) * ys% + 32 1082 ad = ALCHP(cz) 1084 : 1086 rem Create sprite header 1088 POKE ad, 2, m%, 0: rem GD2 palette mapped sprite.. 1090 POKE ad + 3, %1000011: rem Pattern compressed, dont cache 1092 POKE_W ad + 4, xs%, ys%, 0, 0: rem Size, origen 1094 POKE_L ad + 12, 12, 0, 0: rem Offset to data 1096 POKE$ ad + 24, 'RLE' & bp%: rem Compression header 1098 POKE_L ad + 28, sz: rem Expanded data size 1100 tg = ad + 32 : rem Target address for sprite data 1102 : 1104 rem Now create sprite 1106 FOR y% = 1 TO ys%: POKE$ tg, l$: tg = tg + LEN(l$) 1108 : 1110 rem Save sprite (if wanted) 1112 IF LEN(fnm$): SBYTES_O fnm$, ad, cz: RECHP ad: RETurn 0 1114 RETurn ad 1116 END DEFine MkBlkNat
Back to the issue of getting the correct colour code irrespective of display mode: What if you want to use the Wman palette colours? Getting the correct Wman palette colour is complicated by the fact that a completely different colour system is used in defining these colours; what you see is not what you get! (Bob Spelten has written a compact explainer of all this with his QCoCo package, for those interested in finding out more (See the Contacts page)). So rather than getting into the weeds over all this, the simplest approach is to leave all the hard work to the system to sort out: Set the colour you want using the tools and traps provided by the system for that purpose, and simply harvest the results of that labour with a simple peek. Afterall, when all is said and done, the display driver only works with native colours. So we need GETCOL% again.
The syntax of GETCOL% is: colour_native% = GETCOL%(#channel_no, key%) default #1 Where key% -1 => paper 0 => strip 1 => ink
There are many ways one could do MkBlkWM, depending on whether there already are open channels, the number of repeated calls required etc, so the routine below will just be one way, for the sake of illustration. Modify it according to need. MkBlkWM relies on MkBlkNat, above:
100 rem + ------------------------------------------------------------------------ + 102 rem |< MkBlkWM >| 104 rem + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 106 rem | Make a single colour, compressed, block sprite using Wman colours | 108 rem | | 110 rem | This is a rather twisted hack to get the system to translate a Wman | 112 rem | palette colour to the native colour format, but since doing so is a | 114 rem | rather complex and convoluted process anyway, it seemed the best way.. | 116 rem | | 118 rem | Dependencies: MkBlkNat, & GETCOL% | 120 rem + ------------------------------------------------------------------------ + 122 rem | V0.01, pjw, 2021 Nov 15 | 124 rem | V0.02, pjw, 2022 Nov 06, Uses update MkBlkNat, so no PCBO% | 126 rem + ------------------------------------------------------------------------ + 128 : 130 DEFine FuNction MkBlkWM(xs%, ys%, col%, fnm$) 132 LOCal ch, c% 134 ch = FOPEN("scr_0x0a0x0") 136 WM_INK#ch; col%: c% = GETCOL%(#ch; 1): CLOSE#ch 138 RETurn MkBlkNat(xs%, ys%, c%, -1, fnm$) 140 END DEFine MkBlkWM
If you download the BASIC (see below) you get a simple "harness" with the routines. This may give a better idea of how they could be used.
Note: If you set odd x-values for these sprites in any mode, you get a black stripe down the right side of the sprite. This is a feature of the system, not these routines.
And thats it! Please let me know how you get on! Also, if you find any bugs or discover optimisations etc, Id be interested to know..