Episode 11: Drawing a player image

Episode 11: Drawing a player image

Last episode saw us greatly increase the speed of our rectangle-rendering function by taking the rectangle line-by-line instead of pixel-by-pixel. In this episode we're going to adapt our function to drawing icons with just a couple extra lines of code.

First we need an icon! The 320x200 CGI-Plus graphics mode gives us 16 colors to work with:

I'm sure there are better ways to do this, but I just used a basic drawing app to build a simple player icon pixel-by-pixel, approximating the colors:

We'll use chroma-keying as a simple transparency. Notice the areas of my icon colored black; our image-drawing function will detect these black pixels and skip over them.

Now - how to get this icon data into our app? I just transcribed it as hex, remembering each byte is 2 pixels. For example, it starts with 4 pixels of black followed by 6 pixels of yellow: 0x00 0x00 0xee 0xee 0xee. We can put it right in our data section as one big array. This is the whole thing:

player_icon:
  db 0x00, 0x00, 0xee, 0xee, 0xee, 0x00, 0x00 ; 1
  db 0x00, 0x00, 0x88, 0xee, 0x88, 0x00, 0x00 ; 2
  db 0x00, 0x00, 0xee, 0xee, 0xee, 0x00, 0x00 ; 3
  db 0x00, 0x00, 0xee, 0x88, 0xee, 0x00, 0x00 ; 4
    
  db 0x00, 0x00, 0xee, 0xee, 0xee, 0x00, 0x00 ; 5
  db 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00 ; 6
  db 0xee, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xee ; 7
  db 0xee, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xee ; 8
    
  db 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0x00, 0x00 ; 9
  db 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0x00, 0x00 ; 10
  db 0x00, 0x00, 0x22, 0x22, 0x22, 0x00, 0x00 ; 11
  db 0x00, 0x00, 0x33, 0x33, 0x33, 0x00, 0x00 ; 12
    
  db 0x00, 0x00, 0x33, 0x00, 0x33, 0x00, 0x00 ; 13
  db 0x00, 0x00, 0x33, 0x00, 0x33, 0x00, 0x00 ; 14
  db 0x00, 0x00, 0x33, 0x00, 0x33, 0x00, 0x00 ; 15
  db 0x00, 0x66, 0x66, 0x00, 0x66, 0x66, 0x00 ; 16

Make sure to update your player_w and player_h based on the size of your icon! Mine is 14x16.

In the .copyByte loop of our draw_rect function, we're copying our color value from SI into DI, then incrementing DI until we get to the end of the line. Let's imagine we had a little player icon stored as an array of color values, row-by-row and line-by-line. We could easily draw this icon by pointing SI to the first byte of the icon and incrementing it with SI... we'd basically be tracing along the icon from start to finish, copying its values into our framebuffer. Let's try it!

First make a copy of the draw_rect function named draw_icon. We need to change the parameters a bit: Instead of a color value, we're going to take a pointer to the start of the icon data:

draw_icon( icon_ptr, icon_w, icon_h, x, y )

Notice I've reordered the parameters a bit. You must jiggle around your BP offsets to compensate (for example x was the first parameter and now it's the fourth, so it's moved from [bp+4] to [bp+10]). Feel free to keep the parameters in the old order and just replace the color parameter with the icon_ptr parameter if that makes it easier.

At the beginning of the function, we can move the icon_ptr directly into SI, so take out the nibble-conversion code. Otherwise nothing changes until we get to .copyByte. Here, we'll do a couple things different. First, SI is now an address, not a color, so we use the bracket operator to dereference the address and move the value found there into AL:

mov al, [si]

Next, to support transparency, we'll skip copying any pixels which are black. Black is conveniently zero, so we'll test AL for zero, and if true, jump over the line of code where we copy the value. After this, we simply increment SI along with DI (so we point at the next byte in the image) before continuing.

Back in our main game loop, we'll replace the draw_rect that draws our green rectangle with a draw_icon call. Remember to order the parameters correctly, and when pushing the address of the icon, don't dereference it. I've done this by copying the address into AX then pushing AX:

mov ax, player_icon
push ax

Let's look at the results:

It does the job!

BONUS: See it running on a real live PCjr!

See the full code for this episode in the project's GitHub.