    
        
Device-Independent
        Bitmaps and Palettes
    
Introduction
The purpose of this article is to
show how to create a Device-Independent Bitmap of a given format,
how to get a pointer to the memory bits that make up the image
and how to display it on screen regardless of the current display
mode.
Pick a Flavor
When you create a DIB, you have to
specify the pixel format you want it in. In general there are two
"flavors":
    
Palette images    
RGB imagesPalette images usually contain 256
colors. You supply a palette of 256 colors, and each pixel in the
image takes up one byte which is an index into the palette table.
With RGB images you specify each
of the 3 RGB components for each pixel. RGB images are usually
16-bits or 24-bits per pixel.
Setting up the Palette
If you want to display a 256 color
image then you'll need to set up a palette for it. We need this
palette information twice: once to create the bitmap, and again
to create a palette object we'll use when rendering to the
window. To keep things simple, I'm going to start out by
declaring a 256x3 array of bytes which we'll use later on. The
following code also initializes the entire palette to a smooth
gradiant from black to bright red:
// Set up a palette
BYTE palette[256][3];
for (i=0; i<255; i++)
{
 palette[i][0] = i; // red
 palette[i][1] = 0; // green
 palette[i][2] = 0; // blue
}
Creating the Bitmap
There are several ways to create a
bitmap under Windows95, the most useful is probably with the 
CreateDIBSection()function. 
CreateDIBSection() allows you to specify the
desired pixel format, and it returns both a handle to the bitmap
object and a pointer to the image memory.
Before calling CreateDIBSection()we must set up a BITMAPINFO structure for the desired pixel
format. The BITMAPINFO structure contains two fields: 
bmiHeader(of type BITMAPINFOHEADER) and 
bmiColors (a one element
array of type RGBQUAD). The pixel format itself is stored in the
bmiHeader header field, while the bmiColors field is used to
store the first palette entry. Since this first example is for a
256-color bitmap, we need to allocate a structure containing
enough memory for the BITMAPINFO structure itself as well as an
extra 255 palette entries immediately after it (which we can then
access by indexing off the bmiColors field). The following code
will allocate the amount of memory we need:
// Allocate enough memory for the BITMAPINFOHEADER and 256 RGBQUAD palette entries
LPBITMAPINFO lpbi;
lpbi = (LPBITMAPINFO) new BYTE[sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD))];
Our next step is to initialize the
fields in the BITMAPINFOHEADER member 
bmiHeader.
BITMAPINFOHEADER contains the following fields:
    
biSize: the size of this
        structure, set it to sizeof(BITMAPINFOHEADER).
    
biWidth: the bitmap's width
        in pixels.
    
biHeight: the bitmap's height
        in pixels.
    
biPlanes: the number of
        planes in the image. This should be set to 1 for both
        palette and RGB bitmaps.
    
biBitCount: the number of
        bits per pixel. Set it to 8 for 256 color bitmaps, and 24
        for 24-bit RGB bitmaps.
    
biCompression: the type of
        compression used. We don't want any compression, so set
        it to BI_RGB for both bitmap flavors.
    
biSizeImage: the size of the
        image, in bytes. This is used for compressed bitmaps
        where the image size is not a direct function of the
        width and height of the image itself. Our bitmaps aren't
        compressed, so we can set it to 0.
    
biXPelsPerMeter and
        biYPelsPerMeter: specifies the resolution of the bitmap.
        We'll be displaying these on the screen, so set both
        fields to 0.
    
biClrUsed and biClrImportant:
        these values are used to notify Windows of which colors
        in the image are most important. I won't be covering this
        topic in this article, so for now just set both to 0.
