Mosaic: DrawProc
Until now, we've used a simple temporary DrawProc that just copies ImageDC on the control. We will now change this procedure to make it draw the tiles in the order supplied by an array.
8.1 - Arrays
First of all, we will define two arrays. The position array is actually not really necessary, it can be done by calculations too but this is a good exercise in using arrays.
TilePositions dw 0,0 ; tile 1 , index 0
dw 50,0 ; tile 2 , index 1
dw 100,0 ; tile 3 , index 2
dw 150,0 ; tile 4 , index 3
dw 0,50 ; tile 5 , index 4
dw 50,50 ; tile 6 , index 5
dw 100,50 ; tile 7 , index 6
dw 150,50 ; tile 8 , index 7
dw 0,100 ; tile 9 , index 8
dw 50,100 ; tile 10 , index 9
dw 100,100 ; tile 11 , index 10
dw 150,100 ; tile 12 , index 11
dw 0,150 ; tile 13 , index 12
dw 50,150 ; tile 14 , index 13
dw 100,150 ; tile 15 , index 14
dw 150,150 ; tile 16 , index 15
.data?
TileTable db 16 dup (?)
The first table, TilePositions is a two dimentional array of words (2-byte value). Each pair of words contains the left-topmost coordinate of a tile. The first word is the X coordinate, the second word the Y coordinate.
The second table, TileTable, is a byte table. Each byte contains the 1-based index of a tile that is at a certain position. Here's an example:
First example
TileTable index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Byte value | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
This means all tiles are in the right order:
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 |
Second example
TileTable index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Byte value | 7 | 8 | 1 | 2 | 14 | 15 | 12 | 10 | 3 | 9 | 6 | 11 | 4 | 16 | 5 | 13 |
Will cause:
7 | 8 | 1 | 2 |
14 | 15 | 12 | 10 |
3 | 9 | 6 | 11 |
4 | 16 | 5 | 13 |
The value 0 in the table has a special meaning. This is the blank space in the tiles (the tile that is missing), so you can shuffle the tiles. Example:
TileTable index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Byte value | 7 | 8 | 1 | 2 | 14 | 15 | 12 | 10 | 3 | 0 | 6 | 11 | 4 | 16 | 5 | 13 |
Will cause:
7 | 8 | 1 | 2 |
14 | 15 | 12 | 10 |
3 | 6 | 11 | |
4 | 16 | 5 | 13 |
Note: The mix of 0-based and 1-based arrays and indices can be confusing. Try to remember what values mean in a certain array. Comments in your code can help you with this.
8.2 - Drawing the tiles
To draw the tiles in the order given by the TiteTable array, we will count from 0 to 15, representing the tile positions (o-15), and retrieve the tile number (1-16) at that position from the TileTable array. The pseudo code for the routine will be:
{
Get the tile number at curtilepos from TileTable
If tile number = 0 {
do nothing (blank space)
}
else
{
Get position A of tilenumber in the image from TilePositions
Get position B of the tile at the output image from TilePositions
Copy the tile from position A (in the tile image) to position B (in the backbuffer)
}
}
Draw the backbuffer on the control
In assembler, this will become:
Rect220 RECT <0,0,220,220>
.code
;================================================================================
; Draw Numbers
;================================================================================
DrawProc proc uses ebx edi esi hWnd:DWORD, hDC:DWORD
; Blank the backbuffer with the background color brush:
invoke FillRect, BackBufferDC, ADDR Rect220, hBackgroundColor
;Loop:
xor ebx, ebx
.WHILE ebx<16
;Get the tile that is at tilepos ebx
xor ecx, ecx
mov cl, byte ptr [offset TileTable + ebx]
.IF ecx==NULL ;tile = 0 means blank space
jmp @blankspace_skip
.ENDIF
dec ecx ; decrease number (tables are 0-based, tile numbers 1-based)
;Get the coordinates of that tile:
shl ecx, 2 ;multiply tilenr-1 by 4
mov eax, dword ptr [offset TilePositions + ecx]
; The high word of eax is the X coordinate, the low word the y coordinate
mov cx, ax ;ecx = source y
shr eax, 16 ;eax = source x
;Get coordinates of tile pos ebx
mov edx, ebx
shl edx, 2
mov edx, dword ptr [offset TilePositions + edx]
xor edi, edi
mov di, dx ;edi=destination y
shr edx, 16 ;edx=destination x
add edx, 9 ;margin (vertical)
add edi, 9 ;margin (horizontal)
invoke BitBlt, BackBufferDC, edi, edx, 50, 50, ImageDC, ecx, eax, SRCCOPY
@blankspace_skip:
inc ebx
.ENDW
; Copy the backbuffer onto the control
invoke BitBlt, hDC, 0, 0, 220, 220, BackBufferDC, 0, 0, SRCCOPY
ret
DrawProc endp
Some important things:
xor ecx, ecx
mov cl, byte ptr [offset TileTable + ebx]
Ebx is the counter, ecx cleared before use (this is necessary for the further code), then the byte at [offset TileTable + ebx] is put in cl (lower 8 bits of ecx). The offset TileTable gives a pointer in memory to the table, ebx is the byte index that is added to the table.
; Get the coordinates of that tile:
shl ecx, 2 ;multiply tilenr-1 by 4
mov eax, dword ptr [offset TilePositions + ecx]
; The high word of eax is the X coordinate, the low word the y coordinate
To get the position from a tilenumber, first 1 is substracted from the tilenumber, because the tilenumbers start at 1, and the tables need an index that starts with 0. This is the dec ecx. Then that index is multiplied by 4 by shifting the bits left twice. This is needed because a coordinate in the TilePositions array takes 4 bytes (2 words). Then the new index is added to the offset of TilePositions, and the result is put in eax. Eax is a dword, but the values in the table are words. This means that eax contains two values from the table, and that is exactly what we need. The high word of eax is the X coordinate (the first word), the low word is the Y coordinate (the second word). With some move and shift operations we can extract the X and Y part of the coordinate.
This coordinate is the left-topmost point of the tile at the current position in the tile image (hImage in ImageDC). Now the TilePositions array is used again to get the position of the current tile in the output backbuffer.
Finally, margins are added and then the right tile is copied from ImageDC to the current position in the backbuffer. After all tiles are drawn, the whole backbuffer is copied to the control DC.
8.3 - Testing the function
To test DrawProc, temporarily comment this line:
TileTable db 16 dup (?)
to
;TileTable db 16 dup (?)
Then add this to the .data section:
TileTable db 7,8,1,2,14,15,12,10,3,0,6,11,4,16,5,13
This will initialize the TileTable so you can see if it works. Assemble your program and run it. It should look like this:
The tiles are ordered according to the TileTable array and at the 0 byte in the table, a tile is left out.
8.4 - Done
Delete the TileTable line you changed and put back the old line (remove the semicolon to uncomment it). Your program will show nothing when you will run it, because then the array is initialized with zeroes. But this will change later.
The project files up till now: mosaic5.zip