MkBlk

Make a rectangular, single colour, compressed sprite on the fly

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:

MkBlk64

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
  

MkBlkNat

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
  

MkBlkWM

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..



Generated with sb2htm on 2022 Nov 06
©pjwitte 2oo1 - 2o22