Images

Now we know how to draw graphic primitive objects, like pixels, lines, bars etc, so we want to do something more entertaining stuffs. We want images on the screen. For example, the following picture shows two fruits, and we would like to put them to the screen.

If we have a closer look, these images are built by pixels (what a surprise):

As we already learned, each pixel in the screen is just a byte, so both the apple and the pear can be represented by a byte array. (In this lesson, don't ask, how we generated these arrays. Assume we have them somehow.)
  • The apple has 12x12 pixels, so it needs 12x12 = 144 bytes
  • The pear has 11x12 pixels, so it needs 11x12 = 132 bytes

It's really important, that we have to store the sizes as well, without this information we couldn't show the image properly.

const  
 apple : array[0..143] of byte =(
  $00,$00,$00,$00,$00,$00,$30,$02,$00,$00,$00,$00,
  $00,$00,$00,$00,$00,$30,$02,$00,$00,$00,$00,$00,
  $00,$00,$0C,$0C,$0C,$30,$02,$0C,$0C,$0C,$00,$00,
  $00,$0C,$0C,$0F,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$00,
  $0C,$0C,$0F,$0C,$0C,$0C,$0C,$0C,$0C,$70,$0C,$0C,
  $0C,$0F,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$70,
  $0C,$0F,$0C,$0C,$0C,$0C,$0C,$0C,$70,$0C,$70,$70,
  $00,$0C,$0C,$0C,$0C,$0C,$70,$0C,$0C,$0C,$70,$00,
  $00,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$70,$70,$70,$00,
  $00,$00,$0C,$0C,$0C,$0C,$0C,$70,$0C,$70,$00,$00,
  $00,$00,$00,$0C,$0C,$0C,$70,$70,$70,$00,$00,$00,
  $00,$00,$00,$00,$00,$30,$30,$00,$00,$00,$00,$00);

 pear : array[0..131] of byte =(
  $00,$00,$00,$00,$00,$00,$30,$30,$30,$00,$00,
  $00,$00,$00,$00,$2C,$30,$30,$00,$00,$30,$00,
  $00,$00,$00,$2C,$2C,$2C,$2B,$00,$00,$00,$30,
  $00,$00,$00,$2C,$2C,$2C,$2C,$00,$00,$00,$30,
  $00,$00,$2C,$2C,$2C,$2C,$2C,$2B,$00,$00,$00,
  $00,$2C,$2C,$2C,$2C,$2C,$2C,$2C,$2B,$00,$00,
  $2C,$2C,$2C,$2C,$2C,$2C,$2C,$2B,$2A,$2B,$00,
  $2C,$2C,$2C,$2C,$2C,$2B,$2B,$2B,$2B,$2A,$00,
  $2C,$2C,$2C,$2C,$2B,$2C,$2B,$2A,$2B,$2A,$00,
  $2C,$2C,$2C,$2C,$2C,$2B,$2B,$2B,$2A,$2A,$00,
  $00,$2C,$2C,$2B,$2B,$2B,$2A,$2A,$2A,$00,$00,
  $00,$00,$00,$2B,$2B,$2B,$2A,$00,$00,$00,$00);

Show the image - no transparency

Anyway, the image itself is a rectangular object, so the way we put this to the screen will be similar as we did it for the bars. Remember, in the lesson 5 we used a REP STOSB instruction to draw one pixelline of a bar object. For images, instead of filling up the line with a solid color (STOSB) we have to copy over the pixels from the source array (MOVSB). The basic steps in this method are the following:

Ok, do it then. See the implementation below. Notice, that we use the register pair DS:SI to get the data from the source variable (lds si,src - this is the instruction that takes the pointer). Because the DS is required for the pascal internal work, we have to save it (push DS) at the beginning and load back at the end (pop DS). In the inner loop (CX takes width times) we use the REP MOVSB instruction, this is the one that shows a line of the image.

procedure ShowImage(x,y,width,height:integer; var src); assembler;
asm
   push DS
   mov  ES,SegA000      { Screen segment }
   mov  ax,320
   mul  y
   add  ax,x
   mov  bx,ax           { Address calculation, store the result in BX }
   cld
   lds  si,src
   mov  dx,height
@l0:
   mov  cx,width
   mov  di,bx           { show one line from the source data }
   rep  movsb
   add  bx,320          { address for the next line }
   dec  dx
   jnz  @l0             { draw all lines }
   pop  DS
end;

Test

Time to test. We show both fruits on the screen. Notice, that the array definitions are in a different file (fruits.inc) as we don't want to mix the code with pure data.

program images;

uses gfx256;

var
   i : integer;

{$I fruits.inc}

begin
 graph320x200;

 for i:=0 to 9 do ShowImage(i*16,10,12,12,apple);      
 for i:=0 to 9 do ShowImage(i*16,30,11,12,pear);      

 ShowImage(100,100,12,12,apple);
 ShowImage(105,105,11,12,pear);

 readln;
 text80x25;
end.
The result as we see on the screen:

The procedure ShowImage obviously works, but there is an issue here. If you see the fruits below, the image of the pear covers some pixels from the previously drawn apple. This is not an error, ShowImage does its job. The problem is that we don't handle the possible transparency, therefore the image covers all pixels inside its rectangular area. So we need another routine that takes care of this issue.

Show the image - with transparency

If we want some pixels to be transparent, we have to choose a color index (one from the possible 256) as a special one. When an image contains pixel with this color index, instead of showing the pixel, we skip that point, so the background remains. Usually, most of the game programmers use the color index $00 for the transparency. As we know, by default the color $00 is the color black.

Understand, that the $00 is just a palette index and not the color itself. Therefore, even if we use the $00 for the transparent pixels, this DOES NOT MEAN that we can't show the color black on the screen anymore. For example, if you go back to the lesson 6, you will see, that color index 16 ($10) has the color black as well. Anyway, you can define your own colors with the SegRGB procedure, therefore you could setup other indexes as black.

Now, how will the transparent image routine work? Instead of the previous REP MOVSB instruction, we have to take each pixel, check if the value is $00, and if it is, we don't show that pixel. So the internal loop that draws one line will look like this:

@l1:
   lodsb                { get the pixel from the source data }
   test al,al           { skip if the value is zero          }
   jz   @skip
   mov  ES:[di],al      { put the pixel                      }
@skip:
   inc  di              { next pixel position                }
   loop @l1             { draw all pixels in this line       }

Do you get me? If the pixel's value is $00, we don't show it, just skip. Apart form this little modification, the rest is the same. Put it together:

procedure ShowImageTransparent(x,y,width,height:integer; var src); assembler;
asm
   push DS
   mov  ES,SegA000      { Screen segment }
   mov  ax,320
   mul  y
   add  ax,x
   mov  bx,ax           { Address calculation, store the result in BX }
   cld
   lds  si,src

   mov  dx,height
@l0:
   mov  cx,width
   mov  di,bx

@l1:
   lodsb                { get the pixel from the source data }
   test al,al           { skip if the value is zero          }
   jz   @skip
   mov  ES:[di],al      { put the pixel                      }
@skip:
   inc  di              { next pixel position                }
   loop @l1             { draw all pixels in this line       }

   add  bx,320          { address for the next line          }
   dec  dx
   jnz  @l0             { draw all lines                     }

   pop  DS
end;

program images2;

uses gfx256;

var
   i : integer;

{$I fruits.inc}

begin
 graph320x200;

 for i:=0 to 199 do
  begin
   if random(100)<50
     then ShowImageTransparent(random(300),random(180),12,12,apple)
     else ShowImageTransparent(random(300),random(180),11,12,pear);
  end;

 readln;
 text80x25;
end.
The result now is this:

In this case the color $00 is transparent, therefore we see the background's pixels where the recangular area contains this value.

Okay, see some notes at the end, because we will use something similar showing sprites on the screen soon...

Downloads: [ Gfx256 unit | Fruits | Normal Images | Transparent images ]

Back