• No results found

One of the biggest benefits of using a DIB over a DDB is the fact that individual pixels can be directly accessed. This makes feasible many advanced graphical rendering techniques, such as ray casting. Accessing the individual pixels of a DDB (and even using the Pixels property of a TCanvas) is incredibly slow and could never be used for such advanced graphical output techniques.

To access individual pixels using the TBitmap object, the ScanLine property must be used. The ScanLine property is an indexed property that requires a row number and returns a pointer to the block of memory that contains the specified rows of pixels. The actual format of the information stored in this block of memory is dependent upon the value of the TBitmap’s PixelFormat property. The PixelFormat value reflects the number of bits required to describe one pixel in the image. Refer to the color depth topic discussed previously for more information on bits-per-pixel and its relation to the number of colors an image can contain. Thus, it is very important to know how the image

information is stored before accessing it.

Since this book is primarily concerned with 256-color images and graphical modes, assume that a TBitmap object has been loaded with a 256-color bitmap image. A variable of type PByteArray can be used to access the pixels pointed to by the ScanLine property. PByteArray is defined as a pointer to an array of bytes. The result of accessing the

ScanLine property is assigned to our PByteArray variable, at which point the individual pixels can be accessed by a simple index into the array. Since a 256-color bitmap defines one pixel as 8 bits, or a byte, using a PByteArray variable allows the developer to access individual pixels correctly. If the image were of any other color depth, a different approach to accessing the pixels would need to be used. Listing 3-6 demonstrates accessing individual pixels through the ScanLine property.

Previous Table of Contents Next [an error occurred while processing this directive]

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.

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

Listing 3-6: Accessing individual pixels through the ScanLine property

procedure TForm1.Button1Click(Sender: TObject);

var

Bitmap: TBitmap;

Pixels: PByteArray;

iBand, iRow, iCol: Integer;

DrawColor: Byte;

begin

{create the bitmap object}

Bitmap := TBitmap.Create;

{initialize the appropriate properties so we can access individual pixels in a known way}

Bitmap.Width := 169;

Bitmap.Height := 128;

Bitmap.PixelFormat := pf8Bit;

{create 8 horizontal bands of alternating colors}

DrawColor := 19;

for iBand := 0 to 7 do begin

{flip-flop the color. this is an 8 bit image, so we specify the color as an index into the palette.}

if DrawColor = 16 then DrawColor := 19 else

DrawColor := 16;

{each band is comprised of 16 rows of color}

for iRow := 0 to 15 do begin

Title

{retrieve a pointer to the row of pixels}

Pixels := Bitmap.ScanLine[(iBand*16)+iRow];

{set each pixel in the row to the specific color}

for iCol := 0 to Bitmap.Width-1 do Pixels[iCol] := DrawColor;

end;

end;

{draw the bitmap to the screen}

Canvas.Draw(8, 8, Bitmap);

{free the bitmap}

Bitmap.Free;

end;

Figure 3-13: Individually painted pixels Raster Operations

In addition to simply copying bitmap pixels from place to place, certain functions can modify exactly how the pixels are drawn onto their destination according to a specified setting. This is known as a raster operation, or ROP, and it determines how the pixels from the source, the destination, and the destination device context’s selected brush are combined. The pixels are combined using boolean operations.

raster operation (ROP): A Boolean function performed on pixels when they are copied from bitmap to bitmap that determines how the pixels from the source, the destination, and the destination device context’s selected brush are combined.

There are 256 total raster operations, each producing a different output based on the Boolean operator used and the order in which pixels are combined. Of these, only 16 are commonly used in Windows graphics

programming. There are several GDI functions, such as BitBlt and StretchBlt, that allow a developer full access to all 256 raster operations. Using the Delphi VCL, the CopyMode property of the TCanvas object specifies the raster operation to be used, and can be set to one of 16 constants that represent the 16 most commonly used raster operations.

Raster operations are incredibly useful under certain circumstances. They can be used to easily implement specific special effects which, without raster operations, would be more difficult to implement otherwise. For example, using raster operations, it is very easy to simulate transparent pixels in a bitmap when it is displayed over a background. This effect and others could be implemented using any one of a hundred different

