位图、调色板以及区域
一、调色板
在使用256色显卡的计算机上,每个程序允许使用的颜色一共只有256种。但是除了操作系统保留的20种称为静态颜色外其他的236种颜色允许每个应用程序自己决定。于是就有了调色板。应用程序在这种计算机上运行为了获得较好的显示效果必须将自己需要使用的主要颜色选入显卡的调色板当中以供使用。
使用调色板我们首先要知道计算机显卡的类型,以判断是否要使用或者是否能够使用调色板。获取这个信息的方法是调用CDC::GetDeviceCaps(RASTERCAPS) & RC_PALETTE如果这个表达式为非零那么允许使用调色板。
使用调色板我们首先要声名一个CPalette对象,并且调用CreatePalette成员函数创建一个调色板,这个成员函数的参数是一个LOGPALETTE结构,这个结构的形式如下:
typedef struct tagLOGPALETTE {
WORD palVersion; //使用0x300
WORD palNumEntries; //调色板颜色数目
PALETTEENTRY palPalEntry[1]; //调色板的每个颜色信息
} LOGPALETTE;
可以看到在这个结构中只包含了一个配置调色板颜色的信息,这是因为MFC并不知道需要多少种颜色,但是我们必须自己扩展这个结构以容纳所有的颜色,方法如下:
struct
{
LOGPALETTE lp;
PALETTEENTRY pe[2];
}lpl;
LOGPALETTE *plp = (LOGPALETTE *) &lpl;
{
LOGPALETTE lp;
PALETTEENTRY pe[2];
}lpl;
LOGPALETTE *plp = (LOGPALETTE *) &lpl;
这样就能够容纳3种颜色的信息了。PALETTEENTRY的结构如下:
typedef struct tagPALETTEENTRY {
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY;
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY;
这个结构当中的peFlags一般被设置为0。在创建完调色板对象以后我们就可以将CPalette对象选入CDC当中选入调色板对象时需要调用CDC::SelectPalette(CPalette *,BOOL)成员函数而非对于普通GDI对象的SelectObject成员函数,并且调用CDC::RealizePalette()函数来实现调色板。在使用调色板的程序当中绘图,我们不能使用RGB宏而要使用PALETTERGB或者PALETTEINDEX宏,前者使用调色板中相近颜色来实现RGB颜色,后者使用调色板索引值来确定颜色。在使用调色板的程序中我们需要框架窗口响应WM_QUERYPALLETE和WM_PALETTECHANGE消息,前者表示设置调色板,后者表示系统调色板由于前端窗口改变而发生的改变。对他们的处理就是刷新视图窗口:
BOOL CMainFrame::OnQueryNewPalette()
{
m_wndView.Invalidate();
{
m_wndView.Invalidate();
return CFrameWnd::OnQueryNewPalette();
}
}
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
{
CFrameWnd::OnPaletteChanged(pFocusWnd);
{
CFrameWnd::OnPaletteChanged(pFocusWnd);
if (pFocusWnd != this)
m_wndView.Invalidate();
}
m_wndView.Invalidate();
}
有一种动画并不是通过擦除重画的方式来实现,而是通过改变调色板中对应位置的颜色来实现的。要实现这种动画我们首先需要调用CPalette::GetPaletteEntries函数将调色板中指定范围的颜色读取到PALETTEENTRY数组中,然后用AnimatePalette函数将新的颜色信息加载到调色板中。两个函数的原型如下:
UINT GetPaletteEntries(
UINT nStartIndex, //起始位置
UINT nNumEntries, //读取数量
LPPALETTEENTRY lpPaletteColors //PALETTEENTRY数组
) const;
UINT nStartIndex, //起始位置
UINT nNumEntries, //读取数量
LPPALETTEENTRY lpPaletteColors //PALETTEENTRY数组
) const;
void AnimatePalette(
UINT nStartIndex,
UINT nNumEntries,
LPPALETTEENTRY lpPaletteColors
);
UINT nStartIndex,
UINT nNumEntries,
LPPALETTEENTRY lpPaletteColors
);
下面是一个相关的例子:
PALETTEENTRY pe[3]; //这段程序假设调色板包含3种颜色
m_palette.GetPaletteEntries(2,1,pe); //将调色板中的第三个(索引为2)的颜色读取到pe数组的第一个元素
m_palette.GetPaletteEntries(0,2,&pe[1]); //将调色板中从第一个元素开始后的两个元素读取到pe数组从第二个元素开始的位置
m_palette.AnimatePalette(0,3,pe); //用pe数组中的颜色替换调色板中的颜色
Invalidate();
m_palette.GetPaletteEntries(2,1,pe); //将调色板中的第三个(索引为2)的颜色读取到pe数组的第一个元素
m_palette.GetPaletteEntries(0,2,&pe[1]); //将调色板中从第一个元素开始后的两个元素读取到pe数组从第二个元素开始的位置
m_palette.AnimatePalette(0,3,pe); //用pe数组中的颜色替换调色板中的颜色
Invalidate();
二、位图
位图可以分为与设备相关位图(DDB)和与设备无关位图(DIB)。两种位图的区别在于是否与位图的显示和保存是否与显示设备相关上。与位图相关位图中的信息与当前的显示硬件相关当文件被移植到其他计算机上时不保证能够正确的显示;与设备无关位图当中储存的信息与当前显示硬件不相关,因此可以在两台不同的计算机上任意一直并且保证显示的正确性。
在MFC当中使用位图需要使用到CBitmap类,这个类完整的封装了DDB的所有内容,如果要使用DIB则需要配合CBitmap了和API函数一起使用。在使用DDB时我们首先要使用CBitmap类的CreateCompatibleBitmap(CDC,int nWidth,int nHeight)成员函数构造一个与CDC指向显示设备兼容的位图,然后使用一个称之为内存设备描述表的CDC对象,将位图通过SelectObject成员函数选入到CDC当中,到这里我们就可以调用GDI函数在位图当中绘图。当需要将内存中位图显示到显示器或者打印机上时我们可以使用BitBlt或者StretchBlt函数来进行位块传送。当然当使用的位图是资源当中的文件时,我们只需要调用CBitmap::LoadBitmap(ID)
对于DIB类型的位图,我们从文件载入位图需要调用::LoadImage API函数,这个函数的最后一个参数必须要包含LR_CREATEDIBSECTION,这样在载入的位图当中才会包含DIBSECTION部分信息。调用这个函数后返回一个HBITMAP句柄,我们只需要将这个句柄挂接到CBitmap对象上即可。
我们需要获取DIB位图的宽、高以及颜色数,我们只需要调用CBitmap::GetObject函数,这个函数需要一个DIBSECTION数据结构作为参数。在这个数据结构当中dsbm.bmWidth和dsbm.bmHeight两个字段表示位图的宽和高。如果dsbmhi.biClrUsed字段如果不为零,那么这个字段表示了位图所包含的颜色数,否则表达式1<<dsbmhi.biBitCount表示了位图所包含的颜色数。
在8位显示设备上显示位图,我们需要一个调色板,这时从DIB文件当中获得的颜色数就能够起到关键性的作用,如果位图中的颜色多于256色,那么我们就直接调用CPalette::CreateHalftonePalette()成员函数来创建调色板,否则我们就读取DIB颜色表中的数据来创建调色板。读取DIB文件中的颜色表可以调用::GetDibColorTable API函数,这个函数需要一个RGBQUAD数据结构的数组,这个数组当中将会被储存所有颜色表中的数据。下面是一个从DIB读取到创建调色板的函数:
BOOL CLoadBmp1Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
HBITMAP hBitmap = (HBITMAP)::LoadImage(NULL,lpszPathName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if (hBitmap == NULL)
{
CString string;
string.Format(_T("%s is not a DIB file"),lpszPathName);
AfxMessageBox(string);
return FALSE;
}
if (hBitmap == NULL)
{
CString string;
string.Format(_T("%s is not a DIB file"),lpszPathName);
AfxMessageBox(string);
return FALSE;
}
m_bitmap.Attach(hBitmap);
CClientDC dc(NULL);
if ((dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) == 0)
return TRUE;
DIBSECTION ds;
m_bitmap.GetObject(sizeof (DIBSECTION),&ds);
int nColor;
if (ds.dsBmih.biClrUsed != 0)
nColor = ds.dsBmih.biClrUsed;
else
nColor = 1 << ds.dsBmih.biBitCount;
if (nColor > 256)
m_palette.CreateHalftonePalette(&dc);
else
{
RGBQUAD *pRGB = new RGBQUAD[nColor];
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CBitmap *OldBitmap = (CBitmap *)memDC.SelectObject(m_bitmap);
::GetDIBColorTable((HDC)memDC,0,nColor,pRGB);
memDC.SelectObject(OldBitmap);
int nSize = sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (nColor - 1));
LOGPALETTE *pLp = (LOGPALETTE *)new BYTE[nSize];
pLp->palVersion = 0x300;
pLp->palNumEntries = nColor;
for (int i = 0;i < nColor;i++)
{
pLp->palPalEntry[i].peFlags = 0;
pLp->palPalEntry[i].peRed = pRGB[i].rgbRed;
pLp->palPalEntry[i].peGreen = pRGB[i].rgbGreen;
pLp->palPalEntry[i].peBlue = pRGB[i].rgbBlue;
}
m_palette.CreatePalette(pLp);
delete[] pRGB;
delete[] pLp;
}
CClientDC dc(NULL);
if ((dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) == 0)
return TRUE;
DIBSECTION ds;
m_bitmap.GetObject(sizeof (DIBSECTION),&ds);
int nColor;
if (ds.dsBmih.biClrUsed != 0)
nColor = ds.dsBmih.biClrUsed;
else
nColor = 1 << ds.dsBmih.biBitCount;
if (nColor > 256)
m_palette.CreateHalftonePalette(&dc);
else
{
RGBQUAD *pRGB = new RGBQUAD[nColor];
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CBitmap *OldBitmap = (CBitmap *)memDC.SelectObject(m_bitmap);
::GetDIBColorTable((HDC)memDC,0,nColor,pRGB);
memDC.SelectObject(OldBitmap);
int nSize = sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (nColor - 1));
LOGPALETTE *pLp = (LOGPALETTE *)new BYTE[nSize];
pLp->palVersion = 0x300;
pLp->palNumEntries = nColor;
for (int i = 0;i < nColor;i++)
{
pLp->palPalEntry[i].peFlags = 0;
pLp->palPalEntry[i].peRed = pRGB[i].rgbRed;
pLp->palPalEntry[i].peGreen = pRGB[i].rgbGreen;
pLp->palPalEntry[i].peBlue = pRGB[i].rgbBlue;
}
m_palette.CreatePalette(pLp);
delete[] pRGB;
delete[] pLp;
}
return TRUE;
}
}
三、区域
区域就是指客户区当中允许被绘图的一部分,当DC中设置了一个区域以后在区域以外的所有绘图都会被裁减掉。创建区域的方法是首先定义一个CRgn对象,并使用这个对象调用CreateRectRgn、CreateEllipticRgn创建矩形、椭圆型的区域,或者使用CreateFromPath成员函数根据DC在BeginPath()和EndPath()之间创建的通道来创建区域。当创建完区域以后我们需要调用CDC::SelectClipRgn成员函数将区域选入到设备描述表当中,使区域生效。
当前的多个区域可以进行AND、OR、DIFF操作以创建更为复杂的区域。要实现这种操作需要调用CRgn::CombineRgn(CRgn,CRgn,UINT)成员函数,这里参数中的两个CRgn对象是操作的源对象,而调用成员函数的对象是目标对象,这个目标对象在调用操作之前必须已经被创建,而最后一个UINT参数是一个表示操作类型的常数。