位图(一)

位图又可以分为 DDB(Device-Dependent Bitmap) 设备相关位图 DIB(Device-Independent Bitmap) 设备无关位图

位图(bitmap) 是一个二维矩形数组

当现实生活中的图像被存放为位图时,图像被分成网络,像素则是基本采样单元。

位图中每个像素的值代表网格的一个单元中的图像的平均颜色。

位图有空间尺寸,也就是图像的宽和高,用像素表示。

位图除了有空间尺寸外,还有颜色维度,也就是每个像素所需的位的数目,有时候又叫位图的颜色深度,或位数。

位图的每个像素都有同样的颜色位数。每个像素只有一位的位图称为单色位图。

要想有更多颜色,就需要每个像素有更多的位。Windows 引进了 调色板管理器,每个数值对应一个颜色。

通常 0 对应黑色,1 对应于白色。可以看看这个单色位图:

数据类型是 static BYTE 的二维数组。

从左到右读这些位时,可以给每八位赋一个十六进制字节。

如果位图的宽度不是十六的倍数,在右边添零以得到偶数个字节。

至于如何将这些数值让计数机显示成位图,后面将进行介绍。

可是如果要用更多颜色怎么表示呢?    例如这个图:

虽然实际上像素点和单元格没有这么大,但是这个图便于大家理解。

那么计算机如何用 0 和 1 才能显示出这个图呢?为此Windows 制作了相关规定。 

由于在 DDB 中没有颜色表,所以它不能把图像中的位图位和颜色对应起来。因此使用 DDB 不太合适。

可以把 DIB 想象为一种文件格式。它的扩展名是 BMP。

Windows 应用程序中用到的位图一般都是作为 DIB 文件存储在可执行文件的只读资源中。

DIB 文件有四个主要部分:

  • 文件头
  • 信息头
  • RGB 颜色表(有时可能没有)(颜色表是根据图像数据得到的,所以我们用数据来显示图像时,不用设置)
  • 位图像素位

文件头结构体定义如下:

typedef struct tagBITMAPFILEHEADER {
  WORD  bfType;  // 文件签名,必须是 BM
  DWORD bfSize;  // 位图文件的长度(以字节为单位)
  WORD  bfReserved1; // 必须是0
  WORD  bfReserved2; // 必须是0
  DWORD bfOffBits;  // 从 BITMAPFILEHEADER 结构的开头到位图位的偏移量 (以字节为单位)
} BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

信息头结构体定义如下:

typedef struct tagBITMAPCOREHEADER {
  DWORD bcSize; // 该结构体大小 = 12
  WORD  bcWidth; // 位图的宽度(以像素为单位)
  WORD  bcHeight; // 位图的高度(以像素为单位)
  WORD  bcPlanes; // 目标设备的平面数。必须为 1
  WORD  bcBitCount; // 每个像素的位数。此值必须为1、4、8或24。
} BITMAPCOREHEADER, *LPBITMAPCOREHEADER, *PBITMAPCOREHEADER;

对于前三种情形(位数为1,4,8), BITMAPCOREHEADER 后面是一个颜色表。而 24 位 DIB 没有颜色表。

颜色表结构体如下:

typedef struct tagRGBTRIPLE {
  BYTE rgbtBlue;
  BYTE rgbtGreen;
  BYTE rgbtRed;
} RGBTRIPLE, *PRGBTRIPLE, *NPRGBTRIPLE, *LPRGBTRIPLE;

 例如:如果 bcBitCount = 8; 则 RGB 的范围是 RGB(0~255, 0~255, 0~255);

信息头和颜色表结合在一起的结构体:

typedef struct tagBITMAPCOREINFO {
  BITMAPCOREHEADER bmciHeader;
  RGBTRIPLE        bmciColors[1];
} BITMAPCOREINFO, *LPBITMAPCOREINFO, *PBITMAPCOREINFO;

如果需要为一个 8 位 DIB 的 PBITMAPCOREINFO 结构分配内存,则可以这样:

PBMI = (BITMAPCOREINFO *)malloc( sizeof(BITMAPCOREINFO) + 255*sizeof(RGBTRIPLE));

在介绍位图的像素位之前,先了解一下它的存储方式。

在 DIB 中,图像的底行是文件的第一行,图像的顶行是文件的最后一行。

这是一种由下往上的组织方式。可以想象一下笛卡尔(直角)坐标系。

位图像素位和 DDB 相同,是一组表示像素的数据。

数据类型同样是 static BYTE 的二维数组。至于如何将该数组和文件头结合起来,这就涉及到一些函数。