So the code to initialize the lpbi
structure should look something like this:
lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
lpbi->bmiHeader.biWidth = width;
lpbi->bmiHeader.biHeight = height;
lpbi->bmiHeader.biPlanes = 1;
lpbi->bmiHeader.biBitCount = 8;
lpbi->bmiHeader.biCompression = BI_RGB;
lpbi->bmiHeader.biSizeImage = 0;
lpbi->bmiHeader.biXPelsPerMeter = 0;
lpbi->bmiHeader.biYPelsPerMeter = 0;
lpbi->bmiHeader.biClrUsed = 0;
lpbi->bmiHeader.biClrImportant = 0;
The next step is to fill in the bmiColorsarray. We do this by simply copying the values out of the palette
array we set up in the first step:
// Set the bitmap palette
for (i=0; i<255; i++)
{
 lpbi->bmiColors[i].rgbRed      = palette[i][0];
 lpbi->bmiColors[i].rgbGreen    = palette[i][1];
 lpbi->bmiColors[i].rgbBlue     = palette[i][2];
 lpbi->bmiColors[i].rgbReserved = 0;
}
We are now ready to create the
bitmap. The 
CreateDIBSection() function is declared as
follows:
HBITMAP CreateDIBSection(HDC hdc,CONST BITMAPINFO *pbmi,UINT iUsage,VOID *ppvBits,HANDLE hSection,DWORD dwOffset)As you can see, the first
parameter is a device context. What we need here is the device
context for the screen, we can get it by calling 
GetWindowDC()
and passing in NULL as the window handle. pbmi is a pointer to a
BITMAPINFO structure (ie our 
lpbi variable). iUsage is
an identifier that specifies the type of information we've filled
the palette with, so set it to RGB (do so for 24-bit images as
well). ppvBits should point to a variable of type LPBYTE, it will
be filled with a pointer to the bitmap's pixel memory. hSection
and dwOffset are used with file mapping objects, set them both to
0. The following code gets the screen dc, calls 
CreateDIBSection()to create the bitmap then frees both the dc and the lpbi
structure we allocated:
HDC hScreenDC = GetWindowDC(NULL);
hBitmap = CreateDIBSection(hScreenDC, lpbi, DIB_RGB_COLORS, (LPVOID *)&m_pBits, NULL, 0 );
ReleaseDC(NULL,hScreenDC);
delete [](BYTE *)lpbi;
BTW, if you're using MFC then
you'll probably want to declare a variable of type CBitmap and
attach the bitmap handle to it with a call to the 
Attach()member function.
Creating a Palette Object
In order to display the bitmap on
screen we also need a palette object. The palette object is
created with a call to 
CreatePalette(), and we pass in a
pointer to a LOGPALETTE structure. LOGPALETTE contains the
following fields:
    
palVersion: indicates the
        Windows version number. You should set this to 0x300.
    
palNumEntries: the number of
        indices in the palette. This should be set to 256.
    
palPalEntry: an array of
        PALETTEENTRY structures containing the palette
        information.
