与设备无关的位图.docx
《与设备无关的位图.docx》由会员分享,可在线阅读,更多相关《与设备无关的位图.docx(14页珍藏版)》请在冰豆网上搜索。
与设备无关的位图
与设备无关的位图(DIB)
DIB(Device-indepententbitmap)的与设备无关性主要体现在以下两个方面:
DIB的颜色模式与设备无关。
例如,一个256色的DIB即可以在真彩色显示模式下使用,也可以在16色模式下使用。
256色以下(包括256色)的DIB拥有自己的颜色表,像素的颜色独立于系统调色板。
由于DIB不依赖于具体设备,因此可以用来永久性地保存图象。
DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。
运行在不同输出设备下的应用程序可以通过DIB来交换图象。
DIB还可以用一种RLE算法来压缩图像数据,但一般来说DIB是不压缩的。
11.4.1DIB的结构
与BorlandC++下的框架类库OWL不同,MFC未提供现成的类来封装DIB。
尽管Microsoft列出了一些理由,但没有DIB类确实给MFC用户带来很多不便。
用户要想使用DIB,首先应该了解DIB的结构。
在内存中,一个完整的DIB由两部分组成:
一个BITMAPINFO结构和一个存储像素阵列的数组。
BITMAPINFO描述了位图的大小,颜色模式和调色板等各种属性,其定义为
typedefstructtagBITMAPINFO{
BITMAPINFOHEADERbmiHeader;
RGBQUADbmiColors[1];//颜色表
}BITMAPINFO;
RGBQUAD结构用来描述颜色,其定义为
typedefstructtagRGBQUAD{
BYTErgbBlue;//蓝色的强度
BYTErgbGreen;//绿色的强度
BYTErgbRed;//红色的强度
BYTErgbReserved;//保留字节,为0
}RGBQUAD;
注意,RGBQUAD结构中的颜色顺序是BGR,而不是平常的RGB。
BITMAPINFOHEADER结构包含了DIB的各种信息,其定义为
typedefstructtagBITMAPINFOHEADER{
DWORDbiSize;//该结构的大小
LONGbiWidth;//位图的宽度(以像素为单位)
LONGbiHeight;//位图的高度(以像素为单位)
WORDbiPlanes;//必须为1
WORDbiBitCount//每个像素的位数(1、4、8、16、24或32)
DWORDbiCompression;//压缩方式,一般为0或BI_RGB(未压缩)
DWORDbiSizeImage;//以字节为单位的图象大小(仅用于压缩位图)
LONGbiXPelsPerMeter;//以目标设备每米的像素数来说明位图的水平分辨率
LONGbiYPelsPerMeter;//以目标设备每米的像素数来说明位图的垂直分辨率
DWORDbiClrUsed;/*颜色表的颜色数,若为0则位图使用由biBitCount指定的最大颜色数*/
DWORDbiClrImportant;//重要颜色的数目,若该值为0则所有颜色都重要
}BITMAPINFOHEADER;
与DDB不同,DIB的字节数组是从图象的最下面一行开始的逐行向上存储的,也即等于把图象倒过来然后在逐行扫描。
另外,字节数组中每个扫描行的字节数必需是4的倍数,如果不足要用0补齐。
DIB可以存储在*.BMP或*.DIB文件中。
DIB文件是以BITMAPFILEHEADER结构开头的,该结构的定义为
typedefstructtagBITMAPFILEHEADER{
WORDbfType;//文件类型,必须为“BM”
DWORDbfSize;//文件的大小
WORDbfReserved1;//为0
WORDbfReserved2;//为0
DWORDbfOffBits;//存储的像素阵列相对于文件头的偏移量
}BITMAPFILEHEADER;
紧随该结构的是一个BITMAPINFOHEADER结构,然后是RGBQUAD结构组成的颜色表(如果有的话),文件最后存储的是DIB的像素阵列。
DIB的颜色信息储存在自己的颜色表中,程序一般要根据颜色表为DIB创建逻辑调色板。
在输出一幅DIB之前,程序应该将其逻辑调色板选入到相关的设备上下文中并实现到系统调色板中,然后再调用相关的GDI函数(如:
:
SetDIBitsToDevice或:
:
StretchDIBits)输出DIB。
在输出过程中,GDI函数会把DIB转换成DDB,这项工作主要包括以下两步:
将DIB的颜色格式转换成与输出设备相同的颜色格式。
例如,在真彩色的显示模式下要显示一个256色的DIB,则应该将其转换成24位的颜色格式。
将DIB像素的逻辑颜色索引转换成系统调色板索引。
11.4.2编写DIB类
由于MFC未提供DIB类,用户在使用DIB时将面临繁重的WindowsAPI编程任务。
幸运的是,VisualC++提供了一个较高层次的API,简化了DIB的使用。
这些API函数实际上是由MFC的DibLook例程提供的,它们位于DibLook目录下的dibapi.cpp、myfile.cpp和dibapi.h文件中,主要包括:
ReadDIBFile//把DIB文件读入内存
SaveDIB//把DIB保存到文件中
CreateDIBPalette//从DIB中创建一个逻辑调色板
PaintDIB//显示DIB
DIBWidth//返回DIB的宽度
DIBHeight//返回DIB的高度
如果读者对这些函数的内部细节感兴趣,那么可以研究一下dibapi.cpp和myfile.cpp文件,但要做好吃苦的准备。
即使利用上述API,编写使用DIB的程序仍然不是很轻松。
为了满足读者的要求,笔者编写了一个名为CDib的较简单的DIB类,该类是基于上述API的,它的主要成员函数包括:
BOOLLoad(LPCTSTRlpszFileName);
该函数从文件中载入DIB,参数lpszFileName说明了文件名。
若成功载入则函数返回TRUE,否则返回FALSE。
BOOLLoadFromResource(UINTnID);
该函数从资源中载入位图,参数nID是资源位图的ID。
若成功载入则函数返回TRUE,否则返回FALSE。
CPalette*GetPalette()
返回DIB的逻辑调色板。
BOOLDraw(CDC*pDC,intx,inty,intcx=0,intcy=0);
该函数在指定的矩形区域内显示DIB,它具有缩放位图的功能。
参数pDC指向用于绘图的设备上下文,参数x和y说明了目的矩形的左上角坐标,cx和cy说明了目的矩形的尺寸,cx和cy若有一个为0则该函数按DIB的实际大小绘制位图,cx和cy的缺省值是0。
若成功则函数返回TRUE,否则返回FALSE。
intWidth();//以像素为单位返回DIB的宽度
intHeight();//以像素为单位返回DIB的高度
CDib类的源代码在清单11.3和11.4列出,CDib类的定义位于CDib.h中,CDib类的成员函数代码位于CDib.cpp中。
对于CDib类的代码这里就不作具体解释了,读者只要会用就行。
清单11.3CDib.h
#if!
definedMYDIB
#defineMYDIB
#include"dibapi.h"
classCDib
{
public:
CDib();
~CDib();
protected:
HDIBm_hDIB;
CPalette*m_palDIB;
public:
BOOLLoad(LPCTSTRlpszFileName);
BOOLLoadFromResource(UINTnID);
CPalette*GetPalette()const
{returnm_palDIB;}
BOOLDraw(CDC*pDC,intx,inty,intcx=0,intcy=0);
intWidth();
intHeight();
voidDeleteDIB();
};
#endif
清单11.4Cdib.cpp
#include
#include"CDib.h"
#ifdef_DEBUG
#undefTHIS_FILE
staticcharBASED_CODETHIS_FILE[]=__FILE__;
#endif
CDib:
:
CDib()
{
m_palDIB=NULL;
m_hDIB=NULL;
}
CDib:
:
~CDib()
{
DeleteDIB();
}
voidCDib:
:
DeleteDIB()
{
if(m_hDIB!
=NULL)
:
:
GlobalFree((HGLOBAL)m_hDIB);
if(m_palDIB!
=NULL)
deletem_palDIB;
}
//从文件中载入DIB
BOOLCDib:
:
Load(LPCTSTRlpszFileName)
{
HDIBhDIB;
CFilefile;
CFileExceptionfe;
if(!
file.Open(lpszFileName,CFile:
:
modeRead|CFile:
:
shareDenyWrite,&fe))
{
AfxMessageBox(fe.m_cause);
returnFALSE;
}
TRY
{
hDIB=:
:
ReadDIBFile(file);
}
CATCH(CFileException,eLoad)
{
file.Abort();
returnFALSE;
}
END_CATCH
DeleteDIB();//清除旧位图
m_hDIB=hDIB;
m_palDIB=newCPalette;
if(:
:
CreateDIBPalette(m_hDIB,m_palDIB)==NULL)
{
//DIB有可能没有调色板
deletem_palDIB;
m_palDIB=NULL;
}
returnTRUE;
}
//从资源中载入DIB
BOOLCDib:
:
LoadFromResource(UINTnID)
{
HINSTANCEhResInst=AfxGetResourceHandle();
HRSRChFindRes;
HDIBhDIB;
LPSTRpDIB;
LPSTRpRes;
HGLOBALhRes;
//搜寻指定的资源
hFindRes=:
:
FindResource(hResInst,MAKEINTRESOURCE(nID),RT_BITMAP);
if(hFindRes==NULL)returnFALSE;
hRes=:
:
LoadResource(hResInst,hFindRes);//载入位图资源
if(hRes==NULL)returnFALSE;
DWORDdwSize=:
:
SizeofResource(hResInst,hFindRes);
hDIB=(HDIB):
:
GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT,dwSize);
if(hDIB==NULL)returnFALSE;
pDIB=(LPSTR):
:
GlobalLock((HGLOBAL)hDIB);
pRes=(LPSTR):
:
LockResource(hRes);
memcpy(pDIB,pRes,dwSize);//把hRes中的内容复制hDIB中
:
:
GlobalUnlock((HGLOBAL)hDIB);
DeleteDIB();
m_hDIB=hDIB;
m_palDIB=newCPalette;
if(:
:
CreateDIBPalette(m_hDIB,m_palDIB)==NULL)
{
//DIB有可能没有调色板
deletem_palDIB;
m_palDIB=NULL;
}
returnTRUE;
}
intCDib:
:
Width()
{
if(m_hDIB==NULL)return0;
LPSTRlpDIB=(LPSTR):
:
GlobalLock((HGLOBAL)m_hDIB);
intcxDIB=(int):
:
DIBWidth(lpDIB);//SizeofDIB-x
:
:
GlobalUnlock((HGLOBAL)m_hDIB);
returncxDIB;
}
intCDib:
:
Height()
{
if(m_hDIB==NULL)return0;
LPSTRlpDIB=(LPSTR):
:
GlobalLock((HGLOBAL)m_hDIB);
intcyDIB=(int):
:
DIBHeight(lpDIB);//SizeofDIB-y
:
:
GlobalUnlock((HGLOBAL)m_hDIB);
returncyDIB;
}
//显示DIB,该函数具有缩放功能
//参数x和y说明了目的矩形的左上角坐标,cx和cy说明了目的矩形的尺寸
//cx和cy若有一个为0则该函数按DIB的实际大小绘制,cx和cy的缺省值是0
BOOLCDib:
:
Draw(CDC*pDC,intx,inty,intcx,intcy)
{
if(m_hDIB==NULL)returnFALSE;
CRectrDIB,rDest;
rDest.left=x;
rDest.top=x;
if(cx==0||cy==0)
{
cx=Width();
cy=Height();
}
rDest.right=rDest.left+cx;
rDest.bottom=rDest.top+cy;
rDIB.left=rDIB.top=0;
rDIB.right=Width();
rDIB.bottom=Height();
return:
:
PaintDIB(pDC->GetSafeHdc(),&rDest,m_hDIB,&rDIB,m_palDIB);
}
11.4.3使用CDib类的例子
现在让我们来看一个使用CDib类的例子。
如图11.4所示,程序名为ShowDib,是一个多文档应用程序,它的功能与VC的DibLook例程有些类似,可同时打开和显示多个位图。
图11.4用ShowDib来显示位图
请读者用AppWizard建立一个名为ShowDib的MFC工程。
程序应该用滚动视图来显示较大的位图,所以在MFCAppWizard的第6步应把CShowDibView的基类改为CScrollView。
由于ShowDib程序要用到CDib类,所以应该把dibapi.cpp、myfile.cpp、dibapi.h、CDib.cpp和CDib.h文件拷贝到ShowDib目录下,并选择Project->AddtoProject->Files命令把这些文件加到ShowDib工程中。
在ShowDib.h文件中CShowDibApp类的定义之前加入下面一行:
#defineWM_DOREALIZEWM_USER+200
当收到调色板消息时,主框架窗口会发送用户定义的WM_DOREALIZE消息通知视图。
接下来,需要用ClassWizard为CMainFrame加入WM_QUERYNEWPALETTE和WM_PALETTECHANGED消息的处理函数,为CShowDibDoc类加入OnOpenDocument函数。
最后,请读者按清单11.5、11.6和11.7修改程序。
清单11.5CMainFrame类的部分代码
//MainFrm.cpp:
implementationoftheCMainFrameclass
voidCMainFrame:
:
OnPaletteChanged(CWnd*pFocusWnd)
{
CMDIFrameWnd:
:
OnPaletteChanged(pFocusWnd);
//TODO:
Addyourmessagehandlercodehere
SendMessageToDescendants(WM_DOREALIZE,1);//通知所有的子窗口
}
BOOLCMainFrame:
:
OnQueryNewPalette()
{
//TODO:
Addyourmessagehandlercodehereand/orcalldefault
CMDIChildWnd*pMDIChildWnd=MDIGetActive();
if(pMDIChildWnd==NULL)
returnFALSE;//没有活动的MDI子框架窗口
CView*pView=pMDIChildWnd->GetActiveView();
pView->SendMessage(WM_DOREALIZE,0);//只通知活动视图
returnTRUE;//返回TRUE表明实现了逻辑调色板
}
清单11.6CShowDibDoc类的部分代码
//ShowDibDoc.h:
interfaceoftheCShowDibDocclass
#include"CDib.h"
classCShowDibDoc:
publicCDocument
{
...
//Attributes
public:
CDibm_Dib;
...
};
//ShowDibDoc.cpp:
implementationoftheCShowDibDocclass
BOOLCShowDibDoc:
:
OnOpenDocument(LPCTSTRlpszPathName)
{
if(!
CDocument:
:
OnOpenDocument(lpszPathName))
returnFALSE;
//TODO:
Addyourspecializedcreationcodehere
BeginWaitCursor();
BOOLbSuccess=m_Dib.Load(lpszPathName);//载入DIB
EndWaitCursor();
returnbSuccess;
}
清单11.7CShowDibView类的部分代码
//ShowDibView.h:
interfaceoftheCShowDibViewclass
classCShowDibView:
publicCScrollView
{
...
afx_msgLRESULTOnDoRealize(WPARAMwParam,LPARAMlParam);
DECLARE_MESSAGE_MAP()
};
//ShowDibView.cpp:
implementationoftheCShowDibViewclass
BEGIN_MESSAGE_MAP(CShowDibView,CScrollView)
...
ON_MESSAGE(WM_DOREALIZE,OnDoRealize)
END_MESSAGE_MAP()
voidCShowDibView:
:
OnInitialUpdate()
{
CScrollView:
:
OnInitialUpdate();
CSizesizeTotal;
//TODO:
calculatethetotalsizeofthisview
CShowDibDoc*pDoc=GetDocument();
sizeTotal.cx=pDoc->m_Dib.Width();
sizeTotal.cy=pDoc->m_Dib.Height();
SetScrollSizes(MM_TEXT,sizeTotal);//设置视图的滚动范围
}
voidCShowDibView:
:
OnActivateView(BOOLbActivate,CView*pActivateView,CView*pDeactiveView)
{
//TODO:
Addyourspecializedcodehereand/orcallthebaseclass
if(bActivate)
OnDoRealize(0,0);//刷新视图
CScrollView:
:
OnActivateView(bActivate,pActivateView,pDeactiveView);
}
LRESULTCShowDibView:
:
OnDoRealize(WPARAMwParam,LPARAM)
{
CClientDCdc(this);
//wParam参数决定了该视图是否实现前景调色板
dc.SelectPalette(GetDocument()->m_Dib.GetPalette(),wParam);
if(dc.RealizePalette())
GetDocument()->UpdateAllViews(NULL);
return0L;
}
voidCShowDibView:
:
OnDraw(CDC*pDC)
{
CShowDibDoc*pDoc=GetDocument();
ASSERT_VALID(pDoc);
//TODO:
adddrawcodefornativedatahere
pDoc->m_Dib.Draw(pDC,0,0);//输出DIB
}
在程序中使用CDib对象的代码很简单。
当用户在ShowDib程序中选择File->Open命令并从打开文件对话框中选择了一个BMP文件后,CShowDibDoc:
:
OnOpenDocument函数被调用,该函数调用CDib:
:
Load载入位图。
在CShowDibView:
:
OnDraw中,调用CDib:
:
Draw输出位图。
在CShowDibView:
:
OnInitialUpdate中,根据DIB的尺寸来确定视图的滚动范围。
需要重点研究的是ShowDib如何处理调色板问题的。
ShowDib是一个多文档应用程序,可以同时显示多幅位图。
由于每个位图一般都有不同的调色板,这样就产生了共享系统调色板的问题。
程序必须采取措施来保证只有一个视图的逻辑调色板作为前景调色板使用。
当主框架窗口收到WM_QUERYNEWPALETTE消息时,主框架窗口向具有输入焦点的视图发送wParam参数为0的WM_DOREALIZE