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.

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

loop curtilepos: 0 to 15
{
    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:

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

 ;Get the tile that is at tilepos ebx
 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.

 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

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:

Shuffle test

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