别着急,现在先简单介绍下 DDB 怎么使用吧!

DDB 是 Windows 图形设备接口定义的若干图形对象之一。

这些图形对象在 GDI 模块内部存储,应用程序用数值句柄来引用它们。

创建 DDB:

我们可以用 HBITMAP("指向位图的句柄") 的变量存储一个指向 DDB 的句柄。

然后通过调用 DDB 创建函数之一(CreateBitmap) 来取得句柄。创建完之后自然要删除 DeleteObject(hBitmap);

CreateBitmap() 介绍:

功能:创建一个带有特定宽度、高度和颜色格式的位图。

函数原型:HBITMAP CreateBitmap(
                  int        nWidth,   // 指定位图宽度、单位为像素。
                  int        nHeight,  // 指定位图高度、单位为像素。
                  UINT       cPlanes,  // 指定该设备使用的颜色位面数目。
                  UINT       nBitCount,  // 指定用来区分单个像素点颜色的位数(比特数目)。
                  CONST VOID *lpBits  // 位图内存指针:指向 static BYTE 类型的数值。可以为 NULL。
                  );

返回值:如果函数成功,那么返回值是位图的句柄;如果失败,那么返回值为NULL。

事实上,调用 CreateBitmap() 时,你只能按照两种方式设置参数:

cPlanes 和 cBitsCount 值都等于一(指明一个单色位图)。

cPlanes 和 cBitsCount 值等于某个设备环境中的值,这个值可以通过 PLANES 和 BITSPIXEL 索引调用 GetDeviceCaps() 得到。

在更加现实的情况下,将只是针对第一种情况调用 CreateBitmap()。对于第二种情况,可以用 CreateCompatibleBitmap() 来化繁为简:

该函数创建一个与设备兼容的位图,该设备的设备环境句柄由第一个参数给出。

CreateCompatibleBitmap() 使用设备环境句柄获得 GetDeviceCaps() 返回的信息,然后把这些信息传给 CreateBitmap()。

除了和真实的设备环境有相同的内存组织方式外,DDB 在其他方面和设备环境无关。

CreateCompatibleBitmap() 介绍:

功能:该函数创建与指定的设备环境相关的设备兼容的位图。

函数原型:HBITMAP CreateCompatibleBitmap(
                  HDC hdc,  // 设备环境句柄。
                  int cx,   // 指定位图的宽度,单位为像素。
                  int cy  // 指定位图的高度,单位为像素。
                  );

返回值:如果函数成功, 则返回值是兼容位图 (DDB) 的句柄。如果函数失败, 返回值为 NULL。

CreateBitmapIndirect() 介绍:

功能:创建一个具有特定宽度、高度和颜色格式的位图。

函数原型:HBITMAP CreateBitmapIndirect(
                  CONST BITMAP *lpbm  // 指向 BITMAP 结构的指针。该结构包含有关位图的信息。
                   );

返回值:如果函数执行成功,那么返回值是指向位图的句柄;如果函数执行失败,则返回值为NULL。

BITMAP 结构体:

typedef struct tagBITMAP{
LONG bmType; //位图类型,必须为0
LONG bmWidth; //位图宽度
LONG bmHeight; //位图高度
LONG bmWidthBytes; //每一行像素所在的byte数,必须为偶数
WORD bmPlanes; //颜色平面数
WORD bmBitsPixel; //像素的位数
LPVOID bmBits; //位图内存指针:指向 static BYTE 类型的数值。可以为 NULL。
}BITMAP,*PBITMAP;

关于第四个参数可以这样计算:

bmWidthBytes = 2*((bmWidth * bmBitsPixel + 15)  / 16);

这个公式可以这样理解:

由于每个像素行都必须分配偶数个字节:

为了得到个偶数,将分子分母都乘2但因隐式转换问题:

当 (bmWidth * bmBitsPixel) 小于16时会直接去 除小数部分导致结果为0。为了向上取整,在加个15。即得该公式。

 而 DIB 每个像素行的字节数总是 4 的倍数,同理:

RowLength = 4*((bmWidth*bmBitsPixel) + 31) / 32) ;

另外我们可以这样填充 BITMAP 结构体:

GetObject(hBitmap, sizeof(BITMAP), &Bitmap);

不过我们仍需填充一项,那就是 bmBits。因为该函数填充该结构体,这项会为 NULL。

位图的位:

如果使用 CreateBitmap() 或 CreateBitmapIndirect() 时,没有给出位图内存的指针。

我们可以使用下面的函数继续对该值进行设置:

SetBitmapBits() 介绍:

功能:将位图的颜色数据位设置为指定的值。

