Download Tutorial 1 Source - 42Kb
Download Tutorial 2 Source - 10Kb
Download Tutorial 3 Source - 28Kb
Download Tutorial 4 Source - 29Kb
Windows GDI Tutorial 1 - Drawing a bitmap
Bitmaps and palettes are both potentially the most useful
part and most confusing parts of the GDI subsystem to neophyte
coders. In this, and the following GDI tutorials I will explain
how to draw bitmaps onto a window, how to implement bitmap
transparency and how to draw animations in a window without any
flicker or flashing.
This being the first tutorial I start by explaining the basic
concepts of a device context, and how to properly use them.
GDI & DC's
GDI stands for "Graphics Device Interface", DC for
"Device Context". The designers of Windows decided that
it would be nice to have a single way of drawing to all
"things", and thus developed GDI as a universal set of
routines that can be used to draw onto a screen, printer, plotter
or bitmap image in memory.
The GDI library is based around an object called a Device
Context. A Device Context is a handle to a drawing surface on
some device - Device Contexts can typically be obtained for the
display device (the entire screen), printers and plotters. Most
commonly worked with are window dc's (a display DC that merely
represents the area of a single window) and a memory DC that
represents a bitmap as a device.
What these objects all have in common (display, printer,
bitmap, etc) is they have some concept of a "drawing
surface" where output will appear.
Associated with a Device context a number of tools that can be
used to act on the associated drawing surface: Pens, brushes,
fonts etc. In the case of physical devices like a plotter there
will be a one to onw mapping of HPEN
to physical pen. In the case
of the display, or memory DC's, a number of preset pens are
provided, and more can be created on the fly as needed.
BITMAPS
A Bitmap is the in-memory representation of a drawing surface.
By "selecting" a bitmap into a memory DC, the DC then
represents that bitmap as a drawing surface, and all the normal
GDI operations can be performed on the bitmap. GDI also has a
number of functions that can copy areas from the drawing surface
of one DC to another, so bitmaps then are a useful way to store
images in memory that will later be copied to the display (or
other devices).
A Bitmap that can be slected into a DC is called a
"Device Dependent Bitmap" and is represented to the
programmer by an HBITMAP
handle.
There is another kind of Bitmap called a "Device
Independent Bitmap". This type of bitmap is defined in the
windows header files as a number of structs that are filled in by
the programmer. Being "device independent" means there
is no HBITMAP
that can be selected into a "Device
context" so GDI operations cannot be performed on this type
of bitmap. There are a couple of "DIB" specific
functions that can create a DDB (device dependent bitmap) given a
DIB, or copy areas from a DIB onto a DC.
The Tutorial
In this tutorial (GDI01) I demonstrate how to load a bitmap
resource as a device dependent bitmap, and how to display the
bitmap on a window. The bitmap is loaded in the applications
WM_CREATE
handler, and shown in the applications
WM_PAINT
handler.
In GDI tutorial 2 I will demonstrate how to implement bitmap
transparency using Device Dependent Bitmaps.
The tutorial comprises a single window that I create in
main.cpp. Stored in a global variable is the bitmap handle that
is initialized in the OnCreate()
function, used in
OnPaint()
, and destroyed in OnDestroy()
.
The example bitmap shipped with the tutorial is a 256 color image,
and will appear rather flat on a 256 color display. The lack or
proper color on 256 color displays will be fixed in tutorial 3
where I intend to discuss palettes.
Windows GDI Tutorial 2 - Transparency
Simple bitmap graphics is quite powerfull. Soon however one
gets to a situation where one wants to overlap two
non-rectangular images. Windows bitmaps are
"unfortunately" rectangles so what one needs to do is
mark certain areas of the bitmap as "not part of the
image". In other words: transparent.
GDI has no built in transparency support - you have to
implement transparency in bitmaps yourself. Specific versions of
GDI have had support for transparent areas in a bitmap - NT4 for
instance has a specif function, and the VFW kit for Windows 3.11
included an extended devmode option that could be set in a DC to
specify that the color set in SetBkColor was to be transparent.
These methods however are not compatible with other platforms
(notably Windows 95) and should probably be avoided.
A Simple Method
The most simple method to implement a bitmap with
"transparent" areas is to have two bitmsps. One bitmap
specifies the image - and all the "transparent" areas
are set to black. The other bitmap is monochrome / black and
white. This bitmap has white pixels around the edges of the
image. The black pixels forming a siloette of the image.
GDI supports boolean operations when combinig the contents of
DCs surfaces, and we use this to our advantage here. To paint a
"transparent" bitmap pair onto a DC the following
process is performed:
- Blit the monochrome bitmap onto the destination using
SRCAND
as the raster code. The SRCAND
code directs GDI to
set each destination pixel as the binary and of the
destination pixel and the source pixel. In this case
black acts like zero, and white like all 1's: Destination
pixels where the source are black become black.
Destination pixels where the source is white are left
untouched. The effect is of a hole being cut in the
destination image.
- Blit the color image bitmap onto the destination using
SRCPAINT
. The SRCPAINT
raster code directs GDI to set
each destination pixel as the binary OR of the previous
destination value and source pixel. Now, due to the
previous step, wherever the source has non-black pixels
the destination has been zero'd. And zero OR something is
that something. So, this step combines the two images
seamlessly.
A Different method
The above method of having paired bitmaps, a color image
bitmap, and a monochrome mask is in terms of GDI operations the
simplest. Some people however perfer not to supply a 2nd
monochrome mask image: They specify the transparent areas of the
bitmap by assigning a certain color to be transparent. This color
fills the area around the image, and care must be taken not to
use the "magic" color in the image itself.
Also, special care must be taken when using this kind of
bitmap with GDI on low color displays: GDI always creates
"compatible" DDBs (and you the programmer always wants
to use "compatible" bitmaps) in the format of the
display mode. This can result in a loss of color
"resolution" and a whole range of colors might be
mapped to the magic transparent color. It is therefore best to
make sure that the transparent color is one of the twenty system
colors that are guaranteed to always exist.
Monochrome Bitmaps
Dealing with monochrome bitmaps can at first be tricky due to
the lack of explicit documentation dealong with how they interact
with color bitmaps. In the example code I Blit directly from a
color bitmap to a monochrome bitmap, and later in the reverse
direction. To figure out whats going to happen in this situation
you have to know how GDI maps monochrome bitmap pixels to color
bitmap pixels.
The background "color" of a bitmap is white, and is
stored as binary 0. When combined with a color bitmap via a
raster operation (typically in a call to BitBlt) the background
pixels in the monochrome bitmap are first mapped to the
background color of the color bitmaps DC. This is normally set to
white (RGB(255,255,255)), but can easilly be changed by using the
SetBKColor()
API. The foreground pixels of a
monochrome bitmap (binary 1) is mapped to the text color of
destination DC - default is black (RGB(0,0,0)), but once again
the SetTextColor()
API can be used to change that.
When transferring bytes from a color to a monochrome bitmap,
the mapping is simpler. All pixels that are the same color as the
background color are mapped to the background color on the mono
bitmap (0). All other pixels are demed to be foreground.
Raster Operations
Raster operations (
SRCPAINT
,
SRCCOPY
) etc are performed only
after any mapping has taken place. They are performed bytewise on
the image bytes. this is the most efficient means of operation,
but it means that logical raster operations performed on 256
color displays will tend to have unexpected results, as any
palettes are totally ignored by this process. The default twenty
system colors will behave in an expected way, as the system
palette has been arranged specially so the mappings work. Instead
of simply using the 1st twenty colors, the system palette uses
the first ten, and last ten colors, so when a NOT is performed on
black (color index 0) the result of the NOT operation (color
index 255) is the expected white.
The Tutorial
The code demonstrates how to create a monochrome bitmap from
the color image. The code uses a simple method to find out what
color to use as the transparent color - it checks the color of
the top left pixel. The files, when compiled and run should
procude a window with an irritating checkerboard pattern. The
transparent image is painted over this checker pattern. The files
to download are listed at the top of this article.
The relevent functions in main.cpp are heavily commented. Look
in the WM_CREATE
handler where the main bitmap is loaded and a
monochrome version is generated. The WM_PAINT
handler
demonstrates how to blit the two bitmaps correctly. WM_DESTROY
cleans up the two bitmaps. Also look in the RegisterClass()
function of the frame to see where the checkered background is
set.
Whats to come
In the next gdi tutorial I look at:
- loading bitmap resources manually.
- Creating bitmaps and palettes from bitmap resources.
- Using palettes correctly to ensure color fastness on 256
color display hardware.
In tutorial 4 I will examine some advanced uses of DIBs. While
slower than the DDBs weve been using up till now they are
ameanable to a number of tricks that make them quite useful.
Windows GDI Tutorial 3 - Using Palettes and accessing bitmap resources
Trying to figure out how to implement palette support can be a
quite messy process. The documentation that exists is never
entirely clear on what approach one should be taking and what to
do when it doesn't work. So...
Loading resources directly
Bitmap resources, and bitmap files on disk, are stored in the
windows DIB format. As a resource, a bitmap consists of a
BITMAPINFO
structure describing the bitmap followed by the actual image data
as an array of bytes. On disk in a .bmp file, the file starts
with a
BITMAPFILEHEADER
structure, followed by a
BITMAPINFO
structure. The start of the image
data is indicated by a field in the
BITMAPFILEHEADER
structure, and does not necessairly follow the
BITMAPINFO
structure directly. This diffrence introduces some annoying
incompatiblities when dealing with bitmap resources, and bitmap
files.
The LoadBitmap()
function, while simple to use, is too
braindead to be used in a situation where your application
requires palette support, as it creates all bitmaps using the
system default palette which only has 20 colors.
While only a problem on 256 color display setups, its a very ugly problem - all your loaded
bitmaps are displayed with a mere 20 colors.
The solution is to use the resource functions to load the
bitmaps directly using the resource functions to search the exe
file for the bitmap resource, and get pointers to the resource
data. As we know the data is stored in DIB format, we can use the
CreateDIBitmap()
API to create a DDB from the DIB data.
Palettes and Bitmaps
A bitmap to windows is just an array of bytes. In 16 bit and
higher modes the color information of the bitmap data is encoded
directly in the data. In 256 color mode however there is no color
information stored in the bitmap. Each byte in the bitmap is
simply an index into a palette of colors.
So, any operations performed on a bitmap will be performed by
GDI will be done using the current selected logical palette.
Please note that the phrase "logical palette" refers
to a GDI palette object - refrenced by a HPALETTE
handle. The
physical palette refers to the state of the actual display device
palette.
Now, the quickest way to blit a bitmap onto the display would
be a simple memcpy operation. And GDI does this as much as
possible. In order for the results to look pleasing however, the
bytes of the bitmap have to match the correct entries in the
physical palette. To ensure this GDI, when it first realizes a
palette, creates a mapping of logical palette entries to the
system palette at the time. GDI expects that the next time the
palette is realized it will be able to take the same mapping.
The bytes in a bitmap then are drawn from this cache table -
NOT the logical palette.
Anyway. The whole subject is very hairy, and all I can suggest
is a full reading of all the available dox you can find on
palettes if you wish to truly understand the subject.
The following notes may ease some potential confusion:
- Realization is a once off thing. Once
RealizePalette()
has
been called on an HDC
, that HPALETTE
does not need to be
realized again until UnRealizePalette()
is called, or
WM_PALETTECHANGED
or WM_QUERYNEWPALETTE
indicates the
Palette Manager itself has unrealized all palettes and is
starting again. You can select and deselect a HPALETTE
as
many times as you wish without having to call
RealizePalette()
.
- The last parameter to
RealizePalette()
can always be set to
FALSE
. TRUE
would only be used if you were realizing a
window DC and specifically do not want the palette to get
mapped into the physical palette.
- Always make sure the palette that a bitmap was created
with is selected into any DC you select the bitmap into
(the order does not matter).
- BitBlt between memory DCs requires that the target DC has
the same palette selected as the source DC.
- BitBlt from memory DC to Screen DC requires the source
palette is realized.
Windows GDI Tutorial 4 - DIBs
Welcome to GDI tutorial 4 - In this tutorial I am going to
concentrate on Device Independent Bitmaps.
DIBs and Bitmap resources
As should hopefully be clear by now, a bitmap resource is
stored as a device independent bitmap. The resource data contains
the
BITMAPINFO
struct, followed by an array of bytes. Passing
pointers to these two structs allows loaded bitmap resources to
be used directly with all windows API functions that work with
DIBs.
A restriction that should be noted: As resources are paged out
of the exe or dll file they were loaded from, care should be
taken to avoid writing to the memory. Under Win16, all changes
written to a resource might be lost if the resource is unlocked
and relocked. Under Win32, writing to resource memory causes a
memory exception that the operating system handles to create a
duplicate resource.
DIBs and bitmap files
Bitmap files too can be directly used as a DIB once loaded
into memory. The one diffrence between files and resources is
that the file starts with a
BITMAPFILEHEADER
struct. This struct
is directly followed by the
BITMAPINFO
struct containing the
information about the DIB and the color table if present. The
BITMAPFILEHEADER
struct also unfortunatly contains a file offset
to the DIBs byte array, so in a bitmap file the byte array might
not follow directly after the
BITMAPINFO
structure.
Some resource compilers do not handle bitmap files properly if
the bitmap data does not follow directly from the BITMAPINFO
struct. They write the padded out information into the resource -
in that case there is now way for the bitmap loading code to know
that there is a gap between the header and bits, and the image
appears corrupted.
Other Image formats and DDBs
The most efficient way to work with images is to use device
dependent bitmaps. GDI stores DDB bitmaps in the format of the
display device - thus DDBs are the most efficient way to work
with images. GDI however does not let the programmer touch the
bits of a DDB directly. GDI does however have a number of
functions that allows the programmer to transfer date from DIBs
to DDBs and the reverse direction. These transfer operations are
slow, as GDI has to translate each pixel on the soure DDB or DIB
to its nearest representation on the target DIB or DDB.
When loading or saving image formats other that bitmaps from
DDBs the programmer therefore usually finds theirself working with
the data as a DIB.
GDI provides the following functions to transfer bits from
DIBs to DDBs, DDBs to DIBs and DIBs to DCs:
CreateDIBitmap()
- this function creates a compatible
device dependent bitmap, and initializes it with the
passed in DIB.
GetDIBits()
- this functions translates a DDBs data into
a DIB that is passed in.
SetDIBits()
- Like CreateDIBitmap()
, this function
intializes a DDB using the DIB data that is passed in.
SetDIBitsToDevice()
- This function copies a DIB directly
(translating each pixel of course) onto a display device
context.
StretchDIBits()
- This function is similar to
StretchBlt()
, it stretches the source onto a DCs surface
- the source data is a DIB.
DIBs and palettes and converting
When translating data from a DIB onto a DDB in a
SetDIBits()
,
SetDIBitsToDevice()
, or
StretchDIBits()
call, GDI has a lot of work
to do. Even more so if the target display device is operating in
256 (or any other palette) mode.
The logic that GDI uses to convert a pixel is thus: First, GDI
resolves the RGB value of the source (DIB) pixel it is
converting. If the DIB is itself has a color table, the pixel
index is looked up in the color table, and the retrieved RGB
color is used in the GDI operation. Now, RGB value in hand, GDI
looks up the RGB value and matches it to the closest color found
in the device contexts palette. (all the above calls take a
device context - The DC is in a couple of cases merely a carrier
of an hpalette). The color in the logical palette is then matched
to a color in the physical palette, any raster operations (in the
case of SetDIBitsToDevice()
or StretchDIBits()
) are now applied using
the physical index, and the result is stored (on the display or
target DDB).
On non palette devices the situation is much simpler. After
the RGB value of the DIB pixel is found, it is combined with the
target RGB using the given raster operation.
DIB Transparency
A common method of implementing transparency with DDBs is to
have a color bitmap, and an associated monochrome bitmap
containing a mask. The mask bitmap is combined with the target DC
using the
SRCAND
raster operation. All white pixels leave the
destination untouched, all black pixels zero the destination -
effectivly cutting a black hole in the target. The color bitmap
is then combined with the destination using the
SRCPAINT
raster
operation. This operation ORs each color pixel from the color
image with a blacked out pixel in the destination. The source
image itself is black where the destination has been left,
leaving the non-transparent pixels untouched - the transparent
pixles now contain the image.
A similar method is used with DIBs, but the DIB method does
not require two complete DIB's *IF* the DIB has a color table. By
blitting the same DIB data twice, once SRCAND
with a color table
intialized with each tranparent color index set to white, and
"data" indexes set to black, and once SRCPAINT
with the
color table set up with data indexes contianing the correct
color, and transparent indexes containig black, the same effect
is achieved.
Note: On 256 color displays the DIB method
will fail if the logical palette selected into the destination dc
does not contain entries for black and white. This is due to the
fact (mentioned above) that the DIB pixels are first matched to
the closest entriy in the logical palette, and the found entry
from the logical palette is then mapped to the physical palette
(that always has the 20 system colors including black and white)
before the raster operation is performed.
The Tutorial
By now you should recognise the simple tutorial. Once again
you will see the aeroplane over a background of clouds. This time
the aeroplane and clouds are displayed using DIBs. The
demo_OnPaint()
function has all the fun stuff as usual. In addition
to the bmpapi files there is a primitive DIB holder class in
dib.cpp and
dib.h. The files you will need are at the top of this article.