Search Tips Advanced Search |
| Delphi Graphics and Game Programming Exposed with DirectX 7.0 by John Ayres Wordware Publishing, Inc. ISBN: 1556226373 Pub Date: 12/01/99
Search this book:
Previous | Table of Contents | Next
#### Pixel Checking A somewhat more direct way of implementing transparency, this method draws the sprite image one pixel at a time. As it is drawing individual pixels, it checks the pixel it will be drawing to see if its color matches the specified transparent color. If so, it skips drawing that pixel entirely; otherwise it draws the pixel into the destination. Implementing this method is a bit more involved than color keying. By its very nature, it is pointer intensive and requires some creative pointer manipulation. It also requires more intimate knowledge of the pixel format of both the source and destination surfaces. It is slower than color keying, but through clever optimization can be as fast as non-hardware assisted color keying. However, the biggest benefit that a developer will see from this method is that it allows the creation of incredible special effects, such as lighting and translucency. Locking a Surface This method requires direct access to the memory that contains both the source bitmap and the destination, which is usually the backbuffer surface. Using normal GDI functions, this would be impossible. This is one of the things that makes DirectDraw so attractive to game developers. To retrieve a pointer to surface memory, we must use the Lock method of the IDirectDrawSurface4 object. Using this method, we can access the entire surface memory or provide the coordinates for accessing only a specific rectangular area of the memory. The Lock method is defined as:
    function Lock (

    lpDestRect: PRect;                        // the surface region to lock

    out lpDDSurfaceDesc: TDDSurfaceDesc2;     // a TDDSurfaceDesc2 structure

    dwFlags: DWORD;                           // indicates locking behavior

    hEvent: THandle                           // unused

    ): HResult;                               // returns a DirectX error code
The first parameter indicates the rectangular area of the surface to lock; specifying nil in this parameter locks the entire surface. The second parameter is a TDDSurfaceDesc2 structure that will be initialized with relevant details about the surface. In particular, the lpSurface member of this structure will contain a pointer to the upper left pixel of the surface memory. Other members of this structure will contain additional information about the surface, such as its height, width, and stride. The third parameter is a series of flags controlling access to the locked memory, such as read-only or write-only. The final parameter is unused and should be set to zero. As an example, the code for retrieving a pointer to our backbuffer memory would look something like:
    var

      BackSurfaceDesc: TDDSurfaceDesc2;

    begin

      {initialize the size member}

      BackSurfaceDesc.dwSize := SizeOf(TDDSurfaceDesc2);

      {lock the backbuffer memory}

      FBackBuffer.Lock(nil,BackSurfaceDesc,DDLOCK_WAIT or DDLOCK_SURFACEMEMORYPTR,0)
| Note: It is very important to always set the size member of any record structure that has one before using it in any function call.
As with all DirectX functions, it is usually a good practice to check the return code from a call to the Lock method. The DDLOCK_WAIT flag will cause the Lock method to wait until the surface is available and no longer being accessed by the hardware blitter. However, if the surface is locked by another thread or process, this function will return immediately with a DDERR_SURFACEBUSY error regardless of the DDLOCK_WAIT flag. Accessing the Memory When the Lock method returns, the lpSurface member of the TDDSurfaceDesc2 structure will contain a pointer to the surface memory. Exactly how this is used depends entirely on the pixel format of the surface. Since all of our examples deal with 256-color palettized display modes, pixels will be stored as bytes. Therefore, if we typecast the lpSurface member as a pointer to a byte, writing a value at the location reference by this pointer will change one byte on the surface, and thus one pixel in the image the surface contains. In practice, it is useful to access this pointer as a pointer to an array of bytes. This allows us to access horizontal bytes as if they were stored in a one-dimensional array. Although perhaps not immediately obvious, the process is simple. We’ll need to create a new data type that is declared as an array of byte with only one element, and another data type that is a pointer to this previous type, like so:
      TBytePtr = array[0..0] of Byte;

      PBytePtr = ^TBytePtr;
Then, if we set a variable of type PBytePtr to the value of the lpSurface member, we can easily reference the surface memory as an array of bytes:
    var

      BackMem: PBytePtr;

    begin

      BackMem := PBytePtr(BackSurfaceDesc.lpSurface);