函数原型:LONG SetBitmapBits(
                  HBITMAP  hbm,   // 指向要设置的位图的句柄。
                  DWORD  cb,  // 参数 pBits 指向的 static BYTE 类型数组的字节数。
                  CONST VOID *pvBits  // 位图内存指针.
                  );

返回值:如果该函数执行成功,则返回值就是在设置位图位时使用的字节数;如果失败,则返回值为0。

GetBitmapBits() 介绍:

功能:将指定位图的位拷贝到缓冲区里。

函数原型:LONG GetBitmapBits(
                  HBITMAP hbit,  // 指向要获取的位图的句柄。
                  LONG  cb,   // 参数 pBits 指向的 static BYTE 类型数组的字节数。
                  LPVOID  lpvBits // 指向接收位图位数据的缓冲区指针。
                 );

返回值:返回值:如果该函数执行成功,那么返回值就是拷贝到缓冲区的字节数;如果该函数执行失败,那么返回值为0。

总之:对于彩色 DDB,不应该用 CreateBitmap() 或 CreateBitmapIndirect() 或 SetBitmapBits() 来设置像素位。

           只有对单色 DDB,才可以放心地设置像素位。

位图操作相关函数

BitBlt() 介绍:

功能:执行从指定的源设备上下文到目标设备上下文中对应于像素矩形的颜色数据的位块传输。

函数原型:BOOL BitBlt(
                  HDC   hdc,   // 指向目标设备环境的句柄。
                  int   x,     // 指定目标矩形区域左上角的X轴逻辑坐标。
                  int   y,     // 指定目标矩形区域左上角的y轴逻辑坐标。
                  int   cx,   // 指定源在目标矩形区域的逻辑宽度。
                  int   cy,   // 指定源在目标矩形区域的逻辑高度。
                  HDC   hdcSrc,   // 指向源设备环境的句柄。
                  int   x1,    // 指定源矩形区域左上角的X轴逻辑坐标。
                  int   y1,   // 指定源矩形区域左上角的y轴逻辑坐标。
                  DWORD rop // 指定光栅操作代码。
                  );

参数

rop

这些代码将定义源矩形区域的颜色数据,如何与目标矩形区域的颜色数据组合以完成最后的颜色。

BLACKNESS:表示使用与物理调色板的相关的色彩来填充目标矩形区域,(对缺省的物理调色板而言,该颜色为黑色)。
DSTINVERT:表示使目标矩形区域颜色取反。
MERGECOPY:表示使用布尔型的AND(与)操作符将源矩形区域的颜色与特定模式组合一起。
MERGEPAINT:通过使用布尔型的OR(或)操作符将反向的源矩形区域的颜色与目标矩形区域的颜色合并。
NOTSRCCOPY:将源矩形区域颜色取反,于拷贝到目标矩形区域。
NOTSRCERASE:使用布尔类型的OR(或)操作符组合源和目标矩形区域的颜色值,然后将合成的颜色取反。
PATCOPY:将特定的模式拷贝到目标位图上。
PATPAINT:通过使用布尔OR(或)操作符将源矩形区域取反后的颜色值与特定模式的颜色合并。
                      然后使用OR(或)操作符将该操作的结果与目标矩形区域内的颜色合并。
PATINVERT:通过使用XOR(异或)操作符将源和目标矩形区域内的颜色合并。
SRCAND:通过使用AND(与)操作符来将源和目标矩形区域内的颜色合并。
SRCCOPY:将源矩形区域直接拷贝到目标矩形区域。
SRCERASE:通过使用AND(与)操作符将目标矩形区域颜色取反后与源矩形区域的颜色值合并。
SRCINVERT:通过使用布尔型的XOR(异或)操作符将源和目标矩形区域的颜色合并。
SRCPAINT:通过使用布尔型的OR(或)操作符将源和目标矩形区域的颜色合并。
WHITENESS:使用与物理调色板中颜色填充目标矩形区域。(对于缺省物理调色板来说,这个颜色就是白色)。
返回值:非零表示成功,零表示失败。

StretchBlt() 介绍:

功能:该函数从源矩形中复制一个位图到目标矩形,必要时按目标设备设置的模式进行图像的拉伸或压缩。