techniques, but raster operations make their implementation fast and simple.

Simulating Transparency

Perhaps the most common function performed by the graphical output code of a game is to display one bitmap over another, with the pixels of the background showing through the “transparent” pixels of the displayed bitmap or sprite. Without transparency, every sprite image would have to be rectangular, or the resulting animation would look horrible. Figure 3-14 graphically illustrates the importance of transparency.

Figure 3-14: The effect of transparency

The easiest method by which sprites can be transparently drawn to the background is known as masking.

Masking uses two bitmaps for each sprite image, as each bitmap will be used with a different raster operation when the sprite is drawn.

masking: The act of using two bitmaps, an AND bitmap and an OR bitmap, along with raster operations to draw one bitmap over another while simulating transparent pixels in the source bitmap.

This method is precisely what happens when an application sets a TBitmap object’s Transparent property to True and then calls TCanvas.Draw. This is a very convenient encapsulation in Delphi. However, the only drawback is that the transparent color is either the color of the pixel in the bottom leftmost pixel of the bitmap or is specified in the TBitmap’s TransparentColor property, depending on the value of its TransparentMode property. The result is that this specified transparent color cannot be used elsewhere in the image, and unless proper care is taken when constructing the image, the result may not be what is expected. Using the direct approach as explained below gives the developer more control over the appearance of the final image. Figure 3-15 displays the difference between using the TransparentColor property and using the technique described below.

The two sprite images consist of an AND mask and an OR mask. The OR mask is the actual sprite image as it should appear in the destination. The only requirement for the OR mask is that pixels that should be transparent must be totally black (this must be palette slot zero for 256 color images). The AND mask is a monochrome bitmap consisting of only black and white. The black areas should match those of the actual sprite image in the OR mask, and the white areas represent pixels that should be transparent (opaque, or sprite image pixels, must be palette slot 0, and transparent pixels must be palette slot 255). Figure 3-16 illustrates a sprite image (the OR mask) and its accompanying silhouette (the AND mask).

Figure 3-15: Using the TBitmap.Transparent property versus the direct approach

Figure 3-16: A sprite AND and OR mask

Drawing a sprite image with transparent pixels requires two steps. First, the AND mask is drawn to the destination bitmap using a boolean AND raster operation. The white pixels (representing the transparent areas) have a binary value of 11111111 for 256-color images (a decimal value of 255, the last palette slot). Higher color depths will result in a binary value of all ones. When this binary value is ANDed with the colors of the pixels in the destination image, the result is the color of the pixels in the destination image, thereby preserving the destination image data. The black pixels (representing opaque image data), on the other hand, have a binary value of 00000000. When ANDed with the destination image, the result is black. Effectively, this creates a

“cutout” area in the destination image where the actual sprite image will appear.

The second step is to draw the OR mask to the destination bitmap using a boolean OR raster operation. The black pixels of the OR mask (representing the transparent areas), when ORed with the destination, will preserve the destination image data, just as the white pixels did in the first step. The actual sprite image data, when ORed with the destination, will be combined with the black “cutout” area produced from the first step. Thus, the sprite image data will be preserved. This results in a perfectly copied sprite with the background image showing through the transparent areas. Figure 3-17 demonstrates the results of each step in the process.

Figure 3-17: Using masks with raster operations

The result, while perhaps not blindingly fast, should be more than adequate under most circumstances, if there are not a large amount of sprites on the screen. This technique is easy to implement, but it does require double the storage space for sprite images. The following example demonstrates how this technique is implemented.

Previous Table of Contents Next

[an error occurred while processing this directive]

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.

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

Listing 3-7: Implementing masking techniques

procedure TForm1.FormPaint(Sender: TObject);

begin

{copy the background image to the destination}

Canvas.Draw(480, 48, imgBackground.Picture.Bitmap);

{combine the AND mask image with the background image in the destination using a boolean AND operation. this carves out an area for the final foreground image}

Canvas.CopyMode := cmSrcAnd;

Canvas.Draw(480+((imgBackground.Width div 2)-(imgAndMask.Width div 2)), 48+((imgBackground.Height div 2)-(imgAndMask.Height div 2)), imgAndMask.Picture.Bitmap);

