8 R& A/ W7 z- U# J
西安交通大学流体机械研究所
0 _" W' Z! l% y) x+ l5 O- P张义云
: B" [% Y! [1 J+ L: }( F
---- 用普通方法显示BMP位图,占内存大,速度慢,在图形缩小时,失真严重,
) W: ~+ `" Q, ]+ F0 I; r" t; ^在低颜色位数的设备上显示高颜色位数的图形图形时失真大。本文采用视频函数
显示BMP位图,可以消除以上的缺点。
7 u# G0 y; g* s$ Z' C- B5 I* ^: X4 U) M
---- 一、BMP文件结构
---- 1. BMP文件组成
---- BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。
. A, x. I) }, N, M* j
---- 2. BMP文件头
1 k0 }/ m6 F( n! _
---- BMP文件头数据结构含有BMP文件的类型、文件大小和位图起始位置等信息
。
7 X3 J% z! e( U( E" ]$ A, K3 h/ b% [) H1 c4 J
---- 其结构定义如下:
typedef struct tagBITMAPFILEHEADER
8 P8 ]3 h* i- O& ^! S" b{
% J* y* Y% f% j5 TWORDbfType; // 位图文件的类型,必须为BM
) K8 k1 j' |8 K9 b6 k \, p8 rDWORD bfSize; // 位图文件的大小,以字节为单位
WORDbfReserved1; // 位图文件保留字,必须为0
+ J# f. l& i4 w8 V5 ?/ F4 `WORDbfReserved2; // 位图文件保留字,必须为0
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图
// 文件头的偏移量表示,以字节为单位
# O l; X1 t$ {} BITMAPFILEHEADER;
, I1 R: `, H( q$ b. Z) E' Z* B$ s6 N( b5 Q! y
?# ~7 ~- y( }. z( t1 E1 {- y& t
---- 3. 位图信息头
' M! C/ m" q8 m5 p# `& K----
! U5 m$ W$ n# A: X
BMP位图信息头数据用于说明位图的尺寸等信息。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // 本结构所占用字节数
LONGbiWidth; // 位图的宽度,以像素为单位
LONGbiHeight; // 位图的高度,以像素为单位
+ x& U* c3 o3 u9 c7 O, \2 Y X7 uWORD biPlanes; // 目标设备的级别,必须为1
, t' r o9 V F7 nWORD biBitCount// 每个像素所需的位数,必须是1(双色),
: e! d, e3 Q1 y; H! M// 4(16色),8(256色)或24(真彩色)之一
DWORD biCompression; // 位图压缩类型,必须是 0(不压缩),
// 1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
; J; ~) J* a. ]1 z, L P8 d$ R: EDWORD biSizeImage; // 位图的大小,以字节为单位
LONGbiXPelsPerMeter; // 位图水平分辨率,每米像素数
LONGbiYPelsPerMeter; // 位图垂直分辨率,每米像素数
DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数
" X5 |$ y9 t) C( X a GDWORD biClrImportant;// 位图显示过程中重要的颜色数
% Q2 S+ Z5 k* V( k. Y} BITMAPINFOHEADER;
* c8 @- o4 h/ @2 a: |5 C9 b) c P5 M9 b
---- 4. 颜色表
---- 颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个
/ N" G% @; r. X) y( J0 I4 ?$ h7 TRGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:
, I3 u: R& `+ s) t* J0 `+ l* Z1 r' H+ E& r
typedef struct tagRGBQUAD {
BYTErgbBlue;// 蓝色的亮度(值范围为0-255)
BYTErgbGreen; // 绿色的亮度(值范围为0-255)
BYTErgbRed; // 红色的亮度(值范围为0-255)
BYTErgbReserved;// 保留,必须为0
} RGBQUAD;
. ?6 }0 G5 l0 y ?% l Y3 K3 @* }4 h颜色表中RGBQUAD结构数据的个数有biBitCount来确定:
当biBitCount=1,4,8时,分别有2,16,256个表项;
当biBitCount=24时,没有颜色表项。
( Q) e0 D4 J3 E2 L9 t/ g位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:
typedef struct tagBITMAPINFO {
! Y5 j& L* K% J* E2 x' ` e" \* |6 RBITMAPINFOHEADER bmiHeader; // 位图信息头
RGBQUAD bmiColors[1]; // 颜色表
/ J# m1 e' P2 R5 X7 F+ T} BITMAPINFO;
4 ~* [. F: B, ?3 N' O# z8 n! t# E2 d- `: _ \! Z
/ V, L1 Q) V( Q6 U! H/ p5 b0 w* `
---- 5. 位图数据
4 ?# T# [8 G4 [---- 位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右
4 t. l( u$ P5 ?4 {# L& [2 l! N( C,扫描行之间是从下到上。位图的一个像素值所占的字节数:
当biBitCount=1时,8个像素占1个字节;
8 ~. E# k$ ~9 V" ^( R当biBitCount=4时,2个像素占1个字节;
当biBitCount=8时,1个像素占1个字节;
, `6 `& D# R4 X9 c; P5 M' N) h* X; n8 r当biBitCount=24时,1个像素占3个字节;
Windows规定一个扫描行所占的字节数必须是
8 l: `$ d: k- U& y9 d$ ^) A3 V5 f- U4的倍数(即以long为单位),不足的以0填充,
+ G4 G4 {0 f2 Q0 ^) k4 i0 }, I一个扫描行所占的字节数计算方法:
DataSizePerLine= (biWidth* biBitCount+31)/8;
; B) q3 \7 Y) l& o+ Q/ ?2 P// 一个扫描行所占的字节数
DataSizePerLine= DataSizePerLine/4*4; // 字节数必须是4的倍数
9 t1 d) T! _" U# Z2 M; U' F0 I位图数据的大小(不压缩情况下):
DataSize= DataSizePerLine* biHeight;
---- 二、BMP位图一般显示方法
---- 1. 申请内存空间用于存放位图文件
* ?% `* G! @" H
---- GlobalAlloc(GHND,FileLength);
+ r3 ~9 j8 ]/ \! h2 o
---- 2. 位图文件读入所申请内存空间中
& q& p8 S/ ]- [4 Q+ q: o }: E f% F0 x# k
---- LoadFileToMemory( mpBitsSrc,mFileName);
$ R3 W" w" q5 F, R H" b; o8 M# e$ w* h6 _* h$ N$ }
---- 3. 在OnPaint等函数中用创建显示用位图
---- 用CreateDIBitmap()创建显示用位图,用CreateCompatibleDC()创建兼容
DC,
) _% w% e6 b7 L2 N0 F3 T" H
---- 用SelectBitmap()选择显示位图。
4 y/ i3 s: i$ g+ \
---- 4. 用BitBlt或StretchBlt等函数显示位图
---- 5. 用DeleteObject()删除所创建的位图
; |5 {) i. g1 l) J3 M, V2 B2 C! t& D. K, Y! ^# J( |8 s
---- 以上方法的缺点是: 1)显示速度慢; 2) 内存占用大; 3) 位图在缩小显示
/ L+ O. l3 n7 W; W% O; {时图形失真大,(可通过安装字体平滑软件来解决); 4) 在低颜色位数的设备上(
如256显示模式)显示高颜色位数的图形(如真彩色)图形失真严重。
---- 三、BMP位图缩放显示
---- 用DrawDib视频函数来显示位图,内存占用少,速度快,而且还可以对图形
6 o: t9 L w: X+ ^进行淡化(Dithering)处理。淡化处理是一种图形算法,可以用来在一个支持比
图像所用颜色要少的设备上显示彩色图像。BMP位图显示方法如下:
---- 1. 打开视频函数DrawDibOpen(),一般放在在构造函数中
. I! y5 B- I% V& p
---- 2. 申请内存空间用于存放位图文件
6 Y) Z1 ]$ p; G" Q' X2 J$ {; l* k+ x5 `7 {- V( c, J M
---- GlobalAlloc(GHND,FileLength);
! h( K4 g2 y g* q0 y1 O8 U7 W% S w+ S! z/ k" {
---- 3. 位图文件读入所申请内存空间中
# v+ |# y/ B9 r+ {. c3 n- |6 }2 \$ h& J% b; t
---- LoadFileToMemory( mpBitsSrc,mFileName);
: N2 ~" [. Q6 k; C
---- 4. 在OnPaint等函数中用DrawDibRealize(),DrawDibDraw()显示位图
* a# ]- V1 E/ A' ?+ o/ ~5 U0 {/ g0 h1 p
---- 5. 关闭视频函数DrawDibClose(),一般放在在析构函数中
9 _, W# W8 ?- t) \# y2 \: Y% X1 x# @) Z: |9 S! V7 Y9 X( V
---- 以上方法的优点是: 1)显示速度快; 2) 内存占用少; 3) 缩放显示时图形
失真小,4) 在低颜色位数的设备上显示高颜色位数的图形图形时失真小; 5) 通
过直接处理位图数据,可以制作简单动画。
3 a2 L5 \' |2 B' O) p
---- 四、CViewBimap类编程要点
0 u9 C! D' z3 V" C% j1 s- ^9 Y, S y
---- 1. 在CViewBimap类中添加视频函数等成员
7 y: Q) `2 Z6 v* ?
HDRAWDIB m_hDrawDib; // 视频函数
) Z$ r! {- k/ Q7 [. s) }2 r- U C sHANDLEmhBitsSrc; // 位图文件句柄(内存)
LPSTR mpBitsSrc; // 位图文件地址(内存)
BITMAPINFOHEADER *mpBitmapInfo; // 位图信息头
, I, e9 _1 W, q( H
---- 2. 在CViewBimap类构造函数中添加打开视频函数
/ q) r; \, j! J) x" q* o$ z6 Z$ J---- m_hDrawDib= DrawDibOpen();
. W8 l7 ^. B7 e, ^1 O# j, J
---- 3. 在CViewBimap类析构函数中添加关闭视频函数
# R4 [+ j% G+ E, H" f
if( m_hDrawDib != NULL)
{
$ ~$ h5 o8 S1 d7 ]DrawDibClose( m_hDrawDib);
( P' i, O- `5 n& l4 m0 ?7 Zm_hDrawDib = NULL;
}
; J' B9 |/ J# U3 z# u1 O+ b }9 P2 c% {( {- V6 |
---- 4. 在CViewBimap类图形显示函数OnPaint中添加GraphicDraw()
voidCViewBitmap::OnPaint()
2 B/ S4 Y( @ ~/ L; C{
+ _' g" E3 D: b. `CPaintDC dc(this); // device context for painting
GraphicDraw( );
. E/ N4 [. j/ x* ?4 x6 ]}
/ Y3 D# ]$ E3 N1 d; o
voidCViewBitmap::GraphicDraw( void )
{
H$ g* y! R" ~ B* ^) @7 q ?CClientDC dc(this); // device context for painting
% Q3 k& e4 q; L, k' _6 N% U- KBITMAPFILEHEADER *pBitmapFileHeader;
ULONG bfoffBits= 0;
CPoint Wid;
% T& I4 V7 u$ I
// 图形文件名有效 (=0 BMP)
if( mBitmapFileType < ID_BITMAP_BMP ) return;
// 图形文件名有效 (=0 BMP)
2 i- G4 |% p% J// 准备显示真彩位图
9 Z& v. _, w' ?4 y0 `) V, hpBitmapFileHeader= (BITMAPFILEHEADER *) mpBitsSrc;
bfoffBits= pBitmapFileHeader->bfOffBits;
6 L! d* h; d. z8 p6 _% Q# _) k' @: I: m6 I6 G- W/ U
// 使用普通函数显示位图
% t1 ~5 o u! G* ^6 z9 e) c% E" d
if( m_hDrawDib == NULL || mDispMethod == 0)
{
HBITMAP hBitmap=::CreateDIBitmap(dc.m_hDC,
mpBitmapInfo, CBM_INIT, mpBitsSrc+bfoffBits,
2 `) S+ S% ^/ q. m! y7 A" o(LPBITMAPINFO) mpBitmapInfo,DIB_RGB_COLORS);
// 建立位图
4 n* V9 |, f. ]+ a. o& C! SHDC hMemDC=::CreateCompatibleDC(dc.m_hDC);// 建立内存
% @3 M# T% F7 I0 T& EHBITMAP hBitmapOld= SelectBitmap(hMemDC, hBitmap); // 选择对象
// 成员CRect mDispR用于指示图形显示区域的大小.
// 成员CPoint mPos用于指示图形显示起始位置坐标.
if( mPos.x > (mpBitmapInfo- >biWidth - mDispR.Width() ))
mPos.x= mpBitmapInfo->biWidth - mDispR.Width() ;
if( mPos.y > (mpBitmapInfo- >biHeight- mDispR.Height()))
mPos.y= mpBitmapInfo- >biHeight- mDispR.Height();
if( mPos.x < 0 ) mPos.x= 0;
if( mPos.y < 0 ) mPos.y= 0;
, g6 N/ u9 O' y: l) H+ ]& [
if( mFullViewTog == 0)
6 ^+ n! q; W/ N# t d5 K{
// 显示真彩位图
::BitBlt(dc.m_hDC,0,0, mDispR.Width(), mDispR.Height(),
hMemDC,mPos.x,mPos.y, SRCCOPY);
- k/ L& C- Y. H+ x% E. F( S} else {
::StretchBlt(dc.m_hDC,0,0, mDispR.Width(), mDispR.Height(),
2 f1 X/ B% p8 r+ S% jhMemDC,0,0, mpBitmapInfo- >biWidth, mpBitmapInfo-
>biHeight, SRCCOPY);
}
$ J7 @- r& X4 R5 @// 结束显示真彩位图
:
eleteObject(SelectObject(hMemDC,hBitmapOld));
// 删 除 位 图
" E' m2 X8 Y" W} else {
5 t: J; n7 h" y( r% x% c0 L9 P% k7 e$ E( R3 {2 ?& s' C
// 使用视频函数显示位图
if( mPos.x > (mpBitmapInfo- >biWidth - mDispR.Width() ))
mPos.x= mpBitmapInfo- >biWidth - mDispR.Width() ;
5 _7 S9 ?4 q8 `$ h3 Fif( mPos.y > (mpBitmapInfo- >biHeight- mDispR.Height()))
x* z/ j, I: K& d7 R7 Q8 q9 OmPos.y= mpBitmapInfo- >biHeight- mDispR.Height();
7 f. J$ d. N6 R# Rif( mPos.x < 0 ) mPos.x= 0;
" }' q1 o) r) V2 u" Aif( mPos.y < 0 ) mPos.y= 0;
4 r E7 j% @* j5 K
// 显示真彩位图
DrawDibRealize( m_hDrawDib, dc.GetSafeHdc(), TRUE);
. Z+ }8 O D( V: s e
if( mFullViewTog == 0)
{
Wid.x= mDispR.Width();
7 l" U6 J/ t9 J/ A3 {6 S" \Wid.y= mDispR.Height();
// 1:1 显示时, 不能大于图形大小
6 K0 T8 K- r5 ^ k6 J1 qif( Wid.x > mpBitmapInfo- >biWidth )
Wid.x = mpBitmapInfo- >biWidth;
- X, C# C0 U) J3 Pif( Wid.y > mpBitmapInfo- >biHeight)
Wid.y = mpBitmapInfo- >biHeight;
8 L# M& o l6 t9 k! i! k( e
DrawDibDraw( m_hDrawDib, dc.GetSafeHdc()
, 0, 0, Wid.x, Wid.y,
mpBitmapInfo, (LPVOID) (mpBitsSrc+bfoffBits),
7 {, @) |8 X, QmPos.x, mPos.y, Wid.x, Wid.y, DDF_BACKGROUNDPAL);
8 t Y/ X- K8 [} else {
DrawDibDraw( m_hDrawDib, dc.GetSafeHdc(),
' e7 f9 d6 q6 _0 z; \4 j x; O0, 0, mDispR.Width(), mDispR.Height(),
1 x! B6 M- ]. N" i5 ]5 ^mpBitmapInfo, (LPVOID) (mpBitsSrc+bfoffBits),
0, 0, mpBitmapInfo- >biWidth, mpBitmapInfo- >biHeight,
DDF_BACKGROUNDPAL);
}
}
return;
9 ~- b5 h( K6 G4 ?/ n$ v3 H}
' h/ }1 p2 O% d" O3 D
" k1 @2 Y( I6 g& M% A
---- 五、使用CViewBimap类显示BMP位图
' T: e. X. C! v7 O! ^$ t---- 1. 在Visual C++5.0中新建一个名称为mymap工程文件,类型为MFC
AppWizard[exe]。在编译运行通过后,在WorkSpace(如被关闭,用Alt_0打开)点
击ResourceView,点击Menu左侧的+符号展开Menu条目,双击IDR_MAINFRAME条目
,进入菜单资源编辑,在'“查看(V)”下拉式菜单(英文版为View下拉式菜单)的
尾部添加“ViewBitmap”条目,其ID为ID_VIEW_BITMAP。
+ }3 Z: I/ A: i7 m2 Y# W
---- 2. 在Visual C++5.0中点击下拉式菜单Project- >Add To project-
>Files...,将Bitmap0.h和Bitmap0.cpp添加到工程文件中。
, @8 A& |, O7 D7 B N
---- 3. 在Visual C++5.0中按Ctrl_W进入MFC ClassWizard,选择类名称为
; t6 P4 @' ^1 q2 b7 JCMainFrame,ObjectIDs: ID_VIEW_BITMAP,Messages选择Command,然后点击Add
# a& a$ J9 s. L: I2 L$ WFucction按钮,然后输入函数名为OnViewBimap。在添加OnViewBimap后,在
- ?! Z9 q0 h1 c5 H" t" g" RMember functions: 中点击OnViewBimap条目,点击Edit Code按钮编辑程序代码
; ^% b, f9 S) W1 n: T! t$ r。代码如下:
`, u2 @: U$ `6 j' H' J# g7 V, [$ B% t. f2 h
void CMainFrame::OnViewBitmap()
{
// TOD Add your command handler code here
CViewBitmap *pViewBitmap= NULL;
pViewBitmap= new CViewBitmap( "BITMAP.BMP", this);
pViewBitmap- >ShowWindow( TRUE);
4 w% @! _5 B E) y/ X}
5 W; ^! X, U3 ?( i, Q% s, J' a3 o, L R
7 P! R( J5 @5 ^- p
---- 并在该程序的头部添加#include "bitmap0.h",然后编译运行。
0 A4 l% Z# G0 T) G3 R1 E% p$ [- o---- 4. 找一个大一点的真彩色的BMP位图,将它拷贝到BITMAP.BMP中。
" N* j8 r6 ^% o% S2 w$ i. f1 `0 P
---- 5. 运行时,点击下拉式菜单“查看(V)- >ViewBitmap”(英文版为View- >
ViewBitmap)即可显示BITMAP.BMP位图。
_4 Q4 J3 \# Z1 Q; k! U
---- 六、CViewBimap类功能说明
9 o. H7 j6 R5 S: U! i
---- 1. 在客户区中带有水平和垂直滚动条。在位图大小大于显示客户区时,可
以使用滚动条;在位图大小小于显示客户区或全屏显示时,滚动条无效。
: L3 L% Q5 X+ |- [$ R" l" T9 Y
---- 2. 在客户区中底部带有状态条。状态条中的第一格为位图信息,第二格为
% ^% y+ V0 j$ V6 z9 E' U位图显示方法,可以是使用普通函数或使用视频函数。在第二格区域内点击鼠标
; |; x& w7 d; v6 n6 \,可在两者之间接换。第三格为位图显示比例,可以是1;1显示或全屏显示。在
第三格区域内点击鼠标,可在两者之间接换。在全屏显示时,如果位图比客户区
) K) M5 s/ W, R/ I6 p/ o小,则对位图放大; 如果位图比客户区大,则对位图缩小。
9 p' b$ B; x4 O- b9 M0 s
---- 3. 支持文件拖放功能。可以从资源管理器中拖动一个位图文件到客户区,
! O6 V( K5 o0 z7 M就可以显示该位图。
% Z' n7 T5 W, A* [* z; q C0 y# R; K& w* A' d2 N; h
---- 程序调试通过后,可以找一个较大的真彩色位图或调整客户区比位图小,
; A* v) f( z) e5 {在全屏显示方式下,比较使用普通函数与使用视频函数的差别。可以看出,位图
6 e2 B, }8 y/ }( g" ]8 F放大时两者差别不大,但在位图缩小时,两者差别明显; 使用视频函数时位图失
; B' e% P1 Q& c" n& W9 M真小,显示速度快。
---- 还可以从控制面板中将屏幕显示方式从真彩色显示模式切换到256色显示模
式,再比较使用普通函数与使用视频函数显示同一个真彩色位图的差别。现在可
以体会到使用视频函数的优越性了吧。
1 M: A, z# k/ {5 D+ h3 k+ I7 g7 D; M Q% e
---- 在全屏显示时,位图的xy方向比例不相同,如要保持相同比例,可在显示
程序中加以适当调整即可,读者可自行完成。
| 欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) | Powered by Discuz! X2.5 |