控制台应用程序下使用GDI在内存中画图

来源:岁月联盟 编辑:exp 时间:2012-11-05

控制台应用程序下使用GDI在内存中画图

1      背景
以前写MFC应用程序的时候,就接触到GDI了。只不过那个时候,Visual Studio已经帮你生成了MFC应用的基本框架,在Visual Studio生成的View文件里,存在一个方法::OnDraw(CDC* /*pDC*/),Visual Studio会添加注释“// TODO: 在此处为本机数据添加绘制代码”,即是说,请在此处调用CDC进行绘制操作。CDC的创建和初始化MFC都帮你完成了,你只需要添加一些个性化代码即可,当然,这对于一个MFC应用来说,当然够了,但是如果你想写一个不使用MFC的应用,我该怎么获得CDC呢?

很多时候,我们并不需要将绘制的图像显示在窗口,只需要将图像绘制在内存中,然后将内存中的图像保存成图片文件。说到内存绘制,也许有人会说,我直接创建一块内存像素数据,然后计算每个图形的像素结构,填充内存像素数据不就可以了么,没有必要使用GDI呀,对于大部分几何图像来说,这么做完全没问题,但是如果你想绘制字符的时候,这么做就很困难了,因为你很难去计算一个字符的像素结构,所以当你需要绘制字符的时候,你还是得使用GDI。

接下来,我将介绍:(1)如何在控制台应用程序下使用GDI在内存中画图;(2)将内存中的图像存储为图片。

2      使用GDI在内存中画图
GDI是Windows API,因此只要是Win32程序(包括Win32应用程序和Win32控制台程序),都可以使用GDI。为了简单起见,如果你想运行本文介绍的代码,新建一个空的Win32控制台程序即可。我觉得在代码中加注释比在这描述更加明了,那么让我们看看代码吧:


[cpp]
#include <Windows.h>  
#include <tchar.h>  
#include <stdio.h>  
 
/**
 * 将HBITMAP保存为BMP图片,实现细节见第三节
 */ 
static void hbitmapToImage(HDC hdc, HBITMAP hbm); 
 
int main(int argc, char *argv[]) 

    // 创建绘制环境  
    HDC memDC = CreateCompatibleDC(NULL); 
    // 创建画布  
    HBITMAP hbm = CreateBitmap(100, 100, 1, 32, NULL); 
    // 创建一个字体,其中40是字体大小,参数可以根据自己需求调整  
    HFONT hfont = CreateFont(40, 0, 0, 0, FW_THIN, false, false, false,  
        DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,  
        DEFAULT_QUALITY, FF_MODERN, _T("宋体")); 
    // 选择画布和字体,你可以保存之前的画布和字体用于恢复,为了篇幅起见,在此就不这样做了  
    SelectObject(memDC, hbm); 
    SelectObject(memDC, hfont); 
    // 开始绘制操作  
    RECT imgRect = {0, 0, 100, 100};// 设置为画布的宽高  
    SIZE fontPixSize;               // 字符的像素大小,对于一种字体来说,其字符的高是一致的,但是宽基本不同  
    // 将背景填充为黑色  
    FillRect(memDC, &imgRect, (HBRUSH)GetStockObject(BLACK_BRUSH)); 
    // 得到字符串的像素宽高,可以为任意字符串  
    GetTextExtentPoint32(memDC, _T("我"), 1, &fontPixSize); 
    SetTextColor(memDC, RGB(255, 255, 255));    // 设置文本颜色  
    SetBkMode(memDC, TRANSPARENT);              // 设置背景模式  
    // 计算绘制起始位置,使字符在画布的中间;当然你也可以添加更多的绘制操作,如PolyDraw,LineTo等  
    TextOut(memDC, (100 - fontPixSize.cx) / 2, (100 - fontPixSize.cy) / 2, _T("我"), 1); 
    // 将HBITMAP保存为BMP图片  
    hbitmapToImage(memDC, hbm); 
    // 释放资源  
    DeleteObject((HGDIOBJ)hfont); 
    DeleteObject((HGDIOBJ)hbm); 
    DeleteDC(memDC); 
    return 0; 