函数原型:BOOL StretchBlt(
                  HDC   hdcDest,  // 指向目标设备环境的句柄。
                  int   xDest,   // 指定目标矩形左上角的X轴坐标,按逻辑单位表示坐标。
                  int   yDest,  // 指定目标矩形左上角的y轴坐标,按逻辑单位表示坐标。
                  int   wDest,  // 指定目标矩形的宽度,按逻辑单位表示宽度。
                  int   hDest,  // 指定目标矩形的高度,按逻辑单位表示高度。
                  HDC   hdcSrc,  // 指向源设备环境的句柄。
                  int   xSrc,  // 指向源矩形区域左上角的X轴坐标,按逻辑单位表示坐标。
                  int   ySrc,  // 指向源矩形区域左上角的y轴坐标,按逻辑单位表示坐标。
                  int   wSrc,  // 指定源矩形的宽度,按逻辑单位表示宽度。
                  int   hSrc,  // 定源矩形的高度,按逻辑单位表示高度。
                  DWORD rop // 指定要进行的光栅操作。
                  );

返回值:非零表示成功,零表示失败。

SetStretchBltMode() 介绍:

功能:设置指定设备环境中的位图拉伸模式。

函数原型:int SetStretchBltMode(
                  HDC hdc,   // 设备环境句柄。
                  int mode   // 指定拉伸模式。
                  );

参数

mode

BLACKONWHITE 或 STRETCH_ANDSCANS(默认值) :

如果两个或多个像素必须被结合成一个像素,StretchBlt 将对像素进行逻辑与操作。

产生的像素只有当所有的初始像素都是白时,才是白色。也就是说黑色像素比白色像素占优势。

这对以白色为底,图像主要是黑色的单色位图来说比较好。

WHITEONBLACK 或 STRETCH_ORSCANS:

如果两个或多个像素必须被结合成一个像素,StretchBlt 将对像素进行逻辑或操作。

产生的像素只有当所有的初始像素都是黑时,才是黑色。也就是白色像素比黑色像素占优势。

这对以黑色为底,图像主要是白色的单色位图来说比较好。

COLORONCOLOR 或 STRETCH_DELETESCANS:

StretchBlt 只是简单地去掉像素行或列,而不做任何逻辑操作。这对彩色位图常常是最佳的方法。

HALFTONE 或 STRETCH_HALFTONE:Windows 根据要结合的源的颜色,计算平均目标颜色。

返回值:如果函数执行成功,那么返回值就是先前的拉伸模式,如果函数执行失败,那么返回值为0。

GetStretchBltMode() 介绍:

功能:获取当前的拉伸模式。

函数原型:int GetStretchBltMode(

                  HDC hdc  // 设备环境句柄。

                  );

返回值:如果函数执行成功,那么返回值是当前伸展模式;如果函数执行失败,那么返回值为0。

BitBlt() 和 StretchBlt() 不是简单地进行位块传输。这些函数实际在以下三个图像之间做位与位的操作:

源:源位图被拉伸或压缩成(如果有必要的话)目标矩形同样的大小。

目标:在BitBlt 或 StertchBlt 调用之前的目标矩形。

图案:在目标设备上当前所选的画刷,在横向和纵向上不断重复,直到大小和目标矩形一样。

有名称的15种光栅操作代码列表如下:

图案(P):

源(S):

目标(D):

11110000

11001100

10101010

布尔

操作

名称
结果 00000000 0 BLACKNESS
  00010001 ~(S|D) NOTSRCERASE
  00110011 ~S NOTSRCCOPY
  01000100 S&~D SRCERASE
  01010101 ~D DSTINVERT
  01011010 P^D PATINVERT
  01100110 S^D SRCINVERT
  10001000 S&D SRCAND
  10111011 ~S|D MERGEPAINT
  11000000 P&S MERGECOPY
  11001100 S SRCCOPY
  11101110 S|D SRCPAINT
  11110000 D PATCOPY
  11111011 P|~S|D PATPAINT
  11111111 1 WHITENESS

PatBlt() 介绍:
功能:使用当前选入指定设备环境中的刷子绘制给定的矩形区域。通过使用给出的光栅操作来对该刷子的颜色和表面颜色进行组合。

函数原型:BOOL PatBlt(

                  HDC hdc,   // 设备环境句柄。

                  int nXLeft,  // 指定要填充的矩形左上角的X轴坐标,坐标按逻辑单位表示。

                  int nYLeft, // 指定要填充的矩形左上角的Y轴坐标,坐标按逻辑单位表示。

                  int nWidth,  // 指定矩形的宽度,按逻辑单位表示宽度。

                  int nHeight,  // 指定矩形的高度,按逻辑单位表示高度。

                  DWORD dwRop  // 指定光栅的操作码。

                  );

参数

dwRop