{copy the result of step one into the background’ image used for step 2}

Canvas.CopyMode := cmSrcCopy;

Canvas.CopyRect(Rect(8, 288, 248, 468), Canvas, Rect(480, 48, 720, 228));

{copy the background’ image resulting from step 1 into the destination}

Canvas.CopyRect(Rect(480, 288, 720, 468), Canvas, Rect(8, 288, 248, 468));

{combine the OR mask image with the result from step 1 in the destination using a boolean OR operation. this copies the foreground image into the area carved out by step 1 while preserving the pixels around it, thereby creating the illusion of transparency.}

Canvas.CopyMode := cmSrcPaint;

Canvas.Draw(480+((imgBackground.Width div 2)-(imgAndMask.Width div 2)), 288+((imgBackground.Height div 2)-(imgAndMask.Height div 2)), imgORMask.Picture.Bitmap);

end;

Title

---Figure 3-18: The transparently displayed image

Palettes

As previously discussed, working under palettized video modes provides a number of advantages and disadvantages. Palettized modes can be a real pain at times, requiring careful planning on the part of art

production and special considerations on the part of the programmer. However, the advantages of working under a 256-color video mode usually outweigh the disadvantages, primarily because it is a very speedy mode to work under, and palette animation can be used for special effects.

The Windows palette manager is a difficult beast to tame. There are many nuances to working in a palettized mode under Windows, making the creation of games using regular GDI functions difficult indeed. As this book’s primary topic is game and graphics programming using DirectX, we will not cover palettes in the depth required for making non-DirectX games. However, it is useful to cover a few of the most basic palette topics in order to prepare the reader for DirectX palettes covered in a later chapter.

Logical vs. System Palettes

There are two different kinds of palettes used by the Windows operating system: the system palette and logical palettes. The system palette is a Windows- maintained global palette containing all of the colors that can be displayed. The first and last ten palette entries are reserved for the system, and contain the 20 static colors that are used for drawing specific user interface elements, such as window title bars, borders, and 3-D user interface elements. The remaining 236 colors can be set in the application.

Logical palettes are created by the application and contain the colors that the application would like to use. When the application specifies the palette it wishes to use, the Windows palette manager merges the logical palette with the current system palette. It accomplishes this by examining each entry in the logical palette and mapping it to an existing entry in the system palette, if a particular color exists in both palettes. If no matching entry is found, the color in the logical palette is placed into the first unused entry in the system palette. If an existing entry is not found and there are no more unused palette slots, the Windows palette manager maps the color to the closest approximation in the system palette. This continues until all colors in the logical palette have been merged with the colors in the system palette.

A logical palette must be selected into a device context before it can be used. If the specified device context identifies an active window, the logical palette is treated as a foreground palette and the Windows palette manager will mark all non-static entries in the system palette as unused. This allows the logical palette to potentially replace all non-static colors of the system palette. If the device context does not identify an active window, the logical palette is treated as a background palette, and can set unused palette slots only if any remain after the foreground palette has been merged.

The palette information stored in a DIB bitmap file can be considered a logical palette. Delphi takes care of most palette-specific concerns automatically, as the TBitmap object will load a bitmap’s palette and put it into use. In the next chapter, we will learn how to load a palette from a bitmap and use it to initialize the DirectX palette.

Note: The TBitmap object has a property called Palette. This property is the handle to the bitmap’s logical palette, and can be used in any GDI function that manipulates logical palettes.

Using a Palette

Delphi encapsulates a large portion of the Windows API, making Windows programming easy and fun.

Unfortunately, not everything in the Windows API has been conveniently simplified in this manner, and this includes palette functionality. This means that in order to use palettes, one must use low-level Windows API calls throughout most of the process.

In order to begin using a custom palette, a logical palette must first be made. First, variables of type PLogPalette and HPALETTE must be declared. The PLogPalette is a pointer to a logical palette structure, which contains a member for the palette version, a member for the number of palette entries, and then an array of type

TPaletteEntry. The TPaletteEntry type is a structure that contains fields for the red, green, and blue components

