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 :
X | X AND 7 |
---|---|
0 | 0 |
1 | 1 |
2 | 2 |
3 | 3 |
4 | 4 |
5 | 5 |
6 | 6 |
7 | 7 |
8 | 0 |
9 | 1 |
10 | 2 |
and so on. Are these the correct values for the bits in the word that we want? Try this and see :
X AND 7 | Correct Bit Required |
---|---|
0 | 7 |
1 | 6 |
2 | 5 |
3 | 4 |
4 | 3 |
5 | 2 |
6 | 1 |
7 | 0 |
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 Mask | OR Mask |
---|---|---|---|
0 | 0 | 01111111 | G0000000 R0000000 |
1 | 1 | 10111111 | 0G000000 0R000000 |
2 | 2 | 11011111 | 00G00000 00R00000 |
3 | 3 | 11101111 | 000G0000 000R0000 |
4 | 4 | 11110111 | 0000G000 0000R000 |
5 | 5 | 11111011 | 00000G00 00000R00 |
6 | 6 | 11111101 | 000000G0 000000R0 |
7 | 7 | 11111110 | 0000000G 0000000R |
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