Drawing the Sprite We must lock both the backbuffer surface and the surface containing the sprite images. Once this is done, we iterate through each row of the sprite, and each pixel in the row. For each pixel in the current row being drawn, we check to see if it is equal to the specified transparent color. If it is not, we go ahead and set the appropriate pixel in the destination memory to the value of the pixel being checked. If it is indeed equal to the transparent color, we simply move on to the next pixel in the row. The following example demonstrates an implementation of this technique. Listing 6-4: Drawing a sprite with transparent pixels
    procedure TfrmDXAppMain.DrawSurfaces;

    var

      SourceRect: TRect

      BackSurfaceDesc,

      SpriteSurfaceDesc: TDDSurfaceDesc2;

      BackMem, SpriteMem: PBytePtr;

      Row, Col: Integer;

    begin

      {erase the last frame of animation}

      SourceRect := Rect(0, 0, DXWIDTH, DXHEIGHT);

      FBackBuffer.BltFast(0, 0, FBackground, SourceRect,

                          DDBLTFAST_NOCOLORKEY OR DDBLTFAST_WAIT);

      {increment the frame number and update the sprite’s position}

      with FSprite do

      begin

        Inc(FCurFrame);

        {rollover the frame number as necessary}

        if FCurFrame > FFrameNum then

          FCurFrame := 0;

        {add the velocities to the positions. our sprite will only move

         horizontally, so we do not need to bother with the vertical values}

        FXPos := FXPos + FXVel;

        {see if the sprite is moving offscreen, and adjust

         its position accordingly}

        if FXPos < 0 then

          FXPos := DXWIDTH - 90;

        {—begin blitting the current frame—}

        {we need to lock both the backbuffer surface and the sprite image

         surface, so begin by locking the backbuffer}

       BackSurfaceDesc.dwSize := SizeOf(TDDSurfaceDesc2);

        {we ’re going to lock the entire surface}

        DXCheck(FBackBuffer.Lock(nil, BackSurfaceDesc,

                DDLOCK_WAIT or DDLOCK_SURFACEMEMORYPTR, 0));

        {initialize the byte array pointer to point at the locked memory}

        BackMem := PBytePtr(BackSurfaceDesc.lpSurface);

        {increment this pointer to the position in the buffer at which the

         sprite should be displayed (based on the sprite’s position)}

        BackMem := Pointer(Longint(BackMem)+FXPos+(FYPos*BackSurfaceDesc.lPitch));

        {now, lock the sprite surface}

        SpriteSurfaceDesc.dwSize := SizeOf(TDDSurfaceDesc2);

        {again, we’ll lock the entire surface}

        DXCheck(FImages.Lock(nil, SpriteSurfaceDesc,

                DDLOCK_WAIT or DDLOCK_SURFACEMEMORYPTR, 0));

        {initialize the byte array pointer to point at the locked memory}

        SpriteMem := PBytePtr(SpriteSurfaceDesc.lpSurface);

        {increment this pointer to the position of the current sprite frame}

        SpriteMem := Pointer(Longint(SpriteMem)+(FCurFrame*90));

      {iterate through each row of pixels in the sprite image (our sprite image is

       90 X 50)}

       for Row := 0 to 49 do

      begin

        {iterate through each pixel in the current row}

        for Col := 0 to 89 do

          {if the pixel in the sprite image is not 0 (the transparent color),

           copy it into the destination surface}

          if SpriteMem^[Col] <> 0 then

            BackMem^[Col] := SpriteMem^[Col];

        {move to the next row in the sprite and backbuffer surface memory}

        Inc(BackMem, BackSurfaceDesc.lPitch);

        Inc(SpriteMem, SpriteSurfaceDesc.lPitch);

      end;

    ********************************************************************************

        {we have now copied the sprite, so unlock the surface memory}

        FBackBuffer.Unlock(nil);

        FImages.Unlock(nil);

      end;

    end;
Previous | Table of Contents | Next

Products | Contact Us | About Us | Privacy | Ad Info | Home Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.