of a color, and a flag indicating how the entry is to be used. The PLogPalette variable is initialized to the appropriate values, including the desired colors, and is then passed to the CreatePalette function. This returns a handle to the newly created palette, which is then assigned to the HPALETTE variable. At this point, the palette can be selected into a device context using the SelectPalette function. If using a bitmap, the palette handle can be directly assigned to the TBitmap.Palette property. To actually merge the logical palette with the system palette so the new colors can be used, call the RealizePalette function. When the application no longer needs the logical palette, it should be deleted by calling the DeleteObject function. The following example demonstrates creating a logical palette to draw a red gradient fill.

Figure 3-19: The custom palette

Previous Table of Contents Next [an error occurred while processing this directive]

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.

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

Listing 3-8: Using custom palettes

type

TForm1 = class(TForm)

procedure FormCreate(Sender: TObject);

procedure FormPaint(Sender: TObject);

procedure FormDestroy(Sender: TObject);

private

{ Private declarations }

FormPalette: HPALETTE; // a handle to a logical palette public

{ Public declarations }

function GetPalette: HPALETTE; override;

end;

{Whoops! Delphi incorrectly imports the GetSystemPaletteEntries function.

Here’s the correct declaration that gives all of the functionality available with this API function}

function GetSystemPaletteEntries(DC: HDC; StartIndex, NumEntries: UINT;

PaletteEntries: Pointer): UINT; stdcall;

var

Form1: TForm1;

implementation {$R *.DFM}

function TForm1.GetPalette: HPALETTE;

begin

{when something requests the palette of the form pass it back the new logical palette}

Result := FormPalette;

end;

Title

---{link in the GetSystemPaletteEntries function}

function GetSystemPaletteEntries; external gdi32 name GetSystemPaletteEntries’;

procedure TForm1.FormCreate(Sender: TObject);

var

ThePalette: PLogPalette; // a logical palette definition structure iLoop: Integer; // general loop counter

begin

{get enough memory to hold the colors in the first 10 system palette slots, plus 32 of our own. this memory is temporary, and is no longer needed once the palette is created.}

GetMem(ThePalette, SizeOf(TLogPalette)+42*SizeOf(TPaletteEntry));

{initialize the palette version number}

ThePalette^.palVersion := $300;

{we will have a total of 42 entries in our palette}

ThePalette^.palNumEntries := 42;

{get the first 10 system palette entries}

GetSystemPaletteEntries(Form1.Canvas.Handle, 0, 10, @(ThePalette^.palPalEntry));

{we only want 32 new palette entries, and we want them to start

immediately after the first 10 system palette entries. by retrieving the first 10 system palette entries, when we realize our new palette, the first 10 logical palette entries will be mapped to the first 10 system palette entries, and our palette entries will follow}

for iLoop := 0 to 31 do begin

{create a gradient red palette}

ThePalette^.palPalEntry[iLoop+10].peRed := 255-((255 div 32)*iLoop);

ThePalette^.palPalEntry[iLoop+10].peGreen := 0;

ThePalette^.palPalEntry[iLoop+10].peBlue := 0;

{do not match this palette entry to any other palette entry}

ThePalette^.palPalEntry[iLoop+10].peFlags := PC_NOCOLLAPSE;

end;

{create the palette}

FormPalette := CreatePalette(ThePalette^);

{free the temporary memory}

FreeMem(ThePalette, SizeOf(TLogPalette)+42*SizeOf(TPaletteEntry));

{select our new logical palette into the device context}

OldPalette := SelectPalette(Canvas.Handle, FormPalette, FALSE);

{map our logical palette into the system palette}

RealizePalette(Canvas.Handle);

{display a red gradient}

for iLoop := 0 to 31 do begin

Canvas.Brush.Color := $01000000 or iLoop+10;

Canvas.FillRect(Rect(10, iLoop*10+16, 260, (iLoop*10)+36));

end;

{select the previous palette back into the device context}

SelectPalette(Canvas.Handle, OldPalette, FALSE);

end;

procedure TForm1.FormDestroy(Sender: TObject);

begin

{we no longer need the logical palette, so delete it}

DeleteObject(FormPalette);

end;