#include <Windows.h>
#include <tchar.h>
#include <stdio.h>

/**
 * 将HBITMAP保存为BMP图片,实现细节见第三节
 */
static void hbitmapToImage(HDC hdc, HBITMAP hbm);

int main(int argc, char *argv[])
{
 // 创建绘制环境
 HDC memDC = CreateCompatibleDC(NULL);
 // 创建画布
 HBITMAP hbm = CreateBitmap(100, 100, 1, 32, NULL);
 // 创建一个字体,其中40是字体大小,参数可以根据自己需求调整
 HFONT hfont = CreateFont(40, 0, 0, 0, FW_THIN, false, false, false,
  DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
  DEFAULT_QUALITY, FF_MODERN, _T("宋体"));
 // 选择画布和字体,你可以保存之前的画布和字体用于恢复,为了篇幅起见,在此就不这样做了
 SelectObject(memDC, hbm);
 SelectObject(memDC, hfont);
 // 开始绘制操作
 RECT imgRect = {0, 0, 100, 100};// 设置为画布的宽高
 SIZE fontPixSize;    // 字符的像素大小,对于一种字体来说,其字符的高是一致的,但是宽基本不同
 // 将背景填充为黑色
 FillRect(memDC, &imgRect, (HBRUSH)GetStockObject(BLACK_BRUSH));
 // 得到字符串的像素宽高,可以为任意字符串
 GetTextExtentPoint32(memDC, _T("我"), 1, &fontPixSize);
 SetTextColor(memDC, RGB(255, 255, 255)); // 设置文本颜色
 SetBkMode(memDC, TRANSPARENT);    // 设置背景模式
 // 计算绘制起始位置,使字符在画布的中间;当然你也可以添加更多的绘制操作,如PolyDraw,LineTo等
 TextOut(memDC, (100 - fontPixSize.cx) / 2, (100 - fontPixSize.cy) / 2, _T("我"), 1);
 // 将HBITMAP保存为BMP图片
 hbitmapToImage(memDC, hbm);
 // 释放资源
 DeleteObject((HGDIOBJ)hfont);
 DeleteObject((HGDIOBJ)hbm);
 DeleteDC(memDC);
 return 0;
}
3      将HBITMAP保存为BMP图片
第2节描述了如何使用GDI在内存中画图,本节介绍如何将HBITMAP保存为BMP图片。代码如下:


[cpp]
/**
 * 将HBITMAP保存为BMP图片
 */ 
