GDI图形图像开发知识总结:
(分析http://www.codeproject.com/Articles/3655/CPPToolTip-v2-1得出)
1:兼容的内存DC默认情况下图像是黑色的。
HDC hDC = ::GetDC(this->GetSafeHwnd());
HDC hMemDC = ::CreateCompatibleDC(hDC);
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC,
m_rcBoundsTooltip.Width(), m_rcBoundsTooltip.Height());
HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hMemDC, hBitmap);
这样 hMemDC 所对应的图像如下图所示。
并不像MSDN上所说的,
Note: When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the hDC that was used to create the memory device context, as shown in the following code:
HDC memDC = CreateCompatibleDC ( hDC );
HBITMAP memBM = CreateCompatibleBitmap ( hDC, nWidth, nHeight );
SelectObject ( memDC, memBM
除非你在这样的情况下再使用
::BitBlt(hMemDC, 0, 0, m_rcBoundsTooltip.Width(), m_rcBoundsTooltip.Height(),
hDC, m_rcBoundsTooltip.left, m_rcBoundsTooltip.top, SRCCOPY);
这样才可以将hDC上的全部颜色信息拷贝到hMemDC中。
结果如图所示
2:HBITMAP与DC的关系?
DC与HBITMAP的关系就好比是叫一个人去某目的地进行演出,而HBITMAP就好比是为这个人演出提供的设备,搭建演出舞台供DC去演绎,展示,同时HBITMAP保存演绎的信息直到调用 DeleteObject(m_hBitmap)并将m_hBitmapBk = NULL;为止;
DC:表示演出目的地,在确定好舞台后才能演绎,展示。
HBITMAP:表示演出舞台,保存演绎的信息,直到调用DeleteObject(m_hBitmap)并将m_hBitmapBk = NULL才丢失原保留的信息。
PrepareDisplayTooltip事先准备的HBITMAP,他提供给hMemDC进行演出,展示,
HBITMAP保存hMemDC演出记录(当然这就好比是最后一帧的图像)。
void CPPToolTip::PrepareDisplayTooltip(LPPOINT lpPoint)
{
CDC * pDC = GetDC();
…
m_hBitmapBk = ::CreateCompatibleBitmap(pDC->GetSafeHdc(), rect.Width(), rect.Height());
HDC hMemDC = ::CreateCompatibleDC(pDC->GetSafeHdc());
//ENG: Creates a background of the tooltip's body
HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hMemDC, m_hBitmapBk);
m_hBitmapBk = ::CreateCompatibleBitmap(pDC->GetSafeHdc(), rect.Width(), rect.Height());
HDC hMemDC = ::CreateCompatibleDC(pDC->GetSafeHdc());
//ENG: Creates a background of the tooltip's body
HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hMemDC, m_hBitmapBk);
HBRUSH hBrush = ::CreateSolidBrush(RGB(255,0,0));
::DeleteObject(hBrush);
//给hMemDC填充红色,同时m_hBitmapBk会保存这一信息
::SelectObject(hMemDC, hOldBitmap);
//选回替代m_hBitmapBk前默认演出舞台,红色信息丢失。
::DeleteDC(hMemDC);
//释放占用的内存,具体参见MSDN:CreateCompatibleDC用法说明
ReleaseDC(pDC);
}
void CPPToolTip::OnRedrawTooltip(HDC hDC, BYTE nTransparency /* = 0 */)
{
if (NULL == hDC)
{
hDC = ::GetDC(this->GetSafeHwnd());
bAutoReleaseDC = TRUE;
} //if
//ENG: Creates memory context
HDC hBkDC = ::CreateCompatibleDC(hDC);
// m_hBitmapBk在前一个函数中保存了红色信息,所以直接选进来就具有这// 样的性质,所以说HBITMAP具有信息保存功能。
HBITMAP hOldBkBitmap = (HBITMAP)::SelectObject(hBkDC, m_hBitmapBk);
DEBUG_SaveBmp(DEBUG_CopyDCToBitmap(hBkDC, m_rcBoundsTooltip), "./start_BkDC.bmp");
}
DEBUG_SaveBmp:最后保存的图像最后如图所示:
m_hBitmapBk不再需要的时候需要调用DeleteObject并将其句柄置为空。
void CPPToolTip::FreeResources()
{
if (NULL != m_hBitmapBk)
{
::DeleteObject(m_hBitmapBk);
m_hBitmapBk = NULL;
} //if
}
3:以下代码做了什么?
HDC hTempDC = ::CreateCompatibleDC(hDestDC);
if (NULL == hTempDC)
{
::DeleteDC(hSrcDC);
return;
} // if
COLORREF* pDestBits = NULL;
HBITMAP hDestDib = CreateDIBSection (
hDestDC, lpbiDest, DIB_RGB_COLORS, (void **)&pDestBits,
NULL, NULL);
if ((NULL != hDestDib) && (NULL != pDestBits))
{
::SelectObject (hTempDC, hDestDib);
::BitBlt (hTempDC, 0, 0, dwWidth, dwHeight, hDestDC, nDestX, nDestY, SRCCOPY);
::SelectObject (hTempDC, hOldTempBmp);
}
被标注为蓝色:
1:hTempDC与hDestDC兼容的显示DC,通过CreateDIBSection获取到hDestDC区域所对应的图形图像数据存储在HBITMAP句柄中,注意这里pDestBits内存区所指向的数据全为0(黑色),但是hDestDC说对应的图像不是黑色的,不知道是不是因为CreateDIBSection使用系统自动分配内存所致(后面的两个参数为NULL),
2:这个句柄随后被选入到hTempDC中。
3:使用BitBlt函数将hDestDC上的图形图像数据拷贝到hTempDC中,这个时候hDestDib也就保留了这图像数据信息,相应得pDestBits指向的内存数据就不再是0(黑色)而就是这副被拷入的图像数据,
4:最后调用SelectObject,hTempDC上原有图像数据信息丢失,但是hDestDib不影响。
4:需要记住几个常用配套函数使用(以上PrepareDisplayTooltip函数是一个例子)
The CreateCompatibleDC function creates a memory device context (DC) compatible with the specified device.
HDC CreateCompatibleDC(
HDC hdc // handle to DC
);
When you no longer need the memory DC, call the DeleteDC function.
2:
HDC GetDC(
HWND hWnd // handle to window
);
After painting with a common DC, the ReleaseDC function must be called to release the DC. Class and private DCs do not have to be released. ReleaseDC must be called from the same thread that called GetDC. The number of DCs is limited only by available memory.
什么是common DC呢,可以参看MSDN主题为: Display Device Contexts。基本上我们平常使用的就是Common DC,private DC是带有CS_OWNDC属性的窗口,绘制的信息可以在DC上保存不丢失,直到窗口被删除。
The SelectObject function selects an object into the specified device context (DC). The new object replaces the previous object of the same type.
HGDIOBJ SelectObject(
HDC hdc, // handle to DC
HGDIOBJ hgdiobj // handle to object
);
Remarks
This function returns the previously selected object of the specified type. An application should always replace a new object with the original, default object after it has finished drawing with the new object.
An application cannot select a bitmap into more than one DC at a time.
The CreateCompatibleBitmap function creates a bitmap compatible with the device that is associated with the specified device context.
HBITMAP CreateCompatibleBitmap(
HDC hdc, // handle to DC
int nWidth, // width of bitmap, in pixels
int nHeight // height of bitmap, in pixels
);
When you no longer need the bitmap, call the DeleteObject function to delete it.
The CreateSolidBrush function creates a logical brush that has the specified solid color.
HBRUSH CreateSolidBrush(
COLORREF crColor // brush color value
The CreateDIBSection function creates a DIB that applications can write to directly. The function gives you a pointer to the location of the bitmap bit values. You can supply a handle to a file-mapping object that the function will use to create the bitmap, or you can let the system allocate the memory for the bitmap.
HBITMAP CreateDIBSection(
HDC hdc, // handle to DC
CONST BITMAPINFO*pbmi, // bitmap data
UINT iUsage, // data type indicator
VOID**ppvBits, // bit values
HANDLE hSection, // handle to file mapping object
DWORD dwOffset // offset to bitmap bit values
);
Remarks
As noted above, if hSection is NULL, the system allocates memory for the DIB. The system closes the handle to that memory when you later delete the DIB by calling the DeleteObject function. If hSection is not NULL, you must close the hSection memory handle yourself after calling DeleteObject to delete the bitmap.
You cannot paste a DIB section from one application into another application.
总结表:
CreateCompatibleDC | DeleteDC(返回值) |
GetDC | ReleaseDC(返回值) |
SelectObject | SelectObject(返回值) |
CreateCompatibleBitmap | DeleteObject(返回值) |
CreateSolidBrush | DeleteObject(返回值) |
CreateDIBSection | DeleteObject(返回值) |
5:实现如图所示效果需要的函数支持
对于这种多边形我们需要做的事,首先就是图形拆解,以上图形需要拆解成二个部分.(上面的圆角矩形及下面的三角形,后面的阴影图形)
上面的圆角矩形及下面的三角形:
///
// Parameters:
//dwDirection - A default direction of a tooltip.
//lpPoint - A mouse position in the screen coordinates.
//lpAnchor - An anchor position in the client coordinates
//rcBody - A rectangle of a tooltip's body in the client coordinates
//rcFull - A rectangle of a full tooltip in the client coordinates
//rcTipArea - A rectangle of a tooltip's info area in the client coordinates
// Return values:
// A real direction of a tooltip //------------------------------------------------------------------
// Explanation:
// 0
// 0 +------------------------------------+
// | |
// | rcBody |
// | |
// | +------------------------------+ |
// | | | |
// | | rcTipArea | |
// | | | |
// | +------------------------------+ |
// | |
// +--+...------------------------------+
// : | / :
// : | / rcFull :
// :..|/................................:
// +- lpAnchor
//
//Gets the tooltip body's region
hrgnBody = ::CreateRoundRectRgn(rcBody.left, rcBody.top, rcBody.right + 1, rcBody.bottom + 1,
m_nSizes[PPTTSZ_ROUNDED_CX], m_nSizes[PPTTSZ_ROUNDED_CY]);
//Gets the tooltip anchor's region
if (m_nSizes [PPTTSZ_HEIGHT_ANCHOR] && m_nSizes [PPTTSZ_WIDTH_ANCHOR])
hrgnAnchor = ::CreatePolygonRgn(ptAnchor, 3, ALTERNATE);
else
hrgnAnchor = ::CreateRectRgn(0, 0, 0, 0);
hRgn = ::CreateRectRgn(0, 0, 0, 0);//返回给HRGN m_hrgnTooltip变量保存
::CombineRgn(hRgn, hrgnBody, hrgnAnchor, RGN_OR);
用这个返回的hRgn给这个m_hrgnTooltip进行保存,同时要重新设置一下窗口,并通过调用SetWindowPos进行重绘,在重绘中进行绘制,这样绘制出来的图像
SetWindowRgn(m_hrgnTooltip, FALSE);
SetWindowPos(rect.x, rect.y, m_hrgnTooltip.width(), m_hrgnTooltip.Height());//注意这里用的是屏幕坐标
后面的阴影图形:
返回的m_hrgnTooltip即为圆角矩形及三角形的组合大小,建立一个与这个一样大小并填充灰色颜色的图形就可以绘制一个与正面图像一样大的灰色图形了,最后再将上下两幅图像进行混色调和(上下两层图像放置在不同DC中,但都与这个窗口的DC兼容,换句话说就是创建两个与窗口DC相兼容的DC),就成了最后要获取的图像。
阴影图像填充色通过参数进行明暗的调节计算方法:
前置条件:
m_nDarkenShadow[取值范围:0~100]—线性,值越大,影音部分越黑
m_nTransparency[取值范围:0~100]—线性,值越小,影音部分越黑
最后的计算颜色的RGB值设为t;
t’=255*(100-m_nDarkenShadow)/100; //(0<=t’<=255)
考虑透明可以这样
t=t’+[(255-t’)*m_nTransparency]/100;
不考虑的话:
t=t’
通过公式可以看出:
t’占一部分,剩下的一部分做透明处理,
在t’确认的条件下,透明度越大,
6:图像混合透明算法
有两个图层,将其各自放入不同的DC中,然后将每层的图像作为一副图像,然后,根据两个图层之间设置的透明度进行融合,
上层(src) |
底层(dest) |
这两幅图像是重合的,为了清楚将他们错开放置。
透明度变量是可以设置的取值范围为(0~100),取值越大,那么上层的图像就越透明,底层图像更加清晰,当为100时,只显示底层图像;相反越小,底层图像就越模糊,上层图像越清晰,当为0时,底层图像完全被上层遮挡。
具体数学算法如下:
参数信息:
pDestBits – 指向底层的图像数据,同时也是最后合成的图像数据
pSrcBits - 指向上层的图像数据
nTransparent – 图层之间透明度(取值范围0~100).
计算因子(不知道这样说是否正确)
//透明度越大,上层因子越小,最后上层占有的颜色分量越轻,反知,下层因子//越大,最后下层占有的颜色分量越重。
Int percent=100-nTransparent;
double src_darken = (double)percent / 100.0;
double dest_darken = 1.0 - src_darken;
destR=GetRValue (clrSrc) * src_darken + GetRValue (clrDest) * dest_darken
destG=GetGValue (clrSrc) * src_darken + GetGValue (clrDest) * dest_darken
destB=GetBValue (clrSrc) * src_darken + GetBValue (clrDest) * dest_darken
示例代码如下:
for (DWORD pixel = 0; pixel < dwWidth * dwHeight; pixel++, pSrcBits++, pDestBits++)
{
*pDestBits = PixelAlpha(*pSrcBits, src_darken, *pDestBits,dest_darken);
} //for
COLORREF CPPDrawManager::PixelAlpha(COLORREF clrSrc, double src_darken, COLORREF clrDest, double dest_darken)
{
return RGB (GetRValue (clrSrc) * src_darken + GetRValue (clrDest) * dest_darken,
GetGValue (clrSrc) * src_darken + GetGValue (clrDest) * dest_darken,
GetBValue (clrSrc) * src_darken + GetBValue (clrDest) * dest_darken);
} //End PixelAlpha
7:图像开发调试代码。
有时候我们不知道中间的图像在绘制过程中呈现出怎么样的情况,比如内存DC中的图像信息,内存DC的信息直到反应到窗口DC才可以看到,为了方便查看中间内存DC到底绘制成什么模样,可以通过抓图的方式来查看。 以下有两段程序可以帮助我们在开发过程中即时查看图像中间的绘制情况。
使用的时候只需要这样调用
DEBUG_SaveBmp(DEBUG_CopyDCToBitmap(hMemDC, m_rcBoundsTooltip), "./start_hMemDC.bmp");
#ifndef _DEBUG_BITMAP_H_
#define _DEBUG_BITMAP_H_
static HBITMAP DEBUG_CopyDCToBitmap(HDC hDC, LPRECT lpRect)
{
if(!hDC || !lpRect || IsRectEmpty(lpRect))
return NULL;
HDC hMemDC; // 内存设备描述表
HBITMAP hBitmap, hOldBitmap; // 位图句柄
int nX, nY, nX2, nY2; // 选定区域坐标
int nWidth, nHeight; // 位图宽度和高度
// 获得选定区域坐标
nX = lpRect->left;
nY = lpRect->top;
nX2 = lpRect->right;
nY2 = lpRect->bottom;
nWidth = nX2 - nX;
nHeight = nY2 - nY;
// 为指定设备描述表创建兼容的内存设备描述表
hMemDC = CreateCompatibleDC(hDC);
// 创建一个与指定设备描述表兼容的位图
hBitmap = CreateCompatibleBitmap(hDC, nWidth, nHeight);
// 把新位图选到内存设备描述表中
hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
// 把指定的设备描述表拷贝到内存设备描述表中
StretchBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, nX, nY, nWidth, nHeight, SRCCOPY);
// 得到内存位图的句柄
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);
// 清除
DeleteDC(hMemDC);
DeleteObject(hOldBitmap);
// 返回位图句柄
return hBitmap;
}
// 把HBITMAP保存成位图
static BOOL DEBUG_SaveBmp(HBITMAP hBitmap, const char *pchFileName)
{
if(!hBitmap || !pchFileName)
return FALSE;
HDC hDC;
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; // 定义文件,分配内存句柄,调色板句柄
// 计算位图文件每个像素所占字节数
hDC = CreateDC("DISPLAY", NULL, NULL, NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1)
wBitCount = 1;
else if (iBits <= 4)
wBitCount = 4;
else if (iBits <= 8)
wBitCount = 8;
else
wBitCount = 24;
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) {
hDC = ::GetDC(NULL);
hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}
// 获取该调色板下新的像素值
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);
::ReleaseDC(NULL, hDC);
}
// 创建位图文件
fh = CreateFile(pchFileName, 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;
}
#endif
8:逻辑坐标,物理坐标,逻辑坐标,窗口坐标。
在使用绘图函数的时候要注意这些坐标的区别,关系及相互转换处理。
9:字体
Size
The size of a font is an imprecise value. It can generally be determined by measuring the distance from the bottom of a lowercase g to the top of an adjacent uppercase M, as shown in the following illustration.
A font's size is specified in units called points. A point is .013837 of an inch. Following the point system devised by Pierre Simon Fournier, it is common practice to approximate a point as 1/72 inch.
To use this font for text-output operations, an application must first create a logical font and then select that font into its device context. A logical font is an application-supplied description of an ideal font. A developer can create a logical font by calling the CreateFont or the CreateFontIndirect functions. In this case, the application would call CreateFontIndirect and supply a pointer to the LOGFONT structure initialized by ChooseFont. In general, it is more efficient to call CreateFontIndirect because CreateFont requires several parameters while CreateFontIndirect requires only onea pointer to LOGFONT.
When initializing the members of the LOGFONT structure, be sure to specify a specific character set in the lfCharSet member. This member is important in the font mapping process and the results will be inconsistent if this member is not initialized correctly. If you specify a typeface name in the lfFaceName member of the LOGFONT structure, make sure that the lfCharSet value matches the character set of the typeface specified in lfFaceName. For example, if you want to select a font such as MS Mincho, lfCharSet must be set to the predefined value SHIFTJIS_CHARSET.
与Font相关的名词解析:
Physical fonts: The fonts stored on the device or in the operating system are called physical fonts.
font mapping: The process of finding the physical font that most closely matches a specified logical
font is called font mapping.
1:使用GDIPlus,需要做如下工作。
GDIPlus相关的gdiplus.lib文件放置在
D:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\Lib目录,随安装一起装入。
我们需要在程序中将其链入(如stdafx.h中)。
#pragma common(lib, “gdiplus.lib);
其次:
需要在使用它的地方加入gdiplus.h头文件,(如stdafx.h中)。
最后还有一点非常重要,如果你前两条都做了,程序可以正常编译,链接,
但是执行的时候却出现了问题,有可能就是缺少执行下面的工作。
在应用程序类中加入 (如WinMain或InitInstance和ExitInstance):
ULONG_PTR m_gdiplusToken;
// Initialize GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput,
NULL);
Gdiplus::GdiplusShutdown(m_gdiplusToken);
具体可参见codeproject
http://www.codeproject.com/Articles/1112/Starting-with-GDI
同样在
http://topic.csdn.net/u/20090604/17/03610006-0B7A-4A00-BD20-168135B27796.html
的这位朋友就是因配置不对造成程序运行错误的一个实例。