5. That calculation again!

Have you had a good think about calulating screen addresses for pixels then? Better still, have you thought about the problem I hinted at above? What is the problem then?

If each word of the screen memory holds data for either 8 or 4 pixels, then how can we calculate the correct address for each pixel, because it is (now) obvious that the address for the first 8 pixels in each row will be the same in mode 4 (or 4 pixels in mode 8) so our wonderful calculation above needs a bit of tweaking to make it work corrctly.

In mode 4, the screen address changes every 8 pixels across. So where x is 0 to 7, the screen address is the same, for x = 8 to 15 it is the next word of memory and so on. The word that the x pixel lives in is found by the calculation, but the actual pixel within that group of 8 pixels is not found. Follow?

Assume row zero and pixel 2, this gives screen address =

base address + (0 * scan width) + INT(2 / 4)

or

base address + 0 + 0

or

base address

This is the same address for pixel 0 through pixel 7. For pixels 8 to 15 it will be as follows (using 8 in the calculation) :

base address + (0 * scan width) + INT(8 / 4)

or

base address + 2

so we know the memory word, but not the actual bits within it. Remember bits 7 = pixel 0, bit 6 = pixel 1 and so on down (up?) to bit 0 for pixel 7. How do we get to a value between 0 and 7 from any x value? If we AND the x value with 7 that will give us a value between 0 and 7 won't it - lets see :

XX AND 7
00
11
22
33
44
55
66
77
80
91
102

and so on. Are these the correct values for the bits in the word that we want? Try this and see :

X AND 7Correct Bit Required
07
16
25
34
43
52
61
70

Not quite it would appear, but we could always subtract (x AND 7) from 7 couldn't we? That would give the correct answer. So a solution is at hand. If we subtract the result of (x AND 7) from 7, we get the correct bit number in each byte of the calculated memory word. Yippee (or is it - read on.)

Not quite, I'm afraid. If we have the memory address, we can extract the current contents - we must preserve the other 7 pixels when we plot this one remember - so we need to mask out the same bit in each byte of the screen word. If we used the subtraction method identified above, we would needs bucket loads of testing and masking to figure out which bit is required. We need another method. Before we get to that, how exactly shall we preserve the current pixels?

Remember that a pixel is defined by a single bit in the green byte and the corresponding bit in the red byte of the screen word. To set a pixel we must first set its two bits to zero (or black) and then set the two bits according to the requested colour. This turns out to be quite simple.

First create a mask where the bit to be changed in the red and green bytes are set to zero and every other bit is set to 1. If we AND this mask word with the screen word we effectively set that one pixel to black. So far so good. Next set a new mask where the single bit in each byte is the requested green or red bit and all the rest are zero. If we now OR this word with the screen word we have set the pixel to our requested colour. Too many words, lets have an example.

Our screen shows the following colours in the first 8 pixels :

red green green black black white red white

This means that we have the following two bit values for each pixel :

01 10 10 00 00 11 01 11

Which means that we have the following word in memory :

01100101 10000111 = $6587

Now let us assume that we want to colour the first pixel (currently red) to white. So our mask to clear that bit (bit 7 in each byte) needs to be set to

01111111 01111111 = $7f7f

Now we AND this word with the screen word to get the following :

01100101 00000111 = $6507

Note now that the first pixel has been set to 00 (bit 7 from both bytes) so it has effectively been set to black.

Next we need a white pixel so the colour mask for white must have a 1 in bit 7 of each byte. The rest must be zero to preserve the current colours of all the other pixels. Our mask must be :

10000000 10000000 = $8080

So if we now OR this into the (new) screen word - currently $6507 - we get the following :

11100101 10000111 = $E587

Taking all the bits into colour values we get this :

11 10 10 00 00 11 01 11

which translates back to the following colours :

white green green black black white red white

Success, we have preserved all other pixels and set the first one to white. Now we know how to do it to one pixel, it is the same for all the other 7, but the masks need to be changed for each pixel. How?

If we decide to change pixel 0 (as above) the masks are $7f7f and $8080. This is easy. If we want pixel 1 to be changed the masks are rotated one bit to the right becoming $bfbf and 4040 and so on. Look again at our table above where we show the result of (x AND 7) and the correct bit in the screen word - notice that if we assume that pixel 0 is being changed we can rotate the masks by (x AND 7) bits to get the correct masks for whichever pixel we try to set, as the following table shows :

Table 8.5. Bitmaps for Mode 4 pixel masking

Pixel(x AND 7)AND MaskOR Mask
0001111111G0000000 R0000000
11101111110G000000 0R000000
221101111100G00000 00R00000
3311101111000G0000 000R0000
44111101110000G000 0000R000
551111101100000G00 00000R00
6611111101000000G0 000000R0
77111111100000000G 0000000R

Note

I have only shown one byte of the AND mask, the other byte is identical as we are masking out the same bit in each byte.

Looking at the table, we see that the result of (X AND 7) is the pixel we need to set in the screen. If we start with a mask suitable for pixel 0 and ROTATE it to the right by (x AND 7) bits, we get the correct mask for that pixel. This also works for our colour mask as well. Things sometimes become clear when you switch to binary, especially in graphics situations!

We now have the basics for a mode 4 'pixel setting' routine. Lets try it out.

Assume that we want to set the colour of any pixel on the screen to any of the 4 colours we want in mode 4. We can actually use any of the mode 8 colours because only bits 2 and 1 will be used. This means that a mode 8 colour of blue (value 001) will result in a mode 4 value black (value 00) being set for the appropriate pixel. This is exactly how SuperBasic would handle it.

We will use the registers as follows :

D1.W = x (across)
D2.W = y (down)
D3.W = colour (0 to 7)

Here's the code in all its glory :

*==============================================================================-
* In D3 bit 2 is green and bit 1 is red, we don't need any other bits, so get
* rid of them now. Then shift the Green bit into bit 15 of D4 and the red into
* bit 7 of D3 ...
*==============================================================================-
start       bra     plot_init       ; Call here (start + 4) to initialise things

plot_4      bsr.s   calc            ; Get A1 = screen address
            andi.w  #6,d3           ; D3 = 00000000 00000GR0 (showing all bits)
            lsl.w   #6,d3           ; D3 = 0000000G R0000000
            move.w  d3,d4           ; D4 = 0000000G R0000000
            lsl.w   #7,d4           ; D4 = GR000000 00000000
            or.w    d4,d3           ; D3 = GR00000G R0000000
            andi.w  #$8080,d3       ; D3 = G0000000 R0000000 (keep both bits 7)

*==============================================================================-
* D3.W is now set to a colour mask for pixel 0. This is where we want to start.
* Now we need to build a mask to clear out pixel 0 as well. This is easy - use the
* value from the table above. Then we can start rotating them into the correct
* position as detailed above.
*==============================================================================-
            move.w  #$7f7f,d2       ; AND mask = 10000000 10000000
            andi.w  #7,d1           ; (x AND 7) in d1
            ror.w   d1,d2           ; Build correct AND mask
            ror.w   d1,d3           ; Build correct OR mask (colour)
            and.w   d2,(a1)         ; AND out the changing pixel
            or.w    d3,(a1)         ; OR in the (new) colour
            moveq   #0,d0           ; No errors
            rts                     ; All done

*==============================================================================-
* Calculate the screen address for the x and y values passed in D1 and D2.
* Trashes A1, D4 and D5.
* The routine plot_init must have been called to initialise the screen addresses
* and scan line widths BEFORE calling this routine.
*==============================================================================-
calc        lea     scr_base,a1     ; Where we hold the screen base address
            move.l  (a1)+,d0        ; Fetch the screen base address
            move.w  (a1),d6         ; And the scan line size
            movea.l d0,a1           ; Get the screen base where we want it

*==============================================================================-
* D1.W = x across value
* D2.W = y down value
* D3.W = ink colour required
* D6.W = scan line size
* A1.L = screen base address
*==============================================================================-
            move.w  d2,d5           ; Copy y value (down)
            ext.l   d5              ; We get a long result next ...
            mulu    d6,d5           ; Multiply by scan_line size
            adda.l  d5,a1           ; A1 = correct scan line address

            move.w  d1,d4           ; Copy x value
            lsr.w   #2,d4           ; D4 = INT(x / 4)
            bclr    #0,d4           ; Make even = green byte in scan_line
            adda.w  d4,a1           ; A1 = correct screen word address
            rts                     ; Done

*==============================================================================-
* This routine must be called once before using the above plot routines. It
* initialises the screen base address and scan line width from the channel
* definition block for SuperBasic channel #0.
*==============================================================================-
plot_init   suba.l  a0,a0           ; Channel id for #0 is always 0
            lea     scr_base,a1     ; Parameter passed to extop routine
            lea     extop,a2        ; Actual routine to call
            moveq   #sd_extop,d0    ; Trap code
            moveq   #-1,d3          ; Timout
            trap    #3              ; Do it
            tst.l   d0              ; OK?
            bne.s   done            ; No, bale out D1 = A1 = garbage

got_them    move.w  d1,-(a7)        ; Need to check qdos, save scan_line
            moveq   #mt_inf,d0      ; Trap to get qdos version (preseves A1)
            trap    #1              ; Get it (no errors)
            move.w  (a7)+,d1        ; Retrieve scan_line value
            andi.l  #$ff00ffff,d2   ; D2 = qdos, mask out the dot in "1.03" etc
            cmpi.l  #$31003034,d2   ; Test for "1?03" where? = don't care
            bcs.s   too_old         ; Less than 1.03 is too old

save        move.w  d1,(a1)         ; Store the scan_line size

done        rts                     ; Finished

too_old     move.w  #128,d1         ; Must be 128 bytes
            bra.s   save            ; Save D1 and exit

extop       move.l  $32(a0),(a1)+   ; Fetch the scan_line length & store it
            move.w  $64(a0),d1      ; Fetch the screen base - don't store it
            moveq   #0,d0           ; No errors
            rts                     ; done

*==============================================================================-
* Set aside some storage space to hold the screen base and scan_line width. This
* saves having to calculate it every time we plot a single pixel.
*==============================================================================-
scr_base    ds.l    1
scan_line   ds.w    1

And that is the end of the code. To use the above in your assembly language programs simply call plot_init once to set up the screen base and scan line widths, then call plot_4 as often as you like. Easy stuff.

To test this code out from SuperBasic, ALCHP (or RESPR) some heap and LBYTES the code file to that address and CALL it. This initialises the system by calling plot_init. Now, simply CALL address, x, y, colour and the points will be plotted. Make sure you are in mode 4 or the results may be a bit crazy! An example program follows :

1000  PLOT_INIT = RESPR(256): REMark Enough space for plot_8 as well!
1005  PLOT_4 = PLOT_INIT + 4
1010  LBYTES flp1_plot_bin, PLOT_INIT
1015  CALL PLOT_INIT
1020  FOR across = 0 to 100
1025    FOR down = 0 to 100
1030      CALL PLOT_4, across, down, RND(0 to 7)
1035    END FOR down
1040  END FOR across