void hbitmapToImage(HDC hdc, HBITMAP hbm) 

    // 得到hbm中每个像素所占的位数  
    int bmPixBitCount = GetDeviceCaps(hdc, BITSPIXEL) * GetDeviceCaps(hdc, PLANES); 
    int wBitCount = 0; 
    if(bmPixBitCount <=  1)                                                  
        wBitCount = 1;            
    else if(bmPixBitCount <=  4)                              
        wBitCount = 4;            
    else if(bmPixBitCount <=  8)                              
        wBitCount = 8;            
    else                                                                                                                            
        wBitCount = 24; 
    //////////////////////////////////////////////////////////////////////////  
    BITMAP bm; 
    GetObject(hbm, sizeof(bm), &bm); 
    int bmRowCount = ((bm.bmWidth * wBitCount + 31) / 32) * 4; // 一行所占的字节数,BMP像素数据按四字节对齐  
    int bmByteCount =  bmRowCount * bm.bmHeight; // 像素数据的大小  
    char *buf = new char[bmByteCount]; 
    // 位图信息头结构,定义参考MSDN  
    BITMAPINFOHEADER bi; 
    bi.biSize= sizeof(BITMAPINFOHEADER);        
    bi.biWidth = bm.bmWidth;        
    bi.biHeight =  bm.bmHeight;        
    bi.biPlanes =  1;        
    bi.biBitCount = wBitCount;        
    bi.biCompression= BI_RGB;        
    bi.biSizeImage=0;        
    bi.biXPelsPerMeter = 0;        
    bi.biYPelsPerMeter = 0;        
    bi.biClrImportant = 0;        
    bi.biClrUsed =  0; 
    GetDIBits(hdc, hbm, 0, bm.bmHeight, buf, (BITMAPINFO*)(&bi), DIB_RGB_COLORS); // 得到像素值,保存在buf中  
    // 位图文件头结构,定义参考MSDN  
    BITMAPFILEHEADER bf; 
    bf.bfType = 0x4D42; // BM  
    bf.bfSize = bmByteCount; 
    bf.bfReserved1 = 0; 
    bf.bfReserved2 = 0; 
    bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); 
    // 写入文件  
    FILE *bmFile = fopen("demo.bmp", "w+b"); 
    if (bmFile) 
    { 
        fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, bmFile);   // 写入位图文件头  
        fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, bmFile);   // 写入位图信息头  
        fwrite(buf, sizeof(char), bmByteCount, bmFile);     // 写入像素数据  
        fclose(bmFile); 
    } 
    delete []buf; 

/**
 * 将HBITMAP保存为BMP图片
 */
void hbitmapToImage(HDC hdc, HBITMAP hbm)
{
 // 得到hbm中每个像素所占的位数
 int bmPixBitCount = GetDeviceCaps(hdc, BITSPIXEL) * GetDeviceCaps(hdc, PLANES);
 int wBitCount = 0;
 if(bmPixBitCount <=  1)                                                
  wBitCount = 1;          
 else if(bmPixBitCount <=  4)                            
  wBitCount = 4;          
 else if(bmPixBitCount <=  8)                            
  wBitCount = 8;          
 else                                                                                                                          
  wBitCount = 24;
 //////////////////////////////////////////////////////////////////////////
 BITMAP bm;
 GetObject(hbm, sizeof(bm), &bm);
 int bmRowCount = ((bm.bmWidth * wBitCount + 31) / 32) * 4; // 一行所占的字节数,BMP像素数据按四字节对齐
 int bmByteCount =  bmRowCount * bm.bmHeight; // 像素数据的大小
 char *buf = new char[bmByteCount];
 // 位图信息头结构,定义参考MSDN
 BITMAPINFOHEADER bi;
 bi.biSize= sizeof(BITMAPINFOHEADER);      
 bi.biWidth = bm.bmWidth;      
 bi.biHeight =  bm.bmHeight;      
 bi.biPlanes =  1;      
 bi.biBitCount = wBitCount;      
 bi.biCompression= BI_RGB;      
 bi.biSizeImage=0;      
 bi.biXPelsPerMeter = 0;      
 bi.biYPelsPerMeter = 0;      
 bi.biClrImportant = 0;      
 bi.biClrUsed =  0;
 GetDIBits(hdc, hbm, 0, bm.bmHeight, buf, (BITMAPINFO*)(&bi), DIB_RGB_COLORS); // 得到像素值,保存在buf中
 // 位图文件头结构,定义参考MSDN
 BITMAPFILEHEADER bf;
 bf.bfType = 0x4D42; // BM
 bf.bfSize = bmByteCount;
 bf.bfReserved1 = 0;
 bf.bfReserved2 = 0;
 bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
 // 写入文件
 FILE *bmFile = fopen("demo.bmp", "w+b");
 if (bmFile)
 {
  fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, bmFile); // 写入位图文件头
  fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, bmFile); // 写入位图信息头
  fwrite(buf, sizeof(char), bmByteCount, bmFile);  // 写入像素数据
  fclose(bmFile);
 }
 delete []buf;
}
程序运行结果如图1所示:

 


图1:程序运行结果