The palPalEntry field contains a
single element of type PALETTEENTRY, so we need to do the same
trick we used when creating the bitmap to tack an extra 255
entries to the end of the LOGPALETTE structure. The following
code allocates the memory, fills it with the information from our
palette array and creates the palette object itself:
// Create the palette object
LPLOGPALETTE lpLogPal;
lpLogPal = (LPLOGPALETTE)new BYTE[sizeof(LOGPALETTE) + 255*sizeof(PALETTEENTRY)];
lpLogPal->palVersion = 0x0300;
lpLogPal->palNumEntries = 256;
for (i=0; i<255; i++)
{
 lpLogPal->palPalEntry[i].peRed   = palette[i][0];
 lpLogPal->palPalEntry[i].peGreen = palette[i][1];
 lpLogPal->palPalEntry[i].peBlue  = palette[i][2];
 lpLogPal->palPalEntry[i].peFlags = 0;
}
hPalette = ::CreatePalette(lpLogPal);
delete [](BYTE *)lpLogPal;
If you're using MFC then you'll
probably want to attach the palette handle to a CPalette
variable. Or better yet, call the CPalette's CreatePalette()
member function and pass the 
lpLogPal variable into it.
Displaying the Bitmap
The information presented in this
section is enough to display the bitmap if the user's display is
set to an RGB mode (eg 16 or 24 bits per pixel). However, it's 
notenough for displaying in 256-color modes. To do that you need the
code in this section, plus handlers for an extra 2 windows
messages (which I'll show in the next section).
To display the bitmap you
obviously need a device context for the window you're rendering
to. This is usually obtained by calling BeginPaint() in response
to a WM_PAINT message. The first task is to create a compatible
device context for the bitmap. This is done by calling 
CreateCompatibleDC()and passing in the destination dc. Next, we need to select the
bitmap into it by calling 
SelectObject(). Don't forget
to save a copy of the bitmap handle it returns, you'll need to
select it back into the dc when you're done. Finally, we need to
select the palette into the destination dc, this is done with a
call to the 
SelectPalette() function.
Before going any further, let me
explain a bit about the 
SelectPalette() function and how
it works. This function accepts a device context (the destination
dc in our case), a handle to the palette object itself and a
BOOLEAN variable called 
bForceBackground. bForceBackgroundindicates to Windows what it should do with the palette object
we've just given it. If it's set to FALSE, then that means we
want windows to try and perfectly match as many of the palette
indices as possible. Windows normally reserves 20 palette entries
for itself and other applications may add their own entries to
the palette. If your application currently has the input focus
then you get palette priority over other programs, but Windows
still has priority over you to set the 20 reserved entries. When
you set a palette, Windows searches for any matches between your
palette entries and the entries in the current physical palette
(i.e. the actual palette that the screen is set to). If no match
is found, then what Windows does depends on the 
bForceBackground
parameter. If bForceBackground is set to FALSE,
then Windows will go ahead and pick the closest matching index
from the current physical palette. If it's set to TRUE then
Windows will find an empty entry in the physical palette and add
the new index to it. None of this really matters in 16 or 24 bit
modes, since Windows doesn't use a physical palette in those
modes. It makes a big difference though in 256-color modes, since
you want Windows to display as many of your palette colors as
possible. Note that if your window does not have the focus and
another window has set the palette, then Windows will map your
palette indices to that application's palette regardless of what 
bForceBackgroundvalue you passed in.
So obviously we want to always
pass in TRUE when we first call 
SelectPalette(), if our
window doesn't have the focus then it'll just be ignored anyway.
The function returns the handle to the previous palette, and when
we're done rendering we need to set that palette back into the
device context. In this second case though, we 
don'twant windows to go and change all the palette entries back to the
old palette, we want it to simply remap them to the palette we've
just set. So when we call 
SelectPalette() the second
time to restore the old palette, we want to pass in FALSE.
With the palette set we can now
blt the bitmap to the destination dc. Here's some code that uses
BitBlt to display the bitmap:
// Assume hPaintDC is a variable of type HDC, and the dc we're rendering to
HDC hBitmapDC = CreateCompatibleDC(hPaintDC);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hBitmapDC, hBitmap);
HPALETTE hOldPalette = SelectPalette(hPaintDC, hPalette, FALSE);
BitBlt(hPaintDC, 0, 0, BITMAP_WIDTH, BITMAP_HEIGHT, hBitmapDC, 0, 0, SRCCOPY);
SelectPalette(hPaintDC, hOldPalette, TRUE);
SelectObject(hBitmapDC, hOldBitmap);
DeleteDC(hBitmapDC);
Dueling Palettes
Windows is a multi-tasking
environment, and that means that at any given moment there are
typically numerous applications all fighting for control of the
palette. Windows has a very simple method of determining who gets
what: if your window has the input focus, then you get palette
priority, and all other windows have their palettes remapped to
yours. Windows loops through each index of all the other
palettes, finds the entry in your palette that's closest and
makes a note of it. Whenever one of those applications renders
something to the screen Windows looks up each pixel's value in an
internal table and draws the remapped color instead. Since the
other application is using your palette, it's not going to look
anywhere near as good as if it were using it's own, but at least
it's better than the alternative (i.e. a jumbled mess of pixels).
Unfortunately it works both ways,
since you too are subject to having another application rip the
palette out from under your feet. In order to help you deal with
this in an effective manner, Windows provides two messages to
notify you of palette changes: WM_PALETTECHANGED and
WM_QUERYNEWPALETTE.
WM_QUERYNEWPALETTE is sent
whenever your application gets the input focus, thus giving you a
chance to prepare the palette for rendering. When you select a
palette into a device context, Windows assigns each palette index
a space in the physical palette, but it does not actually go
ahead and make changes to the physical palette itself. To do that
you need to call the 
RealizePalette() function. The
correct way to handle the WM_QUERYNEWPALETTE message is to get
the device context for the window, select the palette into it
with 
bForceBackground set to FALSE (remember to save the
old palette handle), realize the palette, select the old palette
back in with 
bForceBackground set to TRUE and release
the device context. Here's the WM_QUERYNEWPALETTE handler code to
do all this:
HDC hDC = GetWindowDC(hWnd);
HPALETTE hOldPalette = SelectPalette(hDC, hPalette, FALSE);
UINT nChanged = RealizePalette(hDC);
SelectPalette(hDC, hOldPalette, TRUE);
ReleaseDC(hWnd, hDC);
(Note that we could avoid handling
this message by simply realizing the palette immediately after
the SelectPalette() call in the WM_PAINT handler. Realizing a
palette however is slow, so we may as well only do it when we
need to).
WM_PALETTECHANGED is sent whenever
any application, including yours, causes changes to the physical
palette. If it's your own application doing the changing then you
should ignore this message, but if it's another application then
you need to realize your palette. Realizing the palette can be
done with the same code as used for the WM_QUERYNEWPALETTE
handler. Since this message is only handled when another window
has received the input focus, we should be able to to still pass
in a 
bForceBackground parameter of FALSE, since it'll be
ignored anyway. However, one document I read stated that it's
possible for the WM_PALETTECHANGED message to be sent to your
window 
before the other window has actually received the
input focus. If this happens and you're passing in FALSE, then
the other windows' palette will be messed up, so it's best to
stay on the safe side and pass in the correct value. When this
message is received, check the wParam value to see if it matches
your windows' HWND. If it's different, then realize the palette:
HDC hDC = GetWindowDC(hWnd);
HPALETTE hOldPalette = SelectPalette(hDC, hPalette, TRUE);
UINT nChanged = RealizePalette(hDC);
SelectPalette(hDC, hOldPalette, TRUE);
ReleaseDC(hWnd, hDC);
To avoid code duplication, you
should probably have each message call a function which accepts
the appropriate 
bForceBackground value and passes it
into the first 
SelectPalette() call.
One last thing to keep in mind: if
the 
RealizePalette() function in either handler returns
non-zero, then you should invalidate and repaint your entire
client area. This is needed to fix any image degradation caused
when by another window changing the palette.
True-Color Tyranny
24-bit DIBs are somewhat easier to
create than their 8-bit counterparts, since you no longer have to
worry about a palette. Displaying them is a bit different though
(that is, if want them to look semi-decent on a 256-color
display).
To create a 24-bit bitmap you
simply set the 
biBitCount field of the BITMAPINFOHEADER
structure to 24. Since this bitmap doesn't require a palette, we
can use an actual BITMAPINFOHEADER structure instead of a
BITMAPINFO structure. The important thing to remember here is to 
save
a copy of the BITMAPINFOHEADER variable
. You need it to
display the bitmap, for reasons which will become apparent in a
minute. Here's the code to create a 24-bit DIB:
BITMAPINFOHEADER m_BitmapInfo; // <- this should be saved, eg make it a member variable
m_BitmapInfo.biSize = sizeof(BITMAPINFOHEADER);
m_BitmapInfo.biWidth = BITMAP_WIDTH;
m_BitmapInfo.biHeight = BITMAP_HEIGHT;
m_BitmapInfo.biPlanes = 1;
m_BitmapInfo.biBitCount = 24;
m_BitmapInfo.biCompression = BI_RGB;
m_BitmapInfo.biSizeImage = 0;
m_BitmapInfo.biXPelsPerMeter = 0;
m_BitmapInfo.biYPelsPerMeter = 0;
m_BitmapInfo.biClrUsed = 0;
m_BitmapInfo.biClrImportant = 0;
HDC hScreenDC = GetWindowDC(NULL);
hBitmap = CreateDIBSection(hScreenDC, (LPBITMAPINFO)&m_BitmapInfo, DIB_RGB_COLORS, (LPVOID *)&m_pBits, NULL, 0);
ReleaseDC(NULL,hScreenDC);
We no longer have to worry about
selecting and realizing palettes, so we can just go ahead and
select the bitmap into a device context and blt it to the
destination device
in our WM_PAINT handler:
HDC hBitmapDC = CreateCompatibleDC(hPaintDC);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hBitmapDC, hBitmap);
BitBlt(hPaintDC, 0, 0, BITMAP_WIDTH, BITMAP_HEIGHT, hBitmapDC, 0, 0, SRCCOPY);
SelectObject(hBitmapDC, hOldBitmap);
DeleteDC(hBitmapDC);
This works fine if the display is
in a true color mode, but if it's in a 256-color mode then
Windows maps each pixel to the closest entry in the physical
palette, i.e. usually one of it's 20 reserved entries. Needless
to say, it looks like crap! In theory, we should should be able
to call 
SetStretchBltMode(), SetBrushOrgEx()and 
StretchBlt() to render with dithering. I have tried
and tried to get this to work, but to no avail. It may be a
feature that only certain drivers support. If so, I have yet to
find one that does!
Fortunately, Video for Windows
provides a function for dithering DIBs. Instead of accepting the
bitmap's device context, it requires a pointer to the memory bits
and, lo-and-behold, it's BITMAPINFOHEADER structure (good thing
you saved it, huh?). To use video for windows you need to add the
following declarations to the top of your source. The first line
includes it's header file while the second links in the
appropriate lib file:
// Include video for windows
#include 
#pragma comment(lib, "vfw32.lib")
We no longer need a device
context, so our display code is reduced to just 3 measly lines:
HDRAWDIB hdd = DrawDibOpen(); 
DrawDibDraw(hdd, hPaintDC,0,0,BITMAP_WIDTH,BITMAP_HEIGHT,&m_BitmapInfo,m_pBits,0,0,BITMAP_WIDTH,BITMAP_HEIGHT,DDF_HALFTONE);
DrawDibClose(hdd);
DrawDib apparently uses some kind
of pattern dither (Bayer?) to display images, but it does a
surprisingly good job of it. On my machine it's reasonably fast
too! In the past I've used it to emulate the primary surface in
debug builds of my DirectDraw code, since it allows me to display
16-bit images in a window even when I'm running in a 256-color
mode.
Full-Screen Without
DirectX
If you want to develop high-speed
full-screen applications then you'll almost certainly want to use
DirectX. However, the techniques discussed in this file can be
used to develop full-screen apps, provided you don't try and
update too much of the screen each frame (otherwise it'll bog
down, particularly at higher resolutions).
The first thing you'll probably
want to do is change the screen resolution. Win32 allows you to
do this on the fly 
provided that you don't try and
change the number of bits-per-pixel. I've already shown how to
handle different pixel depths, so only changing the resolution
will be adequate for our needs. Of course, if the user is running
in a 16 or 24-bit mode and you're rendering an 8-bit bitmap then
there'll be some slowdown since Windows has to convert it. The
code to change the display mode is as follows:
SIZE m_sOldRes; // <-- save this somewhere// Get current display mode
sOldRes.cx = GetSystemMetrics(SM_CXSCREEN);
sOldRes.cy = GetSystemMetrics(SM_CYSCREEN);
// Switch to our desired display mode
DEVMODE mode;
ZeroMemory(&mode, sizeof(mode));
mode.dmSize = sizeof(mode);
mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
mode.dmPelsWidth = SCREEN_WIDTH; // eg 640
mode.dmPelsHeight = SCREEN_HEIGHT; // eg 480
ChangeDisplaySettings(&mode, 0); <- don't forget to check the error returned here
To change it back again once the
program has finished you simply plug in the old resolution
values:
// Restore old display mode
DEVMODE mode;
ZeroMemory(&mode, sizeof(mode));
mode.dmSize = sizeof(mode);
mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
mode.dmPelsWidth = sOldRes.cx;
mode.dmPelsHeight = sOldRes.cy;
ChangeDisplaySettings(&mode, 0);
Finally, we need to create a
window that covers the entire screen, including the task bar.
This is easily done by creating a window with the WS_POPUP style
and maximizing it with a call to ShowWindow(hWnd,
SW_SHOWMAXIMISED).
    
        
     
Copyright (c) 1997 http://www.geocities.com/SiliconValley/2151Mark Feldman  ( mailto:pcgpe@geocities.compcgpe@geocities.com ) - All Rights ReservedThis article is part of http://www.geocities.com/SiliconValley/2151/win95gpe.htmlThe Win95 Game Programmer's Encyclopedia Please retain this footer if you distribute this file.     
        
graphics.html         
graphics.htmlBack
        to Graphics
    
http://www.geocities.com/?source=watermark&browser=NSClick Here! 