一、VC++中的DC环境及GUI有关的各种对象
在Windows中有各种图形用户界面GUI(Graphics User Interface)对象,当我们在进行绘图时就需要利用这些对象。而各种对象都拥有各种属性,下面首先介绍几种GUI对象和拥有的属性。
(一)、GUI有关的各种对象
在Windows中有各种图形用户界面GUI(Graphics User Interface)对象,当我们在进行绘图时就需要利用这些对象。而各种对象都拥有各种属性,下面首先介绍几种GUI对象和拥有的属性。
字体对象CFont
字体对象CFont用于输出文字时选用不同风格和大小的字体。可选择的风格包括:是否为斜体,是否为粗体,字体名称,是否有下划线等。
刷子CBrush对象
刷子CBrush对象决定填充区域时所采用的颜色或模板。对于一个固定色的刷子来讲它的属性为颜色,是否采用网格和网格的类型如水平的,垂直的,交叉的等。也可以利用8*8的位图来创建一个自定义模板的刷子,在使用这种刷子填充时系统会利用位图逐步填充区域。
画笔CPen
画笔CPen对象在画点和画线时有用。它的属性包括颜色,宽度,线的风格,如虚线,实线,点划线等。
位图CBitmap对象
位图CBitmap对象可以包含一幅图像,可以保存在资源中。
CPalette调色板
CPalette调色板是一种颜色映射接口,它允许应用程序在不影响其他应用程序的前提下,可以充分利用输出设备的颜色描绘能力。
此外系统中还拥有一些库存GUI对象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )选入这些对象,它们包括一些固定颜色的刷子,画笔和一些基本字体。 如:
BLACK_BRUSH 黑色刷子
NULL_BRUSH 空刷子
WHITE_PEN 白色画笔
DEVICE_DEFAULT_FONT 默认字体
在Windows中使用GUI对象必须遵守一定的规则。首先需要创建一个合法的对象,不同的对象创建方法不同。然后需要将该GUI对象选入DC中,同时保存DC中原来的GUI对象。如果选入一个非法的对象将会引起异常。在使用完后应该恢复原来的对象,这一点特别重要,如果保存一个临时对象在DC中,而在临时对象被销毁后可能引起异常。有一点必须注意,每一个对象在重新创建前必须销毁,下面的代码演示了这一种安全的使用方法:
OnDraw(CDC* pDC)
{
a) CPen pen1,pen2;
b) pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//创建画笔对象一
c) pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//创建画笔对象二
d) CPen* pOldPen=(CPen*)pDC->SelectObject(&pen1);//选择对象进DC
e) drawWithPen1...
f) (CPen*)pDC->SelectObject(&pen2);//选择对象进DC
g) drawWithPen2...
h) pen1.DeleteObject();//再次创建前先销毁
i) pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次创建对象
j) (CPen*)pDC->SelectObject(&pen1);//选择对象进DC
k) drawWithPen1...
l) pDC->SelectObject(pOldPen);//恢复
}
OnDraw(CDC* pDC) 函数是VC中最常见的图形输出刷新函数,参数pDC 为CDC类的一个指针,我们通过它进行画图操作。
代码a行定义CPen 类的两个画笔对象pen1,pen2;分别在行b,c 调用CPen 类成员函数CreatePen 创建两个实心画笔, 其颜色RGB值分别为RGB(128,128,128), RGB(128,128,0)。
行d 将新创建的画笔pen1选入当前设备上下文DC环境并将旧画笔保存在pOldPen里,这样在e行输出的图形或文本线条将以pen1的属性填充。f, g 行选入画笔二并输出。i,j 行销毁画笔一并且创建RGB(0,0,0)色的画笔,k行输出。最后一行l行将旧画笔选入当前DC环境,输出完毕。字体对象,刷子对象及位图对象的使用方法同上,具体使用将在下面的实例中描述。
在绘图时都需要一个DC对象,DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。
(二)、DC环境下输出文本
在MFC里有一个设备环境类CDC封装了有关对物理设备的输出。CDC是设备环境类的基类直接由CObject派生。是图形设备接口的关键元素,它代表了物理设备。每一个C++设备环境对象都有相对应Windows设备环境,并通过一个32位类型的HDC句柄来标识。CDC类的虚拟性使我们可以很容易的做到编写同时适用于多种设备的代码。例如OnDraw函数的pDC->TextOut(0,0,"Hello");既可以适用于显示器、还可以适用于打印预览和打印,只需要在CView::OnDraw函数的pDC参数指向不同的对象类。
CClientDC和CWindowDC是显示设备环境类,都是由CDC派生而来,区别在于CClientDC是窗口的客户区不包括边框、标题栏和菜单栏,(0,0)指客户区域的左上角。CWindowDC的(0,0)指整个屏幕的左上角,这意味着我们可以在显示器的任意地方绘图,包括窗口边框、标题栏和菜单栏等等。CWindowDC一般应用在框架窗口,而不是视图窗口。
CDC对象被创建后一定要在合适的时候将它删除掉,如果忘记了删除设备环境对象则会造成内存丢失。下面程序段实现在DC环境下输出文本。
long CImg::OutImgFromText(LPCTSTR vFileName,
LPCTSTR lpText,
LPCTSTR lpBgImg,
long lCSet,
LPCTSTR lpFont,
long lWidth,
long lHeight,
long lLeft,
long lTop,
long llfHeight,
long lWeight,
long l3D)
{
i. m_nWidth = lWidth;
ii. m_nHeight = lHeight;
iii. if((m_nWidth % 8) != 0)
1. m_nWidth = ((int)(m_nWidth/8) + 1) * 8;
iv. if(m_nWidth < 3 * lLeft)
1. m_nWidth = 3 * lLeft;
v. if(m_nHeight < 3 * lTop)
1. m_nHeight = 3 * lTop;
vi. int nFHeight = llfHeight;
vii. if(0 == nFHeight)
1. nFHeight = 1;
viii. int nRealClientWidth = (m_nWidth - 2 * lLeft);
ix. HDC hDC;
x. hDC = CreateCompatibleDC(NULL);
xi. LOGFONT lf;
xii. memset(&lf,0,sizeof(lf));
xiii. lf.lfCharSet = GB2312_CHARSET;
xiv. lf.lfHeight = nFHeight;
xv. lstrcpy(lf.lfFaceName, lpFont);
xvi. lf.lfPitchAndFamily = 8;
xvii. lf.lfWeight = lWeight;
xviii. HFONT hFont = CreateFontIndirect(&lf);
1. HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); //选入字体
xix. CComBSTR bstrText(lpText);
xx. RECT rectClient = {lLeft, lTop, m_nWidth - lLeft, m_nHeight - lTop};
xxi. ::DrawText(
1. hDC,
2. bstrText.m_str,
3. bstrText.Length(),
4. &rectClient,
5. DT_WORDBREAK|DT_LEFT|DT_CALCRECT
6. ); //计算输出距形
xxii. int nRealHeight = rectClient.bottom + lTop;
xxiii. if(m_nHeight < nRealHeight)
1. m_nHeight = nRealHeight;
xxiv. else
1. rectClient.bottom = m_nHeight - lTop;
xxv. HBITMAP hBitmap;
xxvi. hBitmap = CreateDiscardableBitmap(hDC, m_nWidth, m_nHeight);
xxvii. SelectObject(hDC, hBitmap);
xxviii. //---------------------------------
xxix. HBRUSH hBBg = CreateSolidBrush(RGB(255,255,255));
xxx. RECT rectFull = {0, 0, m_nWidth, m_nHeight};
xxxi. FillRect(hDC, &rectFull, hBBg);
xxxii. if(l3D > 0)
xxxiii. {
1. //SetBkColor(hDC, RGB(200,193,193));
2. SetTextColor(hDC, ::GetSysColor(COLOR_3DDKSHADOW));
3. SetBkMode(hDC, OPAQUE);
xxxiv. }
xxxv. else
xxxvi. {
1. SetBkColor(hDC, RGB(255,255,255));
2. SetTextColor(hDC, RGB(0,0,0));
3. SetBkMode(hDC, TRANSPARENT);
xxxvii. }
xxxviii. ::DrawText(
1. hDC,
2. bstrText.m_str,
3. bstrText.Length(),
4. &rectClient,
5. DT_WORDBREAK
6. ); //输出
xxxix. if(l3D > 0)
xl. {
1. SetTextColor(hDC, ::GetSysColor(COLOR_3DHILIGHT));
2. SetBkMode(hDC, TRANSPARENT);
3. rectClient.left = rectClient.left + l3D;
4. rectClient.top = rectClient.top - 1;
5. rectClient.right = rectClient.right + l3D;
6. rectClient.bottom = rectClient.bottom - 1;
7. ::DrawText(
a) hDC,
b) lpText,
c) wcslen(lpText),
d) &rectClient,
e) DT_WORDBREAK);
xli. }
xlii. SelectObject(hDC, hOldFont);
xliii. DeleteObject(hFont);
xliv. DeleteObject(hBBg);
xlv. SaveDCBmp(hDC, hBitmap, vFileName);
xlvi. //SaveDCJPG(hDC, hBitmap, vFileName);
xlvii. DeleteObject(hBitmap);
xlviii. ::ReleaseDC(NULL, hDC);
xlix. return 0;
}
此函数功能:通过输入特定长度的文本,输出图像到指定文件
参数说明:
vFileName: 图像保存文件路径
lpText: 图像输出文本
lpBgImg: 图像背景路径
lCSet: 字符集
lpFont: 字体名称
lWidth: 图像输出宽度
lHeight: 图像输出高度
lLeft: 图像输出左边距,与右边距相同
lTop: 图像输出上边距,与下边距相同
llfHeight: 文本输出字体高度,字体宽度随高度等比例变化
lWeight: 文本重量
l3D: 三D效果,值为0时无三D效果,大于0时其值为字体偏移量
程序i. 至 viii. 行对输入参数合法性进行检查及究正。
行ix. ,x. 定义及创建与指定设备兼容的设备上下文句柄hDC。
行xi. 至 xviii.1 行定义LOGFONT 逻辑字体结构并填充。通过CreateFontIndirect(&lf) 创建字体并调用SelectObject(hDC, hFont)将创建字体选入设备上下文,原字体句柄保存在hOldFont里。
xix. 至 xxiv. 行取得输入文本长度,在当前字体环境下调用DrawText函数计算输出矩形,并将其矩形保存在rectClient里,以便调整DC输出矩形大小。
行xxvi. 利用上面计算出的长宽创建位图句柄,行xxvii.将其选入设备上下文,准备工作完毕,绘图工作正式开始。
在此函数中,画笔及刷子我们使用系统默认设置,不再重复申请。
行xxxii.判断三D偏移量是否大于零,如果不为零,输出三D效果。
行xxxviii.在新矩形下输入文本。如果有三D输出请求,将矩形偏移l3D个像素,再次输出文本,以显示三D效果。
xlii. 行选入旧字体。
xliii. 行以后删除对象保存位图及恢复现场。保存位图功能SaveDCBmp将在下节讨论。
二、位图文件
(一)、位图文件结构
位图文件由三部分组成:文件头 + 位图信息 + 位图像素数据
1、位图文件头
位图文件头主要用于识别位图文件。以下是位图文件头结构的定义:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
其中的bfType值应该是“BM”(0x4d42),标志该文件是位图文件。bfSize的值是位图文件的大小。bfReserved1, bfReserved2 为保留字,值为0。bfOffBits为位图文件大小与DIB(设备无关的位图 Device-indepentent bitmap)位图数据的大小之差。如:
BITMAPFILEHEADER bmfHdr;
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
2、位图信息
位图信息中所记录的值用于分配内存,设置调色板信息,读取像素值等。以下是位图信息结构的定义:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO;
可见位图信息也是由两部分组成的:位图信息头 + 颜色表
2.1、位图信息头
位图信息头包含了单个像素所用字节数以及描述颜色的格式,此外还包括位图的宽度、高度、目标设备的位平面数、图像的压缩格式。以下是位图信息头结构的定义:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
biSize 结构BITMAPINFOHEADER的字节数,即sizeof(BITMAPINFOHEADER)
biWidth 以像素为单位的图像宽度
biHeight 以像素为单位的图像长度
biplanes 目标设备的位平面数
biBitCount 每个像素的位数
对于每个像素的位数,分别有一下意义:
0,用在JPEG格式中
1,单色图,调色板中含有两种颜色,也就是我们通常说的黑白图片
4,16色图
8,256色图,通常说的灰度图
16,64K图,一般没有调色板,图像数据中每两个字节表示一个像素,5个或6个位表示一个RGB分量
24,16M真彩色图,一般没有调色板,图像数据中每3个字节表示一个像素,每个字节表示一个RGB分量
32,4G真彩色,一般没有调色板,每4个字节表示一个像素,相对24位真彩图而言,加入了一个透明度,即RGBA模式
biCompression 图像的压缩格式(这个值几乎总是为0)
biSizeImage 以字节为单位的图像数据的大小(对BI_RGB压缩方式而言)
biXPelsPermeter 水平方向上的每米的像素个数
biYpelsPerMeter 垂直方向上的每米的像素个数
biClrused 调色板中实际使用的颜色数,这个值通常为0
biClrImportant 现实位图时必须的颜色数, 这个值通常为0,表示所有的颜色都是必需的
2.2、颜色表
颜色表一般是针对16位以下的图像而设置的,对于16位和16位以上的图像,由于其位图像素数据中直接对对应像素的RGB(A)颜色进行描述,因而省却了调色板。而对于16位以下的图像,由于其位图像素数据中记录的只是调色板索引值,因而需要根据这个索引到调色板去取得相应的RGB(A)颜色。颜色表的作用就是创建调色板。颜色表是由颜色表项组成的,颜色表项结构的定义如下:
typedef struct tagRGBQUAD { // rgbq
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
rgbBlue 蓝色的强度
rgbGreen 绿色的强度
rgbRed 红色的强度
rgbReserved 保留字,为0
其中需要注意的问题是,RGBQUAD结构中的颜色顺序是BGR,而不是平常的RGB。
3、位图数据
最后,在位图文件头、位图信息头、位图颜色表之后,便是位图的主体部分:位图数据。根据不同的位图,位图数据所占据的字节数也是不同的,比如,对于8位位图,每个字节代表了一个像素,对于16位位图,每两个字节代表了一个像素,对于24位位图,每三个字节代表了一个像素,对于32位位图,每四个字节代表了一个像素。
(二)、存储区域DC到位图文件
认识了位图文件的结构以后,对特定位图文件进行操作就显得简单了。我们通过创建特定的画笔,刷子及位图对象,在DC 环境下进行绘图后,就要将保存在DC 里的图像存储到位图文件中,以便使用及输出到其他媒体。下面代码实现将设图上下文图形保存为位图文件。
BOOL CImg::SaveDCBmp(HDC hDC, HBITMAP hBitmap, LPCTSTR lpFileName)
{
//当前分辨率下每象素所占字节数
int iBits;
//位图中每象素所占字节数
WORD wBitCount;
//定义调色板大小, 位图中像素字节大小 ,位图文件大小 , 写入文件字节数
DWORD dwPaletteSize=0, dwBmBitsSize=0, dwDIBSize=0, dwWritten=0;
//位图属性结构
BITMAP Bitmap;
//位图文件头结构
BITMAPFILEHEADER bmfHdr;
//位图信息头结构
BITMAPINFOHEADER bi;
//指向位图信息头结构
LPBITMAPINFOHEADER lpbi;
//定义文件,分配内存句柄,调色板句柄
HANDLE fh, hDib, hPal,hOldPal=NULL;
//计算位图文件每个像素所占字节数
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
if (iBits <= 1) wBitCount = 1;
else if (iBits <= 4) wBitCount = 4;
else if (iBits <= 8) wBitCount = 8;
else wBitCount = 24;
//wBitCount = 4;
GetObject(hBitmap, sizeof(Bitmap), (LPSTR)&Bitmap);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = Bitmap.bmWidth;
bi.biHeight = Bitmap.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;
dwBmBitsSize = ((Bitmap.bmWidth * wBitCount + 31) / 32) * 4 * Bitmap.bmHeight;
//为位图内容分配内存
hDib = GlobalAlloc(GHND,dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
// 处理调色板
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal)
{
hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE);
}
// 获取该调色板下新的像素值
GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)
+dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);
//恢复调色板
if (hOldPal)
{
::SelectPalette(hDC, (HPALETTE)hOldPal, TRUE);
RealizePalette(hDC);
}
//创建位图文件
fh = CreateFile(lpFileName, GENERIC_WRITE,0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (fh == INVALID_HANDLE_VALUE) return FALSE;
// 设置位图文件头
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
// 写入位图文件头
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
// 写入位图文件其余内容
WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
//清除
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);
return TRUE;
}
保存位图文件前通过GetObject函数取得位图长度, 通过GetDIBits取得位图图像扫描数据,填充BITMAPFILEHEADER(位图文件头结构); BITMAPINFOHEADER (位图信息头结构); 然后写入位图文件头:
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
写入位图文件其余内容:
WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
以 文件头 + 位图信息 + 位图像素数据 的顺序进行存储。
本文作者: