What is a bitmap and what is a DIB? Is there a difference? Why the Windows API has such functions as CreateBitmap
,CreateCompatibleBitmap
, CreateDIBitmap
, and CreateDIBSection
? And what is the difference betweenSetBitmapBits
and SetDIBits
?
Honestly speaking, for a long time, I had no answers for all those questions. My guess is that I was not the only one. All that became perfectly clear for me only after I had to write my own kernel-mode video driver.
I believe that every programmer who works with the GDI should know all those answers.
What is a video card? Obviously, it is a hardware device that knows to hold an image and transmit it to some display device (CRT monitor or etc.). The image is held in the video card's internal memory (video memory), and the video card produces a signal for the CRT's cathode ray at some rate (refresh rate). This is in general; today's video cards know to do much more.
What is the size of images in general? How many resources do they take? It can be said approximately that an image 640x480 with 8 bits per pixel takes 300k, 800x600 with 16 bpp - 937.5k, and if we talk about 1024x768 with 24bpp - we reach 2.25Mb. It is a lot! If you want to display such an image, you must transfer more than 2Mb through the bus. Suppose now, you want to animate this image somehow (move it, for example). It would take a lot of system resources. And if you want to make some adjustments to the picture on the screen: you must pull it from the video memory, alter it, and then upload it back. Also, think about the drawing complexity: such an image's drawing involves altering hundreds of thousands of pixels, a significant payload for the CPU.
All that led to several inventions in 2D-imaging.
Before we continue, let's agree on the terminology.
TextOut
, you supply it only the string and its placement, whereas there are also font, background and foreground colors, fill mode, clipping region, coordinates transform and etc.TextOut
function (or etc.), or you can directly alter its bits (you must know which). The effect would be the same.TextOut
function, which ends in the graphics card's driver call.I expressly defined the terminology more precisely. This is because functions names in the GDI API are very confusing. From the GDI function names, it could be concluded that a “bitmap” is something that is opposed to a DIB, whereas in fact, a DIB is just a special case of a “bitmap”. In fact, DIBs are opposed to DFBs, both are bitmaps but managed differently.
There had to be CreateDIBxxx
and CreateDFBxxx
functions, whereas GDI calls them CreateDIBxxx
andCreateBitmapxxx
. In some places (but not all), the “Bitmap” term in fact should be replaced by DFB. So – never rely on the function name! Always see the description of what it does.
The Windows video driver architecture is designed to take as much advantages of the video card’s hardware 2D-accelerations as possible. However, the video card does not have to support all known 2D-acceleration techniques; in fact, it does not have to support any of them at all.
Every drawing instruction addressed to the video card ends in some point in the video card’s driver call. This driver is responsible to operate the video card in the proper way (which is manufacturer-dependent) so that the video driver plays the role of the hardware abstraction layer; provides a hardware-independent interface to the hardware.
There is a very basic set of functionality that the video driver must implement. It includes setting a specific video mode to the device, and transferring bits to and from the device. So the video card does not have to support 2D-accelerations at all.
In addition, there is a set of functionality that the video driver may implement. It includes managing its own bitmaps (DFBs), and supporting several drawing operations on them (and between them) such as bit-block transfer (with or without pixel format conversion), text functions, alpha-blending, stretching, gradient fills, and etc. Every such a function, even if it is reported to the OS by the driver, has a right to refuse to work at any time.
What does that mean? First of all, you have to understand that the video card’s driver is involved only for drawing operations on its-managed surfaces, which are the display or a DFB. If you draw something on a DIB, the video card is not involved at all.
Suppose now, you call AlphaBlend
GDI function, whereas the DC represents either a display or a DFB. First, it checks if the video card supports alpha-blending acceleration (if it does. it must have provided a pointer to its function). If it does – the OS calls the driver-supplied DrvAlphaBlend
function, otherwise Windows EngAlphaBlend
function is involved. The video card’s driver examines parameters supplied in its DrvAlphaBlend
function and decides if it wishes to handle it. If it does – it calls the video card in the hardware-dependent way (through so-called miniport driver); otherwise the driver is free to call the EngAlphaBlend
function to do the job, as if it didn’t supply the DrvAlphaBlend
at all.
Now, what EngAlphaBlend
function does? It must perform somehow the alpha-blending operation, although the video driver didn’t support it. In such a case, the OS breaks the requested operation into simpler tasks and calls the video driver again. For instance, it could synthesize the resulting image within the system memory (DIB), and than ask the video driver to draw that DIB. Or it could convert the source parameters (which could be bitmaps as well) into other formats, eliminating the need for the driver to convert from one format to another, and than retry the DrvAlphaBlend
. Again, the driver has a right not to handle the call, and the OS must continue to simplify the driver’s job. In such a way, it can reach a very basic level, which the driver can’t refuse to execute.
So that, as we can see, the Windows driver architecture knows to take advantages of the hardware 2D-accelerations, whereas the only thing that is mandatory for the video card is to know to transfer the image to and from the display.
After we know the difference between DFB and DIB, let’s discuss their advantages.
Drawing operations on DFBs are hardware-accelerated (at least part of them), hence they are usually very fast. Also, transfers between them and between them and the display are very fast too. So, why would we want to use DIBs at all?
The reason is that sometimes drawing output we need is so complex that it can’t be expressed in terms of standard GDI functions. In such cases, it is better to have a system-memory pointer to the image’s bits and alter them directly. Well, this is not an easy way, but it gives the best possible performance, and it is used generally by graphics-oriented programs, such as Photoshop, ACDSee, and etc. Another way is to use nothing but SetPixel
and GetPixel
functions, to build the image pixel by pixel. Doing so for DFBs leads to a catastrophic performance degradation, because every such a call involves a heavy transaction, whereas calling those functions for DIBs is OK.
How do we create a DFB and a DIB? First of all: there is no function that is guaranteed to create a DFB! As we know, the video driver is not required to support DFBs at all. And even if it does, it is allowed to refuse, either because of the video memory shortage or because the pixel format you ask is not supported or not accelerated by the video card. There are functions that attempt to create a DFB. If it fails – they create and return a DIB with adequate parameters. Those functions are CreateBitmap
, CreateBitmapIndirect
, CreateCompatibleBitmap
(when the DC refers to a DFB or a device), and CreateDIBitmap
, although its name is confusing.
BTW, I’ve looked on a couple of video drivers' source code. All of them supported DFBs only with the pixel format identical to the current display’s format, and some of them supported monochrome DFBs regardless of the current video mode.
However, there is a function that is guaranteed to create a DIB. And, there is only one such a function. It is calledCreateDIBSection
. (Again, never rely on a function name, always see its description!)
Now, suppose we have a bitmap handle, HBITMAP
. How do we know what it refers to, a DFB or a DIB? CallingGetObjectType
won’t help, because they both are OBJ_BITMAP
. Fortunately, there is a way to discover who is who. I discovered it occasionally. You can call GetObject
function to fill the BITMAP
structure with the bitmap’s parameters. If the bmBits
member is NULL
– the bitmap is a DFB, otherwise it’s a DIB.
Suppose you have a bitmap for some owner-draw GUI stuff or animations. And this bitmap won’t change; you are not going to draw on it. Indeed, a DBF would be much more efficient here than a DIB. So, if you create it by yourself – useCreateBitmap
to attempt to create a DFB. But sometimes you receive a bitmap handle from other functions, such asLoadBitmap
, LoadImage
, and etc. Some of them usually try to create DFBs, and some don’t. In such cases, the following two conversion functions would be useful:
// This function converts the given bitmap to a DFB. // Returns true if the conversion took place, // false if the conversion either unneeded or unavailable bool ConvertToDFB(HBITMAP& hBitmap) { bool bConverted = false; BITMAP stBitmap; if (GetObject(hBitmap, sizeof(stBitmap), &stBitmap) && stBitmap.bmBits) { // that is a DIB. Now we attempt to create // a DFB with the same sizes, and with the pixel // format of the display (to omit conversions // every time we draw it). HDC hScreen = GetDC(NULL); if (hScreen) { HBITMAP hDfb = CreateCompatibleBitmap(hScreen, stBitmap.bmWidth, stBitmap.bmHeight); if (hDfb) { // now let's ensure what we've created is a DIB. if (GetObject(hDfb, sizeof(stBitmap), &stBitmap) && !stBitmap.bmBits) { // ok, we're lucky. Now we have // to transfer the image to the DFB. HDC hMemSrc = CreateCompatibleDC(NULL); if (hMemSrc) { HGDIOBJ hOldSrc = SelectObject(hMemSrc, hBitmap); if (hOldSrc) { HDC hMemDst = CreateCompatibleDC(NULL); if (hMemDst) { HGDIOBJ hOldDst = SelectObject(hMemDst, hDfb); if (hOldDst) { // transfer the image using BitBlt // function. It will probably end in the // call to driver's DrvCopyBits function. if (BitBlt(hMemDst, 0, 0, stBitmap.bmWidth, stBitmap.bmHeight, hMemSrc, 0, 0, SRCCOPY)) bConverted = true; // success VERIFY(SelectObject(hMemDst, hOldDst)); } VERIFY(DeleteDC(hMemDst)); } VERIFY(SelectObject(hMemSrc, hOldSrc)); } VERIFY(DeleteDC(hMemSrc)); } } if (bConverted) { VERIFY(DeleteObject(hBitmap)); // it's no longer needed hBitmap = hDfb; } else VERIFY(DeleteObject(hDfb)); } ReleaseDC(NULL, hScreen); } } return bConverted; } // This function converts the given bitmap to a DIB. // Returns true if the conversion took place, // false if the conversion either unneeded or unavailable bool ConvertToDIB(HBITMAP& hBitmap) { bool bConverted = false; BITMAP stBitmap; if (GetObject(hBitmap, sizeof(stBitmap), &stBitmap) && !stBitmap.bmBits) { // that is a DFB. Now we attempt to create // a DIB with the same sizes and pixel format. HDC hScreen = GetDC(NULL); if (hScreen) { union { BITMAPINFO stBitmapInfo; BYTE pReserveSpace[sizeof(BITMAPINFO) + 0xFF * sizeof(RGBQUAD)]; }; ZeroMemory(pReserveSpace, sizeof(pReserveSpace)); stBitmapInfo.bmiHeader.biSize = sizeof(stBitmapInfo.bmiHeader); stBitmapInfo.bmiHeader.biWidth = stBitmap.bmWidth; stBitmapInfo.bmiHeader.biHeight = stBitmap.bmHeight; stBitmapInfo.bmiHeader.biPlanes = 1; stBitmapInfo.bmiHeader.biBitCount = stBitmap.bmBitsPixel; stBitmapInfo.bmiHeader.biCompression = BI_RGB; if (stBitmap.bmBitsPixel <= 8) { stBitmapInfo.bmiHeader.biClrUsed = 1 << stBitmap.bmBitsPixel; // This image is paletted-managed. // Hence we have to synthesize its palette. } stBitmapInfo.bmiHeader.biClrImportant = stBitmapInfo.bmiHeader.biClrUsed; PVOID pBits; HBITMAP hDib = CreateDIBSection(hScreen, &stBitmapInfo, DIB_RGB_COLORS, &pBits, NULL, 0); if (hDib) { // ok, we're lucky. Now we have // to transfer the image to the DFB. HDC hMemSrc = CreateCompatibleDC(NULL); if (hMemSrc) { HGDIOBJ hOldSrc = SelectObject(hMemSrc, hBitmap); if (hOldSrc) { HDC hMemDst = CreateCompatibleDC(NULL); if (hMemDst) { HGDIOBJ hOldDst = SelectObject(hMemDst, hDib); if (hOldDst) { if (stBitmap.bmBitsPixel <= 8) { // take the DFB's palette and set it to our DIB HPALETTE hPalette = (HPALETTE) GetCurrentObject(hMemSrc, OBJ_PAL); if (hPalette) { PALETTEENTRY pPaletteEntries[0x100]; UINT nEntries = GetPaletteEntries(hPalette, 0, stBitmapInfo.bmiHeader.biClrUsed, pPaletteEntries); if (nEntries) { ASSERT(nEntries <= 0x100); for (UINT nIndex = 0; nIndex < nEntries; nIndex++) pPaletteEntries[nEntries].peFlags = 0; VERIFY(SetDIBColorTable(hMemDst, 0, nEntries, (RGBQUAD*) pPaletteEntries) == nEntries); } } } // transfer the image using BitBlt function. // It will probably end in the // call to driver's DrvCopyBits function. if (BitBlt(hMemDst, 0, 0, stBitmap.bmWidth, stBitmap.bmHeight, hMemSrc, 0, 0, SRCCOPY)) bConverted = true; // success VERIFY(SelectObject(hMemDst, hOldDst)); } VERIFY(DeleteDC(hMemDst)); } VERIFY(SelectObject(hMemSrc, hOldSrc)); } VERIFY(DeleteDC(hMemSrc)); } if (bConverted) { VERIFY(DeleteObject(hBitmap)); // it's no longer needed hBitmap = hDib; } else VERIFY(DeleteObject(hDib)); } ReleaseDC(NULL, hScreen); } } return bConverted; }