PATCOPY:将指定的模式拷贝到目标位图中。
PATINVERT:使用布尔XOR(异或)操作符将指定模式的颜色与目标矩形的颜色进行组合。
DSTINVERT:将目标矩形反向。
BLACKNESS:使用物理调色板中有关的颜色填充目标矩形。(对于缺省的物理调色板而言,该颜色为黑色)。
WHITENESS:使用物理调色板中有关的颜色来填充目标矩形。(对于缺省的物理调色板而言,该颜色为白色)

返回值:非零表示成功,零表示失败。

现在我们结合一下相关代码来熟悉一下:

DEMOCODE(一):

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName [] = TEXT ("Bricks2") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("CreateBitmap Demo"), 
                          WS_OVERLAPPEDWINDOW, 
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static BITMAP  bitmap = { 0, 8, 8, 2, 1, 1 } ;
     static BYTE    bits [8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
                                    0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 } ;
     static HBITMAP hBitmap ;
     static int     cxClient, cyClient, cxSource, cySource ;
     HDC            hdc, hdcMem ;
     int            x, y ;
     PAINTSTRUCT    ps ;
     
     switch (message)
     {
     case WM_CREATE:
          bitmap.bmBits = bits ;
          hBitmap = CreateBitmapIndirect (&bitmap) ;
          cxSource = bitmap.bmWidth ;
          cySource = bitmap.bmHeight ;
          return 0 ;

     case WM_SIZE:
          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;
          return 0 ;

     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

          hdcMem = CreateCompatibleDC (hdc) ;
          SelectObject (hdcMem, hBitmap) ;

          for (y = 0 ; y < cyClient ; y += cySource)
          for (x = 0 ; x < cxClient ; x += cxSource)
          {
               BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ;
          }

          DeleteDC (hdcMem) ;
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_DESTROY:
          DeleteObject (hBitmap) ;
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

显示位图可以分下面几步:

  1. 加载位图资源 hBitmap = LoadBitmap(...);
  2. 获取位图信息 GetObject(hBitmap, sizeof(BITMAP), &Bitmap);
  3. 创建内存设备环境 hdcMem = CreateCompatibleDC(hdc);
  4. 把 GDI 位图对象选入内存设备环境 SelectObject(hdcMem, hBitmap);
  5. 使用 BitBlt 显示位图 BitBlt(...);

LoadBitmap() 介绍:
功能:该函数从模块的可执行文件中加载指定的位图资源。

函数原型:HBITMAP LoadBitmap(
                  HINSTANCE hInstance,  // 模块实例的句柄, 其可执行文件包含要加载的位图。
                  LPCSTR    lpBitmapName // 指向字符串(以NULL结束)的指针。该字符串包含了要加载的位图资源名称。

                                                            //  也可以用 MAKEINTRESOURCE() 来标识位图资源名称。
                  );

返回值:如果函数成功, 则返回值是指定位图的句柄。如果函数失败, 返回值为 NULL。

内存设备环境:

由于使用 GDI 位图对象时,需要用到内存设备环境。所以先介绍一下它。

通常,设备环境对应于特定的图形输出设备和设备驱动。内存设备环境只存在于内存。

要创建一个内存设备环境,必须有一个对应于真实设备的设备句柄。

CreateCompatibleDC() 介绍:

功能:创建一个与指定设备兼容的内存设备上下文环境( DC )。

函数原型:HDC CreateCompatibleDC(
                  HDC hdc // 现有 DC 的句柄。如果此句柄为 NULL, 该函数将创建与应用程序当前屏幕兼容的内存 DC。
                  );

返回值:如果函数成功, 则返回值是内存 DC 的句柄。如果函数失败, 返回值为 NULL。

部分代码如下:( 需要 #include "resource.h" ,并且加载相应的位图资源 ,VS2017)

DEMOCODE(二):

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HBITMAP hBitmap;
    static BITMAP Bitmap;
    static int  cxBit, cyBit;
    HDC hdcMem;

    switch (message)
    {
    case WM_CREATE:
        hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
        GetObject(hBitmap, sizeof(BITMAP), &Bitmap);
        cxBit = Bitmap.bmWidth;
        cyBit = Bitmap.bmHeight;
        break;

    case WM_PAINT:
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        // TODO: 在此处添加使用 hdc 的任何绘图代码...
        hdcMem = CreateCompatibleDC(hdc);
        SelectObject(hdcMem, hBitmap);
        BitBlt(hdc, 0, 0, cxBit, cyBit, hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem); EndPaint(hWnd,
&ps); break; case WM_DESTROY:
DeleteObject(hBitmap); PostQuitMessage(
0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }

 DDB 的部分就简单的介绍到这啦。关于 DIB 的部分下次再介绍。

原文地址:https://www.cnblogs.com/M-Anonymous/p/9426398.html