第一篇 数字图像处理编程基础

本篇介绍Visual C++数字图像处理的基础知识。本篇内容可以带领初学者快速进入数字图像处理领域,轻松开始Visual C++数字图像处理编程之旅。

第1章 Visual C++图像处理基础

本章系统论述了使用Visual C++对数字图像进行处理的一些基础知识,并通过示例和实例讲解了它们的使用方法。

1.1 Visual C++概述

Visual C++(简称VC)自诞生以来,凭借着C++语言的强大功能、良好的开发环境支持以及与Windows操作系统的血缘关系,一直是Windows操作系统环境下最主要的开发工具之一。使用VC可以完成各种各样的应用程序开发,涵盖底层软件到上层面向用户的软件,而且这些开发出的产品与Windows操作系统最具亲和力。掌握了VC就等于进入了Windows编程的自由王国。

VC已历经数个版本的发展,伴随着Microsoft.NET计划的展开,又也诞生了一系列VC.NET版本,从VS 2002、VS 2003、VS 2005,一直发展到现在最新的VS 2008。目前,在数字图像处理领域使用VC作为开发平台的,多以VC 6.0或VS 2005这两个版本为主。VC 6.0版本代码可以自动迁移到VS 2005版本,而VS 2008主要是在界面设计上增加了对Vista外观样式的支持,对于数字图像处理算法编程,VS 2008与VS 2005并无本质区别,因此本书主要介绍VS 2005,兼顾VC 6.0。

VS 2005最主要的技术特点是:可视化编程、支持面向对象技术以及支持.NET。VS 2005提供了一系列可视化开发工具,如应用程序向导AppWizard、属性窗口和常用控件等,通过使用可视化编程技术使得Windows编程更为直观、方便、快捷。而VS 2005支持的面向对象编程技术包装了Windows内在的复杂运行机制,使得Windows编程更为简单易学。在进入.NET时代后,VS 2005通过.NET技术支持可以实现语言无关性、托管编程以及开发XML Web服务等,大大地拓宽了VS 2005的应用领域。

需要说明的是,VS 2005并不完全兼容VC 6.0,将VC 6.0的项目自动升级到VS 2005后,经常出现编译错误。下面列出VS 2005与VC 6.0之间的几点不同。

▓VS 2005创建的项目默认使用Unicode字符集。

▓VS 2005除掉了VC 6.0中的类向导,取而代之的是右键快捷菜单和属性窗口。

▓VS 2005支持.NET Framework,即可以开发传统的非托管应用程序,又可以开发基于.NET Framework的托管应用程序。

▓VS 2005在开发基于.NET Framework的应用程序时,可以与其他语言进行混合开发,即支持语言无关性。

▓创建VS 2005项目所生成的文件也与以前有所不同,修改和增加了一些文件类型。

▓VS 2005的语法比VC 6.0更严格,更符合标准C++规范,因此,在VC 6.0中可以编译通过的程序在VS 2005中可能报错。

▓VS 2005使用的MFC类库以及运行库也都发生了变化,这也将导致将VC 6.0项目升级到VS 2005时发生错误。

▓VS 2005的开发环境比VC 6.0更方便、更稳定。标签式的文档显示、灵活的窗口定制、完善的智能提示等将会带来崭新的体验。

1.2 数字图像处理的研究内容及应用领域

数字图像处理的英文名称是“Digital Image Processing”。通常所说的数字图像处理是指用计算机进行的处理,因此也称为计算机图像处理(Computer Image Processing)。数字图像处理技术是一个跨学科的领域。随着计算机科学技术的不断发展,图像处理和分析逐渐形成了自己的科学体系,新的处理方法层出不穷,尽管其发展历史不长,但却引起各方面人士的广泛关注。

1. 数字图像处理的研究内容

总的来说,数字图像处理包括以下几项内容。

(1)点运算

点运算主要针对图像的像素进行加、减、乘、除等运算。图像的点运算可以有效地改变图像的直方图分布,这对提高图像的分辨率以及图像均衡都是非常有益的。

(2)几何变换

几何变换主要包括图像的坐标变换,图像的移动、缩小、放大、旋转,多个图像的配准以及图像扭曲校正等。几何变换是最常见的图像处理手段,几乎所有的图像处理软件都提供了最基本的图像缩放功能。图像的扭曲校正功能可以将变形的图像进行几何校正,从而得出准确的图像。

(3)图像增强

图像增强的作用主要是突出图像中的重要信息,同时减弱或者去除不需要的信息。常用的方法有灰度变换增强、直方图增强、频域增强以及彩色增强等。

(4)图像复原

图像复原的主要目的是去除干扰和模糊,从而恢复图像的本来面目,例如去噪声复原处理。常用的方法有线性复原和非线性复原等。

(5)图像重建

图像的重建起源于CT技术的发展,是一门新兴的数字图像处理技术,主要是利用采集的数据来重建图像。图像重建的主要算法有代数法、迭代法、傅里叶反投影法和使用最为广泛的卷积反投影法等。

(6)图像形态学处理

图像形态学是数学形态学的延伸,是一门独立的研究学科。利用图像形态学处理技术,可以实现图像的腐蚀、膨胀和细化等效果。

(7)图像分割

从严格意义上来讲,图像分割也应隶属于图像形态学处理的范畴,但由于近年来这一研究方向的迅猛发展,使其成为了一个独立且热门的研究方向。图像分割的主要目的是将用户感兴趣的区域划分出来,主要方法有边缘分割法、阈值分割法、区域分割法以及纹理分割法等。

(8)图像编码

图像编码研究属于信息论中信源编码的范畴,其主要宗旨是利用图像信号的统计特性及人类视觉特性对图像进行高效编码,从而达到压缩图像的目的。图像编码是数字图像处理中一个经典的研究范畴,有60多年的研究历史,目前已经制定了多种编码标准,如H.261、JPEG和MPEG等。

(9)图像匹配

图像匹配是指通过一定的匹配算法在两幅或多幅图像之间识别同名点,如二维图像匹配中通过比较目标区和搜索区中相同大小的窗口的相关系数,取搜索区中相关系数最大所对应的窗口中心点作为同名点。其实质是在基元相似性的条件下,运用匹配准则的最佳搜索问题。图像匹配主要可分为以像素为基础的匹配和以特征为基础的匹配。

2. 数字图像处理的应用

目前数字图像处理的应用范围越来越广泛,已经渗透到一般工业、航空航天、医疗保健、军事、交通、国家安全、刑侦等各个领域,在国民经济中发挥着越来越大的作用。

(1)遥感技术中的应用

遥感图像处理的用途越来越大,并且其效率和分辨率也越来越高。它被广泛地应用于土地测绘、资源调查、气象监测、环境污染监督、农作物估产和军事侦察等领域。目前遥感技术已经比较成熟,但是还必须解决其数据量庞大、处理速度慢的缺点。

(2)医学应用

图像处理在医学上有着广泛的应用。其中最突出的临床应用就是超声、核磁共振和CT等技术。在医学领域利用图像处理技术可以实现对疾病的直观诊断和无痛、安全方便的诊断和治疗,受到广大患者的欢迎。

(3)安全领域

利用图像处理技术结合模式识别方法,可以应用在监控、指纹档案管理等安全领域中。

(4)工业生产

产品的无损检测也是图像处理技术的一项广泛的应用。

总之,图像处理技术的应用是相当广泛的,它在国家安全、经济发展、日常生活中充当着越来越重要的角色(有关图像处理的应用领域如表1-1所示),对国计民生有着不可忽略的作用。

表1-1 图像处理的应用领域

1.3 颜色模式和调色板

现实世界中形形色色的事物都是有颜色的。本节将介绍如何用计算机来描述这些事物图像的颜色,又如何在编程环境下操作这些颜色。

1.3.1 颜色模式

近年来,多媒体技术的飞速发展使得普通的多媒体计算机玩家也能对静止图像或视频图像进行各种处理。许多优秀的图像编辑处理软件,例如3dsMax、Photoshop等,都具有强大的图像处理功能,而对颜色的处理则是其强大功能不可缺少的一部分。因此,了解一些有关颜色的基本知识和常用的视频颜色模式,对于生成符合视觉感官需要的图像无疑是大有益处的。

颜色的实质是一种光波。它的存在是因为有3个实体:光线、被观察的对象以及观察者。人眼是把颜色当作由被观察对象吸收或者反射不同波长的光波形成的。例如,阳光下看到某物体呈现红色,那是因为该物体吸收了其他波长的光,而把红色波长的光反射到人眼里的缘故。当然,人眼所能感受到的只是波长在可见光范围内的光波信号。当各种不同波长的光信号一同进入眼睛的某一点时,人的视觉器官会将它们混合起来,作为一种颜色接受下来。同样,在对图像进行颜色处理时,也要进行颜色的混合,但要遵循一定的规则,即在不同颜色模式下对颜色进行处理。下面简单介绍数字图像处理中常见的两种颜色模式。

1. RGB颜色模式

根据人眼结构,所有颜色都可看作是3个基本颜色—红(red, R),绿(green,G)和蓝(b1ue, B)的不同组合。区分颜色常用3种基本特性参数:亮度、色调和饱和度。色调和饱和度合起来称为色度,颜色可用亮度和色度共同表示。当把红、绿、蓝三色光混合时,通过改变三者各自的强度比例可得到各种色调和饱和度的颜色。

其中,C代表某一特定色,表示匹配,RGB表示三原色,rgb代表比例系数,且有:

综上所述,任一种颜色都可用3个基本量来描述,所以建立颜色模型就是建立一个3D坐标系统,其中每个空间点都表示某一种颜色。图1-1为RGB颜色模式的示意图。

图1-1 RGB颜色模式

2. HSI颜色模式

HSI颜色模式是从人的视觉系统出发,用色调、色饱和度和亮度来描述色彩。HSI颜色模式可以用一个圆锥空间模型来描述。用这种描述HSI颜色模式的圆锥模型相当复杂,但确能把色调、亮度和色饱和度的变化情形表现得很清楚。通常把色调和饱和度通称为色度,用来表示颜色的类别与深浅程度。由于人的视觉对亮度的敏感程度远强于对颜色浓淡的敏感程度,为了便于色彩处理和识别,人的视觉系统经常采用HSI颜色模式,它比RGB颜色模式更符合人的视觉特性。在图像处理和计算机视觉中大量算法都可在HSI颜色模式中方便地使用,它们可以分开处理而且是相互独立的。因此,HSI颜色模式可以大大简化图像分析和处理的工作量。HSI颜色模式和RGB颜色模式只是同一物理量的不同表示法,因而它们之间存在着转换关系。

HSI颜色模式中的饱和度与颜色的白光光量刚好成反比,它可以说是一个颜色鲜明与否的指标。因此,如果在显示器上使用HSI模型来处理图像,将能得到较为逼真的效果。

▓色调(hue):指物体传导或反射的波长。更常见的是以颜色如红色,橘色或绿色来辨识,取0˚~360˚的数值来衡量。

▓饱和度(saturation):又称色度,是指色彩的强度或纯度。饱和度代表灰色与色调的比例,并以0%(灰色)到100%(完全饱和)来衡量。

▓亮度(intensity):是指颜色的相对明暗度,通常以0%(黑色)到100%(白色)的百分比来衡量。

下面对以上两种颜色模式之间的转换方法进行介绍。

对任何3个在[0,1]范围内的RGB值,其对应HSI模型中的ISH分量可分别由下面给出的公式计算:

若设SI的值在[0,1]之间,RGB的值也在[0,1]之间,则从HSI到RGB的转换公式如下所示。

1) 当H在[0˚, 120˚]之间,有

2) 当H在[120˚, 240˚]之间,有

3) 当H在[240˚, 60˚]之间,有

除了以上两种应用广泛的颜色模式外,还有其他常用的颜色模式,如Lab颜色模式、YUV颜色模式以及CMYK颜色模式等,本书对这些内容不再赘述,有兴趣的读者可以查阅相关的书籍资料。

1.3.2 Windows调色板

现实世界的颜色种类是无限的,但计算机显示系统所能表现的颜色数量有限,因此,为了使计算机能最好地重现实际图景,就必须用一定的技术来管理和取舍颜色。

1. 显示卡

Windows提供了一个独立于硬件的颜色接口。程序提供了一个“绝对的”颜色码,Windows把这个代码映射成计算机显示器上适当的颜色或颜色组合:对于一些常见的显示卡,大多数Windows程序都会优化应用程序的颜色显示。在计算机中,常用的显示卡为标准视频图形阵列显示卡(VGA),其使用18位的颜色寄存器,因此有一个262144种颜色的调色板。然而,由于显存的限制,标准VGA卡采用4色代码,这意味着,它一次只能显示16种颜色。因为Windows对标题、边框、滚动条等需要固定的颜色,程序就可以只使用16种“标准”的纯色,但并不能方便地使用显示卡显示的其他颜色。

每一种Windows颜色由8位红、绿、蓝值的组合来表示。16种标准VGA“纯”(非抖动)颜色如表1-2所示。

表1-2 16种标准VGA“纯”颜色

(1)256色显示卡

大多数显示卡可以在所有空间分辨率下支持8位颜色码,这意味着,它们可以同时显示256色。256色方式现在被认为是颜色编程中的最低要求。

如果Windows配置的是256色显示卡,则用户的程序限制在20种标准纯色内,除非激活Windows的调色板系统,该系统由MFC库的CPalette类和Windows应用程序编程接口支持。此时,可以从1670多万种颜色中选定自己的256种颜色。

安装了SVGA(Super VGA)256色显示驱动程序之后,可以获得的颜色比表1-2所列出的16种VGA颜色多4个,总共20个。表1-3列出了增加的4种颜色。

表1-3 SVGA中增加的4种颜色

(2)16位颜色显示卡

现代大多数的显示卡支持1024×758像素的分辨率,并且,如果有1MB显存,便可以支持8位颜色。如果显示卡有2MB显存,它就可以支持16位颜色,其中红、绿、蓝分别用5位表示。这意味着,可以同时显示32768种颜色。这似乎很多,但对丁红、绿、蓝每一种纯色,都只有32种渐变。

(3)24位颜色显示卡

高级的显示卡支持24位颜色。24位容量可以显示1670多万种纯色,这就是常说的24位真彩色,红、绿、蓝三原色分别用8位来表示。如果正在使用24位显示卡,就可以直接使用所有的颜色。如果1024×758像素分辨率下需要24位颜色,则需要2.5MB的显存。

在Windows中,采用了这种方法来表现颜色,其SDK提供了一个名为RGB的宏将不同的RGB颜色值转化为24位的颜色值,其原型为

        COLORREF RGB(BYTE bRed, BYTE bGreen, BYTE bBlue)

COLORREF表示颜色值的数据类型,是一个32位的无符号长整型。bRed、bGreen、bBlue分别表示红、绿、蓝三原色的浓度,其类型为BYTE,长度为8。其16进制数据表示形式为0x00bbggrr,其中字节rr、gg、bb分别表示红、绿、蓝三原色的浓度,最高位字节为0,保留用于与将来的系统兼容。

在真彩色系统中,每一个像素都用24位来表示,像素值与真彩色颜色值可一一对应,所以像素值就是所表现的颜色值。但对于仅能同时显示16色或256色的系统,每一个像素仅能分别采用4位或者8位来表示,像素值和真彩色颜色值不能一一对应,用像素值代表颜色值的方法将不能得到最佳的效果,而必须采用调色板技术。所谓调色板,就是在16色或者256色显示系统中,将图像中出现最频繁的16或256种颜色所组成的颜色表。对这些颜色按4位或者8位,即0~15或者0~255进行编号,每一编号代表其中的一种颜色。这种颜色编号称为颜色的索引号,4位或者8位的索引号与24位的颜色值的对应表称为颜色查找表。使用调色板的图像称为调色板图像,它们的像素值并不是颜色值,而是颜色在调色板查找表中的索引号。

2. 调色板

为了保证Windows基本显示界面的一致性,Windows保留了一个上述20种颜色的内部系统调色板,用来绘制窗口的图标、边界等通用界面。该调色板在所有的显示设置中都保持不变。在16色的显示系统中,系统调色板通过16种颜色的抖动来产生其余4种颜色。在256色的显示系统中,Windows也保持20种颜色的次序,其余236种颜色由当前的调色板分配。

需要注意的是,真彩色不需要调色板,其中的像素值就是24位的颜色值。16色系统通常采用Windows的内部系统调色板,一般并不直接操作调色板。所以通常,仅在256色显示系统中操作调色板。

在调色板显示系统中,每一幅图像都有自己的调色板,而每一个活动窗口或应用程序都根据自己的需要操作当前的调色板,但是对调色板的操作将影响整个显示系统。这将导致如下的结果,即如果两个窗口或者应用程序使用了不同的调色板,每一时刻将只有一个窗口或应用程序的显示是正确的,另一个将被强制使用不同的调色板而显示出错误的颜色。通常是当前活动窗口或应用程序对当前调色板具有较高的控制优先级。

在窗口中显示的每一个图像的调色板都保存在内存中,称为逻辑调色板显示系统当前使用的调色板称为硬件调色板或者系统调色板,在任一时刻,仅有一个系统调色板,由它决定了当前屏幕上实际的颜色显示。

调色板在windows中是一个重要的概念,Windows调色板编程非常复杂。但是,只有在用户需要以每像素8位以下模式运行程序时,才需要处理调色板。

(1) Win32 SDK中的调色板操作函数

在Visual C++中,MFC定义了CPalette类来对调色板进行操作。CPalette类封装的功能函数如表1-4所示。

表1-4 CPalette类封装的函数

最常用的调色板函数是创建调色板函数,其原型为

        BOOL CreatePalette(LPLOGPALETTE lpLogPalette);

参数lpLogPalette是指向逻辑调色板颜色表项结构LOGPALETTE的指针,该结构定义如下:

        typedef struct tagLOGPALETTE
        {
            WORD palVersion;                   //调色板的版本号,应指定为0×300
            WORD palNumEntries;               //调色板中表项数
            PALETTEENTRY palPalEntry[1];    //调色板表项数组
        }LOGPALETTE;

调色板表项结构PALETTEENTRY定义了调色板中每一颜色表项的颜色和使用方式,定义如下:

        typedef struct tagPALETTEENTRY
        {
              BYTE peRed;                              //该颜色中红色分量值
              BYTE peGreen;                           //该颜色中绿色分量值
              BYTE peBlue;                             //该颜色中蓝色分量值
            BYTE peFlags;                              //该颜色被使用方式
        }PALETTEENTRY

其中peFlags表示颜色被使用的方式,可以为如下值之一。

▓NULL:普通方式。

▓PC_EXPLICT:逻辑调色板表项的低位字表示硬件调色板索引。

▓PC_NOCOLLAPSE:规定颜色系统中调色板的未使用的表项,这些颜色不利用系统调色板中的匹配颜色。

▓PC_RESERVED:指定用于调色板动画的逻辑调色板表项。

如果窗口或应用程序想按自己的调色板显示颜色,就必须将自己的调色板载入系统调色板中,这叫做实现调色板,实现调色板包括两个步骤,即将调色板选择到设备上下文(device context)中,并在设备上下文中实现它,函数CDC::SelectPalette和CDC::RealizePalette用于该操作。函数原型为

        CPalette *SelectPalette(CPalette* pPalette, BOOL bForceBackground);
        UNIT RealizePalette()

Windows系统用调色板管理器来管理与调色板有关的操作。活动窗口的调色板一般即是当前的系统调色板,所有的非活动窗口都必须按照此调色板来显示自己的颜色;但调色板管理器将自动使用系统调色板中最近似的匹配颜色来映射相应的显示颜色。

Windows定义3个调色板消息用于通知窗口系统调色板的变化状态,窗口接受到这些消息后,可以进行适当的调色板操作来维护自身的显示。这3个消息为

▓WM_QUERYNEWPALETTE:通知窗口它将接收到输入焦点,给它一次机会实现其自身的逻辑调色板。

▓WM_PALETTECHANGED:通知窗口系统调色板已经被其他窗口改变。

▓WM_PALETTECHANGING:通知窗口系统调色板将被其他窗口改变。

为保证图像显示颜色的正确,每一窗口接收到WM_PALETTECHANGED消息时都实现其逻辑调色板,并重画客户区。

Windows中的位图操作与调色板密切相关。Windows使用两种不同的位图,即设备相关位图DDB(Device Dependent Bitmap)和设备无关位图DIB(Device Independent Bitmap)。DIB位图文件中包含该位图的逻辑调色板的颜色表,其像素值是该调色板中的颜色索引值。DDB位图中不包含调色板信息,其像素值是该系统调色板中的颜色索引值。在结构上,DIB与DDB的主要区别在于DIB包含一个名为RGBQUAD的结构,它描述了DIB位图的颜色表。

Win32 SDK使用调色板句柄HPALETTE来表示调色板,HPALETTE与CPalette对象的相互转换可由函数CPalette::FromHandle和CPalette::GetSafeHandle实现。

(2)自定义调色板处理函数

虽然Win32 SDK和MFC CPalette类提供了不少调色板函数,但还是不能满足要求。这里定义两个在实际编程中需要用到的函数,以方便对调色板的操作。这两个调色板操作函数如表1-5所示。

表1-5 调色板操作函数集

下面对这两个调色板操作函数进行说明,并给出具体的代码。

函数CopyPalette实现调色板的复制。其中需要说明的是,复制调色板时先利用GetPalette Entries判断源调色板是否为空,但并不将源调色板的表项复制。得到源调色板表项数后,分配内存,再次调用GetPaletteEntries将源调色板的表项复制到目标调色板中。其代码如下:

        /*******************************************
        *\函数名称:CopyPalette
        *\输入参数:   HPALETTE      hpalSrc               需要复制的源调色板句柄
        *\返回值: HPALETTE                     如果操作成功,则返回复制的调色板句柄
        *\说明:该函数将创建一个新的调色板,并从指定的调色板复制调色板内容
        *****************************************/
        HPALETTE CopyPalette(HPALETTE hPalSrc)
        {
          PLOGPALETTE           plogPal;                  //调色板指针,临时变量
          HPALETTE         hPalette;                       //声明一个调色板句柄
          HANDLE              hTemp;                        //声明一个临时句柄
          int        iNumEntries=0;                        //调色板表项数
          iNumEntries=GetPaletteEntries(hPalSrc,0,iNumEntries,NULL);
          //获取调色板中的表项数
          if(iNumEntries==0) return (HPALETTE) NULL;
          //分配调色板内存,得到句柄
          hTemp=GlobalAlloc(GHND,sizeof(DWORD)+sizeof(PALETTEENTRY)*iNumEntries);
          if(!hTemp) return(HPALETTE) NULL;
          plogPal=(PLOGPALETTE)GlobalLock(hTemp);    //得到调色板的指针
          if(!plogPal) return(HPALETTE) NULL;
          plogPal->palVersion=0×300;                    //设置调色板的信息
          plogPal->palNumEntries=(WORD)iNumEntries;
          GetPaletteEntries(hPalSrc,0,iNumEntries,plogPal->palPalEntry);
          //获取指定范围的调色板表项
          hPalette=CrestePalette(plogPal);             //创建一个Windows调色板
          GlobalUnlock(hTemp);                            //释放已分配的内存
          GlobalFree (hTemp);
          Return hPalette;                                 //返回调色板句柄
        }

GetSystemPalette是一个很有用的函数,它将当前正在使用的系统调色板进行复制,并返回调色板句柄。其实现代码如下:

        /********************************************************
        *\函数名称: GetSystemPalette( )
        *\输入参数:无
        *\返回值::HPALETTE                                         系统调色板句柄
        *\说明:该函数获得当前正在使用的系统调色板的句柄
        ******************************************************/
        HPALETTE GetSystemPalette()
        {
          HDC hDC;                                            //设备上下文
          static   HPALETTE hPal=NULL;                     //声明调色板句柄,指针等临时变量
          HANDLE   hLogPal;
          LPLOGPALETTE   lpLogPal;
          int   nColors;                                      //当前系统调色板的颜色数
          hDC=GetDC(NULL);                                  //获得当前系统设备上下文
          if(!hDC)    return   NULL;
          //获得当前系统调色板的颜色数目并给调色板分配内存
          nColors=(1<<(GetDeviceCaps(hDC,BITSPIXEL)*GetDeviceCaps(hDC,PLANES)));
          hLogPal=GlobalAlloc(GHND,sizeof(LOGPALETTE)*nColors*sizeof(PALETTEENTRY));
          if(!hLogPal)     return NULL;
          lpLogPal=(LPLOGPALETTE)GlobalLock(hLogPal)  //得到调色板内存指针
          lpLogPal->palVersion=0×300;                    //设置调色板信息
          lpLogPal->palNumEntries=nColors;
          //将系统的调色板复制到当前的逻辑调色板中
          GetSystemPaletteEntries(hDC,0, nColors, (LPPALETTEENTRY)( lpLogPal->lpLogPalEntry));
          hPal=CreatePalette(lpLogPal);                  //创建Windows调色板
          GlobalUnlock(hLogPal);                           //释放已分配内存并删除临时对象
          GlobalFree(hLogPal);
          ReleaseDC(NULL,hDC);
          Return hPal;                                       //返回
        }

1.4 数字图像文件格式

数字图像在计算机中是以文件的形式进行存储的,不同的应用背景对文件的存储格式提出了不同的要求。

1.4.1 BMP文件格式

计算机内的数字图像通常用由采样点的值所组成的矩阵来表示:

每一个采样单元叫做一个像素(pixel),上式中,MN分别为数字图像在横、纵方向上的像素总数。由于数字图像格式不同,图像文件一般具有不同的扩展名。最常见的图像格式是位图格式,其文件名以BMP为扩展名。

BMP文件由文件头、位图信息头、颜色信息和图像数据4部分组成,结构如表1-6所示。

表1-6 位图结构

以下将对这4个组成部分进行简要介绍。

1.位图文件头

位图文件头结构含有BMP文件的类型、文件大小和位图起始位置等信息。其结构定义如下:

        typedef struct tagBITMAPFILEHEADER
        {
            WORD bfType;         //位图文件的类型,必须为BMP
            DWORD bfSize;        //位图文件的大小,以字节为单位
            WORD bfReserved1;   //位图文件保留字,必须为0
            WORD bfReserved2;   //位图文件保留字,必须为0
            DWORD bfOffBits;    //位图数据起始位置,以相对于文件头的偏移量表示,以字节为单位
        }BITMAPFILEHEADER;

2. 位图信息头

BMP位图信息头结构用于说明位图的尺寸等信息。其结构定义如下:

        typedef struct tagBITMAPINFOHEADER
        {
            DWORD biSize;               //本结构所占用字节数
            LONG biWidth;               //位图的宽度,以像素为单位
            LONG biHeight;              //位图的高度,以像素为单位
            WORD biPlanes;              //目标设备的级别,必须为1
            WORD biBitCount;           //每个像素所需的位数,必须是1(双色),4(16色),8(256色)
                                      //或24(真彩色)之一
            DWORD biCompression;      //位图压缩类型,必须是0(不压缩),1(B1_RLE8压缩类型)
                                      //或2(B1_RLE4压缩类型)之一
            DWORD biSizeImage;         //位图的大小,以字节为单位
            LONG biXPelsPerMeter;     //位图水平分辨率,以像素/米为单位
            LONG biYPelsPerMeter;     //位图垂直分辨率,以像素/米为单位
            DWORD biClrUsed;           //位图实际使用的颜色表中的颜色数
            DWORD biClrImportant;     //位图显示过程中重要的颜色数
        }BITMAPINFOHEADER

3. 颜色表

8位位图文件中,每个像素占8位,可表示256种颜色,8位位图文件含有颜色表,而真彩色位图文件中不含颜色表。颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD类型的结构,定义一种颜色。RGBQUAD结构的定义如下:

        typedef struct tagRGBQUAD
        {
            BYTE rgbBlue;                    //蓝色的亮度(值范围为0~255)
            BYTE rgbGreen;                   //绿色的亮度(值范围为0~255)
            BYTE rgbRed;                     //红色的亮度(值范围为0~255)
            BYTE rgbReserved;               //保留,必须为0
        }RGBQUAD;

颜色表中RGBQUAD结构数据的个数由biBitCount来确定。

▓ 当biBitCount=1、4、8时,分别有2、16、256个表项。

▓ 当biBitCount=24时,没有颜色表项。

位图信息头和颜色表组成位图信息,BITMAPINFO结构定义如下:

        typedef struct tagBITMAPINFO
        {
            BITMAPINFOHEADER bmiHeader;      //位图信息头
            RGBQUAD bmiColors[1];              //颜色表
        }BITMAPINFO;

4. 位图数据

位图数据记录了位图的每一个像素值,记录顺序在扫描行内是从左到右,扫描行之间是从下到上。位图的一个像素值所占的字节数如下:

▓ 当biBitCount=1时,8个像素占1个字节。

▓ 当biBitCount=4时,2个像素占1个字节。

▓ 当biBitCount=8时,1个像素占1个字节。

▓ 当biBitCount=24时,1个像素占3个字节。

位图像素的位数可为1、4、8和24,其图像的颜色数分别为2、16、256和真彩色。其中前3种对应的颜色表,而24为位图的颜色表为空,其像素值就是颜色值。如果BITMAP、INFOHEADER的biClrUsed域不为“0”,则其值为位图所使用的颜色数,也即是颜色表的表项数。如果该域为“0”,则位图的颜色表的表项数是满的,即像素颜色位数为1、4、8,则其颜色表项数分别为2、16、256。颜色表中一般将最重要的颜色排在前面。

8位位图的数据紧跟在颜色表后面,其数据是颜色表的索引值,数据可以是不压缩的,也可以是压缩的。对4位位图和8位位图,可以采用RLE(游程长度编码)压缩,分别称为RLE4和RLE8位图。位数据以行为单位存储,每行都被填充到一个4字节边界,即每行所占的存储长度总是4字节(32位)的倍数,不足时将多余位用“0”填充。位图行的存储次序是颠倒的,即位图文件中第一行数据对应的是位图的最底行。对于像素位数为1的DIB位图,其每个像素只占1位,每个字节存储8个像素。字节的最高位对应于最左边的像素。在像素位数为24的DIB位图中,每个像素占3字节,从左到右的每一个字节分别存储蓝、绿、红的颜色值。每行用“0”填充到一个4字节边界。

Windows位图结构中数据域的含义如表1-7所示。

表1-7 Windows位图结构数据域的含义

1.4.2 其他文件格式

除了上面提到的BMP文件之外,在日常应用中,还有一些常用的图像文件格式,如TIFF文件、GIF文件、PostScript文件以及JPEG文件等。

1. TIFF文件

TIFF(Tag Image File Format,标签图像文件格式)文件最初由Aldus公司与微软公司为PostScript打印联合开发。TIFF与JPEG和PNG一起成为流行的高位彩色图像格式。TIFF格式在业界得到了广泛的支持,如Adobe公司的Photoshop、Jasc的GIMP、Ulead PhotoImpact和Paint Shop Pro等图像处理应用、QuarkXPress和Adobe InDesign这样的桌面印刷和页面排版应用,扫描、传真、文字处理、光学字符识别和其他一些应用等都支持这种格式。从Aldus获得了PageMaker印刷应用程序的Adobe公司现在控制着TIFF规范。术语“Tagged Image File Format”或者“Tag Image File Format”在一些早期的TIFF规范中是作为副标题存在的。目前的TIFF规范TIFF 6.0不再使用这些术语,现在的名字仅仅叫做“TIFF”。

TIFF最初的设计目的是为了1980年代中期桌面扫描仪厂商达成一个公用的扫描图像文件格式,而不是每个厂商使用自己专有的格式。在刚开始的时候,TIFF只是一个二值图像格式,因为当时的桌面扫描仪只能处理这种格式。随着扫描仪的功能越来越强大,并且桌面计算机的磁盘空间越来越大,TIFF逐渐开始支持灰阶图像和彩色图像。

(1) 应用背景

用于桌面排版以及与之相关的应用程序的数据交换。

(2) 文件结构

TIFF格式有3级体系,从高到低依次为:文件头、一个或多个称为IFD的包含标记指针的目录以及数据。

体系的最高层是文件头,只包含3个表项:

▓ 一个代码,指明字节顺序(低字节在先还是高字节在先)。

▓ 一个把文件标识为TIFF文件的代码号。

▓ 一个指向图像文件目录(Image File Directory,IFD)的指针。

IFD提供一系列的指针(索引),这些指针指示了各种有关的数据字段在文件中的开始位置,并给出每个字段的数据类型(如1字节整型)及长度。这种方法允许数据字段定位在文件的任何地方,可以是差不多任意长度,并可以包含大量信息。例如,一个指针可能指向关于彩色调色板数据的一个786字节字段;另一个可能指向扫描仪的一条64字节灰度修正曲线。在一个文件中可能有几个相关的图像,这时可以有几种IFD。IFD的最后一个表项指向任何一个后续的IFD。

每个指针都有一个标记值指明所指向的数据字段类型的一个代码号。TIFF规范列出了所有正式的、非专用的标记号,给予它们有用的名字(如SamplesPerPixel,十进制代码为277),并描述指针所识别的数据,告知数据的组织方法。

(3)优缺点

TIFF的主要优点是适合于广泛的应用程序,并且与计算机的结构、操作系统和图形硬件无关。它可以处理黑白、灰度和彩色图像,允许用户针对扫描仪、监视器或打印机的独特性能进行调整。TIFF格式不易过时。因此对媒体之间的数据交换,TIFF是位图模式的最佳选择之一。

TIFF允许多达48位的色彩分辨(R、G、B各16位),可以作为全RGB色彩,也可以作为64K色彩的调色板。如果需要的话,TIFF还允许使用模糊度或清晰度这样的图像数据。TIFF的文档组织也比较丰富,在其规范说明中有不少关于计算机图形学的背景介绍。规范可由CompuServe(用GO ALDSVC)或AppleLink(用Aldus开发程序图标)。

TIFF的全面性也产生了一些问题,它需要大量的编程工作来实现全面译码。例如,TIFF数据可以用几种不同的方法压缩,为了达到有活力即功能全面,一个TIFF读出程序(用于读TIFF文件的一般程序代码)必须支持这些不同的压缩方法。

作为应用程序之间的一种数据交换格式,TIFF有一个明显的缺陷。销售商可以用Aldus的Developers Desk注册专用的、秘密的数据字段格式。这个性能具有不小的影响,即允许销售商在声明TIFF兼容性的同时,实际维护一种秘密的方式。开发者不能全面或恰当地执行这一复杂文件格式的规范,从而使这一缺陷进一步复杂化了。

2. GIF文件

GIF是图像交换格式(Graphics Interchange Format)的简称,它是由美国CompuServe公司在1987年提出的图像文件格式,它最初的目的是希望每个BBS的使用者能够通过GIF图像文件轻易存储并交换图像数据,这也就是它为什么被称为图像交换格式的原因了。GIF文件格式采用了一种经过改进的LZW压缩算法,通常称之为GIF-LZW算法。这是一种无损的压缩算法,压缩效率也比较高,并且GIF支持在一幅GIF文件中存放多幅彩色图像,并且可以按照一定的顺序和时间间隔将多幅图像依次读出并显示在屏幕上,这样就可以形成一种简单的动画效果。尽管GIF最多只支持256色,但是由于它具有极佳的压缩效率并且可以做成动画而早已被广泛接纳采用。

(1)应用背景

CompuServe网络中图形数据的在线传输。

(2)文件结构

为了理解GIF,请记住它主要是为数据流而设计的一种传输格式,而不是作为文件的存储格式。换句话说,它具有顺序的组织形式(像TIFF那样的存储格式,则更普遍地使用随机组织形式,而不是顺序组织形式)。这种顺序性质对图像没有什么实际影响,除了多幅图像的顺序传输和显示这种专门和少见的情况。但是,当学习GIF时,想象多幅彩色图像顺序地出现在一个屏幕上是会有用的。

GIF有5个主要部分以固定顺序出现,所有部分均由一个或多个块(b1ock)组成。每个块由第一个字节中的标识码或特征码标识。这些部分的顺序为:头块、逻辑屏幕描述块、可选的“全局”色彩表块(调色板)、各图像数据块(或专用的块)以及尾块(结束码)。

▓ 头是一个块,它识别数据流为GIF,并指示恰当地解释后面的数据所需的最早版本的GIF解码程序(87a或89a)。

▓ 逻辑程序描述块定义了包围后面所有图像的一个图像平面的大小、纵横尺寸比例以及色彩深度(类似于产生图像的监视器屏幕)。它还指明后面跟随的是否为“全局”色彩表。

▓ 全局色彩表(如果存在)构成一个24位RGB三元组的调色板(每种底色为一个字节)。如果后面的图像没有其自己的“局部”调色板,那么全局色彩表就是缺省调色板。

▓ 后续数据作为“图形”或“专用”块出现。图形块典型地包含一个或多个位图图像,也可能是覆盖的文本。专用块或者包含一个专用应用程序码,或者包含一句不可打印的注释。

▓ 最后的尾块只是值为3B(十六进制)的一个字节,表示数据流已结束。

当数据用于图像时(大多数情况如此),3个部分块以下列顺序出现:图像描述(图像的大小和调色板以及它在逻辑屏幕中的位置),可选的“局部”色彩表(只为这个图像服务的色彩表)以及用LZW方法压缩的位图数据。位图数据分成若干个长达256字节的“子块”。数据还可能包括LZW算法所使用的专门的“清除码”(clear codes)。

对于多幅图像,按此顺序重复即可。当要完成专门的图像排序或覆盖时,详述这一个操作的一个“图形引出块”就加在图像之前。

(3)优缺点

GIF提供足够的信息并很好地组织这些信息,使得许多不同的输入输出设备能够方便地交换图像。由于CompuServe网络的广泛流行,许多平台都运行GIF。CompuServe通过免费发行格式说明书来推广自己。GIF支持24位色彩,即一个最多有256种颜色的调色板,图像大小最多是64K×64K个像素点。GIF的特点包括LZW压缩法、多幅图像的定序或覆盖、交错屏幕绘图以及文本覆盖。

现行的GIF版本最多有256种24位彩色。它没有为存储灰度或彩色校正数据提供便利,也不能存储CMYK或HSI模型的数据。

3. PostScript文件

PostScript语言是Adobe公司设计的用于打印机打印文件的页面描述语言。除了用于在纸张上打印文字和图像之外,它的功能也像Basic语言、C语言或任何其他编程语言一样。当你在PostScript打印机上工作并告诉文字处理器(或任何其他应用程序)打印页面时,计算机就会用PostScript语言编写一段程序描述该页面,并将这个程序传送给打印机。打印机实际上在一台功能齐全并装有PostScript语言解释器的计算机上执行这个程序,将图形画在内存中的虚拟纸张上,然后将其打印到纸上。

(1)应用背景

最初用于打印机和其他输出设备,现在也用于图像的存储和交换,尤其是已封装的PostScript格式。

(2)文件结构

PostScript是一种编码语言,因此除了检查语法和语义的正确性外,它不要求任何特别结构。但是,对于有效的信息交换和与页、设备无关的方式,确有一种固有的逻辑序列,这也正是PostScript已做到的。Adobe已经通过指定称作文档结构约定(Document Structure Convention)DSC 3.0版的一些约定使这样一个序列规范化。遵循这些约定的文件以序言段开始,后面跟有脚本段。

由DSC强制的结构有助于确保假脱机打印程序、后处理程序和其他资源(这些均称作文档管理程序)正确地处理PostScript文件。为进一步控制这些资源,Adobe还建立了一种称为DSC注释的第二语言,作为DSC的一部分。这种语言的术语出现在PostScript语言的程序中,它们在PostScript解释程序中是作为注释出现的,所以被它忽略,但由文档管理程序读取。作为控制文档管理程序工作的一部分,这些注释用来对文件中的结构(如序言和页)进行分界。

PostScript文件不必为了得到PostScript解释程序的正确执行而一定要符合DSC结构规范或者包含DSC注释。如果不符合,则文档管理对这类文件的处理就可能出点问题。EPS文件以及其他的PostScript变体里需要某一些DSC注释。

DSC 3.0版下的PostScript文件的一般结构如下:

        序言
          
          缺省(可选)
          过程
        脚本
          文档设置
          PostScript码的页(可有多页)
        ...
        ...
          文档尾

这个结构通过使用DSC注释实现,注释通常以双百分号(%%)开始,所包含字符不超过255个,并以第一个CR(回车)或CR/LF(换行)对终止。DSC注释通常包含一个关键词和参量。整行都是大小写敏感的。许多词结果包括一个冒号,它不能被忽略。参数由空白字符分隔。

DSC注释的一个名为体注释的子集用于上述的每一段开始和结束处的分界。例如,DSC注释%%BeginSetup和%%EndSetup分界文档设置节。

这里只讨论最为有用的DSC注释(也就是说,在一页上使用简单的图形,没有文本),产生的简单化结构如下:

        序言
          
          过程
        脚本
          PostScript
          文档尾

(3)优缺点

PostScript是桌面排版的事实上的标准,并结合了许多高级的图形性能,如36位RGB,单色和彩色的标准化和校正、矢量字模和图像的线性变换。

由于图像可以用许多种不同的方法表达,一个通用的PostScript读取程序(解释程序)的实现是一项大型工作(PostScript的完整定义参见Adobe公司的《PostScript语言参考手册》。而且,PostScript通常用ASCII形式记录(虽然Display和Level2 PostScript也支持二进制和压缩的数据),这就使位图文件变大并且/或者显示很慢。

4. JPEG文件

JPEG是Joint Photographic Experts Group(联合图像专家组)的缩写,文件后辍名为.jpg或.jpeg,是最常用的图像文件格式,由一个软件开发联合会组织制定,是一种有损压缩格式,能够将图像压缩在很小的储存空间,图像中重复或不重要的信息会被去除,因此容易造成图像数据的损伤。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量明显降低,如果追求高品质图像,不宜采用过高的压缩比例。但是JPEG压缩技术十分先进,它用有损压缩方式去除冗余的图像数据,在获得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得到较好的图像品质。而且JPEG是一种灵活的格式,具有调节图像质量的功能,允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在10:1到40:1之间,压缩比越大,品质就越低;相反地,压缩比越小,品质就越好。比如可以把1.37MB的BMP位图文件压缩至20.3KB。当然也可以在图像质量和文件尺寸之间找到平衡点。JPEG格式压缩的主要是高频信息,对色彩的信息保留较好,适合应用于互联网,可减少图像的传输时间,可以支持24bit真彩色,也普遍应用于需要连续色调的图像。

(1)应用背景

摄影图像的存储和显示。

(2)压缩过程

JPEG有许多编码和压缩选项,下面只讨论基本的JPEG标准,它是所有的实现必须支持并且所有当前实现都使用的一种JPEG版本。

大多数实现首先都要把RGB图像转换成“亮度/颜色”的彩色空间,也就是转换成灰度图像加上两个通道的颜色差别信息。光栅数据可以二次抽样,把相邻的像素组合成一个值。之后用离散余弦变换(Discrete Cosine Transform,DCT)将光栅数据转化成变化率信息。量化过程把DCT编码产生的结果截断到一个较小压缩的值范围之内,这就是造成JPEG有损的一步;量化系数决定了丢失的数据量以及压缩的程度和重建图像的质量。最后,量化的结果用Huffman或算术编码来压缩,以产生最终的输出。

还原压缩是上述步骤的逆过程,即还原量化结果,使用一个逆DCT来重建图像。量化时丢失的低序位是不可重建的,所以还原程序要相应插入0。

(3)优缺点

JPEG是用于摄影图像的最好压缩方法。

软件压缩和还原速度慢,标准仍然在发展演化,而且由于标准中有可选项,所以存在不兼容的实现。此外,许多早期的实现存在特有的不兼容的地方。

除去以上这些常用的图像文件格式之外,还有很多其他的格式,如PCX、GEM、WordPerfect、PNG、PBM、DXF、WMF以及RIB等。这些格式的适用范围各不相同,也各有优缺点,读者可以根据自己的应用选择合适的图像文件类型。

1.5 使用Visual C++处理数字图像的基本方法

通过前面的学习,已经对VC的基本编程规范以及数字图像的基本概念有了一定的了解。本节将对采用VC对数字图像进行处理的一些基本方法作出简要的介绍。

1.5.1 使用GDI+处理数字图像

GDI+接口是Microsoft Whistler操作系统中的一部分,它是GDI的一个新版本,不仅在GDI基础上添加许多新特性而且对原有的GDI功能进行优化。在为开发人员提供的二维矢量图形、文本、图像处理、区域、路径以及图形数据矩阵等方面构造了一系列相关的类,如Bitmap(位图类)、Brush(画刷类)、Color(颜色类)、Font(字体类)、Graphics(图形类)、Image(图像类)、Pen(画笔类)和Region(区域类)等。其中,Graphics是GDI+接口中的一个核心类,许多绘图操作都可用它来完成。

首先介绍GDI+的新特性及其编程方式的改变,然后介绍用Visual C++.NET在基于对话框和单文档/多文档等应用程序中使用GDI+的一般方法。

1. GDI+新特性

GDI+与GDI相比,增加了下列新的特性。

(1)渐变画刷

以往GDI实现颜色渐变区域的方法是通过使用不同颜色的线条来填充一个裁剪区域而达到的。现在GDI+拓展了GDI功能,提供线型渐变和路径渐变画刷来填充一个图形、路径和区域,甚至也可用来绘制直线、曲线等。这里的路径可以视为由各种绘图函数产生的轨迹。

(2)样条曲线

对于曲线而言,最具实际意义的莫过于样条曲线。样条曲线是在生产实践的基础上产生和发展起来的。模线间的设计人员在绘制模线时,先按给定的数据将型值点准确地“点”到图板上。然后,采用一种称为“样条”的工具(一根富有弹性的有机玻璃条或木条),用压铁强迫它通过这些型值点,再适当调整这些压铁,让样条的形态发生变化,直至取得合适的形状,才沿着样条画出所需的曲线。如果把样条看成弹性细梁,那么压铁就可看成作用在这梁上的某些点上的集中力。GDI+的Graphics::DrawCurve函数中就有一个这样的参数用来调整集中力的大小。除了样条曲线外,GDI+还支持原来GDI中的Bezier曲线。

(3)持久的路径对象

在GDI中,路径是隶属于一个设备环境(上下文),也就是说一旦设备环境指针超过有效期,路径也会被删除。而GDI+是使用Graphics对象来进行绘图操作,并将路径操作从Graphics对象分离出来,提供一个GraphicsPath类供用户使用。这就是说,不必担心路径对象会受到Graphics对象操作的影响,从而可以使用同一个路径对象进行多次的路径绘制操作。

(4)矩阵和矩阵变换

在图形处理过程中常需要对其几何信息进行变换以便产生复杂的新图形,矩阵是这种图形几何变换最常用的方法。为了满足人们对图形变换的需求,GDI+提供了功能强大的Matrix类来实现矩阵的旋转、错切、平移、比例等变换操作,并且GDI+还支持Graphics图形和区域(Region)的矩阵变换。

(5)Alpha混色

在图像处理中,Alpha用来衡量一个像素或图像的透明度。在非压缩的32位RGB图像中,每个像素是由4个部分组成:一个Alpha通道和3个颜色分量(RGB)。当alpha值为0时,该像素是完全透明的,而当alpha值为255时,该像素是完全不透明的。

Alpha混色是将源像素和背景像素的颜色进行混合,最终显示的颜色取决于其RGB颜色分量和alpha值。它们之间的关系可用下列公式来表示:显示颜色=源像素颜色× alpha/255+背景颜色×(255-alpha)/255。

GDI+的Color类定义了ARGB颜色数据类型,从而可以通过调整alpha值来改变线条、图像等与背景色混合后的实际效果。

除了上述新特性外,GDI+还将支持重新着色、色彩修正、消除走样、元数据以及Graphics容器等特性。

2. GDI+编程模块的变化

为了简化GDI+的编程开发过程,Microsoft对GDI+的编程模块作了一些调整,这主要体现在以下几个方面。

(1)不再使用设备环境或句柄

在使用GDI绘图时,必须要指定一个设备环境(DC)。MFC为设备环境提供了许多由基类CDC派生的设备环境类,如CPaintDC、CClientDC和CWindowDC等,用来将某个窗口或设备与设备环境类的句柄指针关联起来,所有的绘图操作都与该句柄有关。而GDI+不再使用这个设备环境或句柄,取而代之是使用Graphics对象。

与设备环境相类似,Graphics对象也是将屏幕的某一个窗口与之相关联,并包含绘图操作所需要的相关属性。但是,只有这个Graphics对象与设备环境句柄还存在着联系,其余的如Pen、Brush、Image和Font等对象均不再使用设备环境。

(2)绘图方式的变化

先来看看同样绘制一条从点(20, 10)到点(200, 100)直线的GDI和GDI+代码,假设这些代码都是添加在OnDraw函数中。

绘制该直线的GDI代码如下:

        void CMyView::OnDraw(CDC* pDC)
        {
          CMyDoc* pDoc = GetDocument();
          ASSERT_VALID(pDoc); CPen newPen( PS_SOLID, 3, RGB(255, 0, 0));
          CPen* pOldPen = pDC->SelectObject( &newPen);
          pDC->MoveTo(20, 10);
          pDC->LineTo(200, 100);
          pDC->SelectObject(pOldPen);
        }

绘制该直线的GDI+代码如下:

        void CMyView::OnDraw(CDC* pDC)
        {
          CMyDoc* pDoc = GetDocument();
          ASSERT_VALID(pDoc);
          using namespace Gdiplus;                        // 使用名称空间
          Graphics graphics(pDC->m_hDC);
          Pen newPen( Color(255, 0, 0 ), 3);
          graphics.DrawLine(&newPen, 20, 10, 200, 100);
        }

从上面代码可以看出,GDI先创建一个Cpen(画笔)对象,然后通过SelectObject将该画笔选入到设备环境(pDC)中。接下来调用相应的画线函数,最后恢复设备环境中原来的GDI对象。而GDI+是先使用Graphics类创建一个与pDC设备环境相关联的Graphics对象,然后使用Pen类进行画笔的创建,最后调用相应的画线方法。由于Pen和设备环境是相互独立的,因而不需要像GDI那样恢复设备环境中原来的设置,而且Pen和Graphics对象的创建不存在先后次序。

3. Graphics绘图方法直接将Pen、Brush等对象作为自己的参数

从上面的代码可以看出,Graphics绘图方法直接将Pen对象作为自己的参数,从而避免了在GDI使用SelectObject进行繁琐的切换,类似的还有Brush、Path、Image和Font等。

4. 不再使用“当前位置”

GDI绘图操作(如画线)中总存在一个被称为“当前位置”的特殊位置。每次画线都是以此当前位置为起始点,画线操作结束之后,直线的结束点位置又成为了当前位置。设置当前位置的理由是为了提高画线操作的效率,因为在一些场合下,总是一条直线连着另一条直线,首尾相接。有了当前位置的自动更新,就可避免每次画线时都要给出两点的坐标。尽管有其必要性,但是单独绘制一条直线的场合总是比较多的,因此GDI+取消这个“当前位置”以避免当无法确定“当前位置”时所造成的绘图的差错,取而代之的是直接在DrawLine中指定直线起止点的坐标。

5. 形状轮廓绘制和填充采用不同的方法

GDI总是让形状轮廓绘制和填充使用同一个绘图函数,例如Rectangle。轮廓绘制需要一个画笔,而填充一个区域需要一个画刷。也就是说,不管是否需要填充所绘制的形状,都需要指定一个画刷,否则GDI采用默认的画刷进行填充。这种方式确实带来了许多不便,现在GDI+将形状轮廓绘制和填充操作分开而采用不同的方法,例如DrawRectangle和FillRectangle分别用来绘制和填充一个矩形。

6. 简化区域的创建

GDI提供了许多区域创建函数,如CreateRectRgn、CreateEllpticRgn、CreateRound RectRgn、CreatePolygonRgn和CreatePolyPolygonRgn等。诚然,这些函数给我们带来了许多方便。但在GDI+中,由于为了便于将区域引入矩阵变换操作,GDI+简化一般区域创建的方法,而将更复杂的区域创建交由Path接管。由于Path对象是与设备环境分离开来的,因而可以直接在Region构造函数中加以指定。

7. 用Visual C++.NET使用GDI+的一般方法

下面分别就单文档和基于对话框应用程序为例,说明使用GDI+的一般过程和方法。

【例1-1】 在单文档应用程序中使用GDI+

创建一个单文档应用程序,使用GDI+在其中绘制一个100×60的矩形,并对其进行着色。程序的结果如图1-2所示。

图1-2 EX_GDIPlus运行结果

[1] 在应用程序中添加GDI+的包含文件gdiplus.h以及附加的类库gdiplus.lib。

通常gdiplus.h包含文件添加在应用程序的stdafx.h文件中,而gdiplus.lib可用两种方法进行添加。一是直接在stdafx.h文件中添加语句#pragma comment(lib,“gdiplus.lib”);二是选择【项目】|【属性】菜单命令,在弹出的对话框中选中左侧的【链接器】|【输入】选项,在右侧的【附加依赖项】框中键入gdiplus.lib。

[2] 在应用程序项目的应用类中,添加一个成员变量,如下列代码:

        ULONG_PTR m_gdiplusToken;

其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus::GdiplusShutdown来关闭GDI+。

[3] 在应用类中添加ExitInstance的重载,并添加下列代码用来关闭GDI+:

        int CEx_GDIPlusApp::ExitInstance()
        {
          Gdiplus::GdiplusShutdown(m_gdiplusToken);
          return CWinApp::ExitInstance();
        }

[4] 在应用类的InitInstance函数中添加GDI+的初始化代码:

        BOOL CEx_GDIPlusApp::InitInstance()
        {
          CWinApp::InitInstance();
          Gdiplus::GdiplusStartupInput gdiplusStartupInput;
          Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
          ...
        }

[5] 在需要绘图的窗口或视图类中添加GDI+的绘制代码。

        void CEx_GDIPlusView::OnDraw(CDC* pDC)
        {
          CEx_GDIPlusDoc* pDoc = GetDocument();
          ASSERT_VALID(pDoc);
          using namespace Gdiplus;
          Graphics graphics( pDC->m_hDC);
          Pen newPen(Color(255, 0, 0 ), 3);
        //创建填充画刷,前景色为绿色,背景色为蓝色
          HatchBrush newBrush(HatchStyleCross, Color(255, 0, 255, 0), Color(255, 0, 0, 255));
          graphics.DrawRectangle(&newPen, 50, 50, 100, 60);   //(50,50)处绘制长100、
          60的矩形
          graphics.FillRectangle(&newBrush, 50, 50, 100, 60);   //在(50,50)处填充长
          100、60的区域
        }

【例1-2】 在基于对话框应用程序中使用GDI+

创建一个对话框应用程序,使用GDI+在其中绘制一个100×60的矩形,并对其进行着色。运行结果如图1-3所示。

图1-3 EX_GDIPlusDlg运行结果

[1] 创建一个默认的基于对话框的应用程序Ex_GDIPlusDlg。

[2] 打开stdafx.h文件,添加下列代码:

        #include <gdiplus.h>
        #pragma comment(lib, "gdiplus.lib")

[3] 打开Ex_GDIPlusDlg.h文件,添加下列代码:

        class CEx_GDIPlusDlgApp : public CWinApp
        {
          ...
          public:
          virtual BOOL InitInstance();
          ULONG_PTR m_gdiplusToken;
          ...
        };

[4] 在CEx_GDIPlusDlgApp类的属性窗口中,为其添加ExitInstance的重载。

        int CEx_GDIPlusDlgApp::ExitInstance()
        {
          Gdiplus::GdiplusShutdown(m_gdiplusToken);
          return CWinApp::ExitInstance();
        }

[5] 定位到CEx_GDIPlusDlgApp::InitInstance函数处,添加下列GDI+初始化代码:

        BOOL CEx_GDIPlusDlgApp::InitInstance()
        {
          CWinApp::InitInstance();
          Gdiplus::GdiplusStartupInput gdiplusStartupInput;
          Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
          ...
        }

[6] 定位到CEx_GDIPlusDlgDlg::OnPaint函数处,添加下列GDI+代码:

        void CEx_GDIPlusDlgDlg::OnPaint()
        {
        if (IsIconic())
        {
        }
        else
        {
          CPaintDC dc(this);                   // 用于绘制的设备上下文
          using namespace Gdiplus;
          Graphics graphics(dc.m_hDC );
          Pen newPen(Color( 255, 0, 0 ), 3 );
          HatchBrush newBrush( HatchStyleCross,
          Color(255, 0, 255, 0),
          Color(255, 0, 0, 255));
          graphics.DrawRectangle(&newPen, 50, 50, 100, 60);
          graphics.FillRectangle(&newBrush, 50, 50, 100, 60);
          CDialog::OnPaint();
        }
        }

从上述例子可以看出,只要能获得一个窗口的设备环境指针,就可构造一个Graphics对象,从而可以在其窗口中进行绘图,不必再像以往那样使用Invalidate/UpdateWindow来防止Windows对对话框窗口进行重绘。

1.5.2 使用DIB处理数字图像

Windows 3.1以上版本提供了对设备无关位图DIB的支持。DIB位图可以在不同的机器或系统中显示位图所固有的图像。与设备相关位图DDB相比,DIB是一种外部的位图格式,经常存储为以BMP为后缀的位图文件(有时也以DIB为后缀)。DIB位图还支持图像数据的压缩。

Win32 SDK支持一些重要的DIB操作函数,但是这些函数还没有封装到MFC中。这些函数如表1-8所示。

表1-8 Win32 SDK中的DIB操作函数

下面对这些DIB位图操作函数进行说明。

1. SetDIBitsToDevice

        int SetDIBitsToDevice
        (
        HDC hdc,                                  // 显示设备上下文句柄
        int xDest,                               // 目标区域左上角x坐标
        int yDest,                               // 目标区域做上角y坐标
        DWORD dwWidth,                          // 源矩形宽度
        DWORD dwHeight,                         // 源矩形高度
        int xSrc,                                 // 源矩形左上角x坐标
        int ySrc,                                 // 源矩形左上角y坐标
        UINT uStartScan,                        // 第一扫描线位置
        UINT cScanLines,                        // 扫描线数
        CONSTlt@span b=1> VOIDlt@span b=1> *lpvBits,lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> // DIB数据指针
        CONST BITMAPINFO *lpbmi,              // BITMAPINFO结构指针
        UINT fuColorUse                         // 使用颜色的方式
        );

其中,fuColorUse指定BITMAPINFO结构中的bmiColors包含的是RGB值还是调色板中的索引值。它的取值为

▓ DIB_PAL_COLORS:调色板中包含的是当前逻辑调色板的索引值。

▓ DIB_RGB_COLORS:调色板中包含的是真正的RGB数值。

该函数可以直接在显示器或打印机上显示DIB。在显示时不进行缩放处理,即位图的每一个像素对应于一个显示器的像素点或一个打印机的打印点。正是这个特点限制了该函数的使用,使它不能像函数BitBlt( )一样,因为后者使用的是逻辑坐标。函数如果调用成功,返回绘制的行数,否则返回0。

2. ScretchDIBits

        int ScretchDIBits(
        HDC hdc,                                  // 显示设备上下文句柄
        int xDest,                               // 目标区域左上角x坐标
        int yDest,                               // 目标区域做上角y坐标
        int nDestWidth,                         // 目标矩形宽度
        int nDestHeight,                        // 目标矩形高度
        int xSrc,                                 // 源矩形左上角x坐标
        int ySrc,                                 // 源矩形左上角y坐标
        int nSrcWidth,                          // 源矩形宽度
        int nSrcHeight,                         // 源矩形高度
        CONST VOID *lpBits,                    // DIB像素数据指针
        CONST BITMAPINFO *lpBitsInfo,        // BITMAPINFO结构指针
        UINT iUsage,                             // 使用颜色方式
        DWORD dwRop                              // 光栅操作代码
        );

其中,颜色使用方式和SetDIBitsToDevice中的说明相同。该函数类似于StretchBlt,利用它可以缩放显示DIB于显示器或打印机行。函数如果调用成功,返回绘制的行数,否则返回0。

3. GetDIBits

        int GetDIBits(
        HDC hdc,                                  // 设备上下文句柄
        HBITMAP hbmp,                           // 位图句柄
        UINT uStartScan,                        // 设置到目标位图的第一扫描线
        UINT cScanLines,                        // 要复制的扫描线数
        LPVOID lpvBits,                         // 位图位数组地址
        LPBITMAPINFO lpbi,                     // 位图BITMAPINFO结构
        UINT uUsage                              // RGB或调色板索引
        );

该函数利用申请到的内存,由DDB位图来构造DIB。通过该函数,可以对DIB的格式进行控制,可以指定每个像素颜色的位数,而且可以指定是否可以压缩。如果采用了压缩格式,则必须调用两次,一次用于计算所需要的内存,另外一次用于产生DIB数据。GetDIBits用法比较复杂,在调用之前,首先要设置好BITMAPINFOHEADER结构(包含在BITMAPINFO结构中)的几个数据域,同时,应分配足够的调色板空间。如果设置lpBits为NULL,则调用GetDIBits时不会返回任何位,只是填充biSizeImage域(计算位图图像位的存储量)和调色板。函数如果调用成功并且lpvBits非空,返回绘制的行数;如果调用失败,则返回0。

4. SetDIBits

        int SetDIBits(
        HDC hdc,                                  // 设备上下文句柄
        HBITMAP hbmp,                            // 位图句柄
        UINT uStartScan,                        // 设置到目标位图的第一扫描线
        UINT cScanLines,                        // 要复制的扫描线数
        LPVOID lpvBits,                         // 位图位数组地址
        LPBITMAPINFO lpbi,                      // 位图BITMAPINFO结构
        UINT fuUsage                             // RGB或调色板索引
        );

该函数将DIB转换为DDB。其用法相对简单,其余可参考GetDIBits中的用法。

5. CreateDIBitmap

        HBITMAP CreateDIBitmap(
        HDC hdc,                                  // 设备上下文句柄
        CONST BITMAPINFOHEADER *lpbmih,     // 指向BITMAPINFOHEADER结构
        DWORD fdwInit,                           // 指定是否按照特定的格式初始化位图
        CONST VOID *lpbInit,                   // 位图位数组地址
        CONST BITMAPINFO *lpbmi,              // 指向初始化位图的BITMAPINFO结构指针
        UINT fuUsage                             // RGB或调色板索引
        );

该函数利用DIB来创建DDB位图。它等价于如下的语句组合:

        hBitmap=CreateCompatibleBitmap(hDC,(WORD)lpInfo->biWidth,(WORD)lpInfo->biHeight);
        SetDIBits(hDC, hBitmap, 0, (WORD)lpInfo0->biHeight,lpBits,lpInfo,wUsage);

6. CreateDIBSection

        HBITMAP CreateDIBSection(
        HDC hdc,                                  // 设备上下文句柄
        CONST BITMAPINFO *pbmi,               // BITMAPINFO结构指针
        UINT iUsage,                             // 使用的颜色类型
        VOID **ppvBits,                         // 存放位图图像位值的地址
        HANDLE hSection,                        // 可选用的文件映射对象的句柄
        DWORD dwOffset                           // 文件映射对象中位图位值的偏移量
        );

其中,如果hSection为NULL,系统将为DIB分配内存。该函数能创建一种特殊的DIB,称之为DIB项(DIB Section),然后返回一个代表DIB的位图句柄,但其中包含DDB位图句柄。当用GetObject检索其信息时,如cbBuffer设置为sizeof(DIBSECTION),则检索的是DIBSECTION结构信息(DIB结构信息);如cbBuffer设置为sizeof(BITMAP),则检索的是BITMAP结构信息(DDB结构信息)。它提供了DIB和DDB的最好特性。这样可以直接访问DIB的内存,可以利用位图句柄和内存设备环境,甚至还可以在DIB中调用GDI函数来绘图。

DIBSECTION结构包含了CreateDIBSection函数所创建的DIB位图的信息,其定义如下:

        Typedef struct tagDIBSECTION
        {
          BITMAP                          dsBm;                     // 对应的DDB结构
          BITMAPINFOHEADER              dsBmih;                   // 位图信息头结构
          DWORD                           DSbITFIELDS[3];         // 颜色屏蔽值
          HANDLE                          dshSection;              // 文件映射对象的句柄
          DWORD                           dsOffset;                // 文件映射对象中位置偏移量
        }DIBSECTION,*PDIBSECTION;
        HANDLE LoadImage(
          HINSTANCE hinst,           // 需要加载图像的实例
          LPCTSTR lpszName,          // 需要加载的图像的文件或者资源名称
          UINT uType,                 // 需要加载的图像类型
          int cxDesired,             // 需加载的光标和图标的像素宽度
          int cyDesired,             // 需加载的光标和图标的像素高度
          UNIT fuLoad                 // 操作代码
        );

其中,uType取值为IMAGE_BITMAP表示要加载的是位图,IMAGE_CURSOR表示光标,IMAGE_INCON表示图标。fuLoad为操作代码的组合,其常用取值如表1-9所示。

表1-9 Win32 SDK中加载图像操作代码

该函数为Windows SDK提供的函数,直接从磁盘文件读入一个位图,并返回一个DIB句柄。如果调用成功,返回读取位图的句柄,否则返回NULL。

7. DrawDibDraw

        BOOL DrawDibDraw(
          HDRAWDIB hdd,                   // DrawDib DC句柄可通过DrawDibOpen()返回
          HDC hdc,                          // 设备上下文句柄
          int xDst,                        // 目标区域的左上角x坐标
          int yDst,                        // 目标区域的左上角y坐标
          int dxDst,                       // 指定DIB的宽度
          int dyDst,                       // 指定DIB的高度
          LPBITMAPINFOHEADER lpbi,      // BITMAPINFOHEADER结构的指针
          LPVOID lpBits,                  // DIB数据指针
          int xSrc,                        // 源位图要绘制区域的左上角x坐标(像素)
          int ySrc,                        // 源位图要绘制区域的左上角y坐标(像素)
          int dxSrc,                       // 要复制源图像矩形区域的宽度(像素)
          int dySrc,                       // 要复制源图像矩形区域的高度(像素)
          UINT wFlags                      // 绘制方式
        );

Windows提供了窗口视频(Video for Windows,VFW)组件,VFW中的DrawDibDraw函数是一个可以替代StrechDIBits的函数。其主要优点是可以使用抖动颜色,并且提供显示DIB的速度,缺点是必须将VFW代码连接到进程中。

8. GetDIBColorTable

        UINT GetDIBColorTable(
          HDC hdc,                                   // 已选入DIB位图区位图的设备上下文句柄
          UINT uStartIndex,                        // 要检索的第一个调色板的索引
          UINT cEntries,                           // 要检索的第一个调色板的数目
          RGBQUAD *pColors                         // 存放检索的调色板的地址
        );

该函数检索DIB位图的调色板的指定部分,DIB位图区位图(DIB section bitmap)应先选入设备上下文中。

9. SetDIBColorTable

        UINT SetDIBColorTable(
          HDC hdc,                                   // 已选入DIB位图区位图的设备上下文句柄
          UINT uStartIndex,                        // 要设置的第一个调色板的索引
          UINT cEntries,                           // 要设置的第一个调色板的数目
          CONST RGBQUAD *pColors                  // 调色板的地址
        );

该函数设置DIB位图的调色板的指定部分,DIB位图区位图应先选入设备上下文中。

1.5.3 使用自定义类CDib处理数字图像

前面已经提过,在MFC中是不提供任何对于DIB的支持的,而单纯使用1.5.2节介绍的DIB函数只能进行面向过程编程,实现起来比较繁琐。为了方便使用,采用面向对象技术,这里设计了一个设备无关类CDib,里面封装了DIB位图处理所需要的基本成员变量和成员函数。在本章中,有关图像处理的部分就是用这个类实现的,后续章节也有引用。下面来看它的实现代码。

(1)CDib类的头文件Dib.h代码如下:

        //=======================================================
        // 文件: Dib.h
        // 内容: 设备无关位图类-头文件
        // 功能:
        //          位图的加载与保存
        //          位图信息的获取
        //          位图数据的获取
        //          位图的显示
        //          位图的转换
        //          位图相关判断
        //=======================================================
        #pragma once
        #include "afx.h"
        class CDib : public CObject
        {
        public:
          CDib(void);                                 // 构造函数,初始化数据成员
          ~CDib(void);                                // 析构函数,释放内存空间
          BOOL LoadFromFile(LPCTSTR lpszPath);  // 从文件加载位图
          BOOL SaveToFile(LPCTSTR lpszPath);    // 将位图保存到文件
          LPCTSTR GetFileName();                   // 获取位图文件名
          LONG GetWidth()                            // 获取位图宽度
          LONG GetHeight();                         // 获取位图高度
          CSize GetDimension();                    // 获取位图的宽度和高度
          DWORD GetSize();                           // 获取位图大小
          WORD GetBitCount();                       // 获取单个像素所占位数
          UINT GetLineByte();                       // 获取每行像素所占字节数
          DWORD GetNumOfColor();                   // 获取位图颜色数
          LPRGBQUAD GetRgbQuad();                  // 获取位图颜色表
          LPBYTE GetData();                         // 获取位图数据
          BOOL Draw(CDC *pDC, CPoint origin, CSize size); // 显示位图
          BOOL RgbToGrade();                        // 24位彩色位图转8位灰度位图
          BOOL GradeToRgb();                        // 8位灰度位图转24位彩色位图
          BOOL HasRgbQuad();                        // 判断是否含有颜色表
          BOOL IsGrade();                            // 判断是否是灰度图
          BOOL IsValid();                            // 判断位图是否有效
        protected:
          DWORD CalcRgbQuadLength();              // 计算位图颜色表长度
          BOOL MakePalette();                       // 根据颜色表生成调色板
          void Empty(BOOL bFlag = TRUE);         // 清理空间
        private:
          char m_fileName[_MAX_PATH];             // 位图文件名
          LPBITMAPFILEHEADER m_lpBmpFileHeader;  // 位图文件头指针,需动态分配和释放
          LPBYTE m_lpDib;                            // 位图指针,包含除位图文件头的所有内容,
                                                      需动态分配和释放
          LPBITMAPINFO m_lpBmpInfo;               // 位图信息指针
          LPBITMAPINFOHEADER m_lpBmpInfoHeader; // 位图信息头指针
          LPRGBQUAD m_lpRgbQuad;                   // 位图颜色表指针
          LPBYTE m_lpData;                           // 位图数据指针
          HPALETTE m_hPalette;                      // 调色板句柄
          BOOL m_bHasRgbQuad;                       // 是否有颜色表
          BOOL m_bValid;                             // 位图是否有效
        };

(2)CDib类的源文件Dib.cpp代码如下:

        #include "StdAfx.h"
        #include "Dib.h"
        //=======================================================
        // 函数功能: 构造函数,初始化数据成员
        //=======================================================
        CDib::CDib(void)
        {
          // 数据成员初始化
          strcpy(m_fileName, "");
          m_lpBmpFileHeader = NULL;
          m_lpDib = NULL;
          m_lpBmpInfo = NULL;
          m_lpBmpInfoHeader = NULL;
          m_lpRgbQuad = NULL;
          m_lpData = NULL;
          m_hPalette = NULL;
          m_bHasRgbQuad = FALSE;
          m_bValid = FALSE;
        }
        //=======================================================
        // 函数功能: 析构函数,释放内存空间
        //=======================================================
        CDib::~CDib(void)
        {
          Empty();   // 清理空间
        }
        //=======================================================
        // 函数功能: 从文件加载位图
        // 输入参数: LPCTSTR lpszPath表示待加载位图文件路径
        // 返回值:    BOOL-TRUE表示成功,FALSE表示失败
        //=======================================================
        BOOL CDib::LoadFromFile(LPCTSTR lpszPath)
        {
          strcpy(m_fileName, lpszPath);   // 记录位图文件名
          CFile dibFile;
          // 以读模式打开位图文件
          if(!dibFile.Open(m_fileName, CFile::modeRead | CFile::shareDenyWrite))
          {
              return FALSE;
          }
          Empty(FALSE);    // 清理空间
          // 为位图文件头分配空间,并初始化为0
          m_lpBmpFileHeader = (LPBITMAPFILEHEADER)new BYTE[sizeof(BITMAPFILEHEADER)];
          memset(m_lpBmpFileHeader, 0, sizeof(BITMAPFILEHEADER));
          // 读取位图文件头
          int nCount = dibFile.Read((void *)m_lpBmpFileHeader, sizeof(BITMAPFILEHEADER));
          if(nCount != sizeof(BITMAPFILEHEADER))    return FALSE;
          if(m_lpBmpFileHeader->bfType == 0x4d42) // 判断此文件是否位图,0x4d42代表BMP
          {
            //若是位图文件计算除位图文件头的空间大小分配空间并初始化为0
            DWORD dwDibSize = dibFile.GetLength() - sizeof(BITMAPFILEHEADER);
            m_lpDib = new BYTE[dwDibSize];
            memset(m_lpDib, 0, dwDibSize);
            dibFile.Read(m_lpDib, dwDibSize);   // 读取除位图文件头的所有数据
            dibFile.Close();   // 关闭位图文件
            m_lpBmpInfo = (LPBITMAPINFO)m_lpDib;   // 设置位图信息指针
            m_lpBmpInfoHeader = (LPBITMAPINFOHEADER)m_lpDib; // 设置位图信息头指针
            m_lpRgbQuad = (LPRGBQUAD)(m_lpDib + m_lpBmpInfoHeader->biSize);
            // 颜色表指针
            if(m_lpBmpInfoHeader->biClrUsed == 0)   // 当没有设置位图使用的颜色数时设置它
            {
              m_lpBmpInfoHeader->biClrUsed = GetNumOfColor();
            }
            DWORD dwRgbQuadLength = CalcRgbQuadLength();   // 计算颜色表长度
            m_lpData = m_lpDib + m_lpBmpInfoHeader->biSize + dwRgbQuadLength; // 数据指针
                if(m_lpRgbQuad == (LPRGBQUAD)m_lpData)    // 判断是否有颜色表
                {
                    m_lpRgbQuad = NULL;     // 将位图颜色表指针置空
                    m_bHasRgbQuad = FALSE; // 无颜色表
                }
                else
                {
                    m_bHasRgbQuad = TRUE;   // 有颜色表
                    MakePalette();           // 根据颜色表生成调色板
            }
            m_lpBmpInfoHeader->biSizeImage = GetSize();//设置位图大小很多文件都未设置此项
            m_bValid = TRUE;   // 位图有效
            return TRUE;
          }
          else
          {
            m_bValid = FALSE;   // 不是位图文件
            return FALSE;
          }
        }
        //=======================================================
        // 函数功能: 将位图保存到文件
        // 输入参数: LPCTSTR lpszPath表示位图文件保存路径
        // 返回值:    BOOL-TRUE表示成功,FALSE表示失败
        //=======================================================
        BOOL CDib::SaveToFile(LPCTSTR lpszPath)
        {
            CFile dibFile;   // 以写模式打开文件
            if(!dibFile.Open(lpszPath, CFile::modeCreate | CFile::modeReadWrite
            |CFile::shareExclusive))   return FALSE;
          strcpy(m_fileName, lpszPath);    // 记录位图文件名
          dibFile.Write(m_lpBmpFileHeader, sizeof(BITMAPFILEHEADER));
          // 将文件头结构写进文件
          dibFile.Write(m_lpBmpInfoHeader, sizeof(BITMAPINFOHEADER));
          // 将信息头结构写进文件
          DWORD dwRgbQuadlength = CalcRgbQuadLength();   // 计算颜色表长度
          if(dwRgbQuadlength != 0) // 如果有颜色表的话,将颜色表写进文件
          {
              dibFile.Write(m_lpRgbQuad, dwRgbQuadlength);
          }
          DWORD dwDataSize = GetLineByte() * GetHeight();
          dibFile.Write(m_lpData, dwDataSize);   // 将位图数据写进文件
          dibFile.Close(); // 关闭文件
          return TRUE;
        }
        //=======================================================
        // 函数功能: 获取位图文件名
        // 输入参数: 无
        // 返回值:    LPCTSTR表示位图文件名
        //=======================================================
        LPCTSTR CDib::GetFileName()
        {
            return m_fileName;
        }
        //=======================================================
        // 函数功能: 获取位图宽度
        // 输入参数: 无
        // 返回值:    LONG表示位图宽度
        //=======================================================
        LONG CDib::GetWidth()
        {
            return m_lpBmpInfoHeader->biWidth;
        }
        //=======================================================
        // 函数功能: 获取位图高度
        // 输入参数: 无
        // 返回值:    LONG表示位图高度
        //=======================================================
        LONG CDib::GetHeight()
        {
            return m_lpBmpInfoHeader->biHeight;
        }
        //=======================================================
        // 函数功能: 获取位图的宽度和高度
        // 输入参数: 无
        // 返回值:    CSize表示位图的宽度和高度
        //=======================================================
        CSize CDib::GetDimension()
        {
          return CSize(GetWidth(), GetHeight());
        }
        //=======================================================
        // 函数功能: 获取位图大小
        // 输入参数: 无
        // 返回值:    DWORD表示位图大小
        //=======================================================
        DWORD CDib::GetSize()
        {
          if(m_lpBmpInfoHeader->biSizeImage != 0)
          {
            return m_lpBmpInfoHeader->biSizeImage;
          }
          else
          {
            return GetWidth() * GetHeight();
          }
        }
        //=======================================================
        // 函数功能: 获取单个像素所占位数
        // 输入参数: 无
        // 返回值:    WORD表示单个像素所占位数
        //=======================================================
        WORD CDib::GetBitCount()
        {
          return m_lpBmpInfoHeader->biBitCount;
        }
        //=======================================================
        // 函数功能: 获取每行像素所占字节数
        // 输入参数: 无
        // 返回值:    UINT表示每行像素所占字节数
        //=======================================================
        UINT CDib::GetLineByte()
        {
          return (GetWidth() * GetBitCount() / 8 + 3) / 4 * 4;;
        }
        //=======================================================
        // 函数功能: 获取位图颜色数
        // 输入参数: 无
        // 返回值:    DWORD表示位图颜色数
        //=======================================================
        DWORD CDib::GetNumOfColor()
        {
          UINT dwNumOfColor;
          if ((m_lpBmpInfoHeader->biClrUsed == 0) && (m_lpBmpInfoHeader->biBitCount < 9))
              {
              switch (m_lpBmpInfoHeader->biBitCount)
              {
                  case 1: dwNumOfColor = 2; break;
                  case 4: dwNumOfColor = 16; break;
                  case 8: dwNumOfColor = 256;
              }
          }
          else
          {
            dwNumOfColor = m_lpBmpInfoHeader->biClrUsed;
          }
          return dwNumOfColor;
        }
        //=======================================================
        // 函数功能: 计算位图颜色表长度
        // 输入参数: 无
        // 返回值:    DWORD表示位图颜色表长度
        //=======================================================
        DWORD CDib::CalcRgbQuadLength()
        {
          DWORD dwNumOfColor = GetNumOfColor();
          if(dwNumOfColor > 256)
          {
            dwNumOfColor = 0;
          }
          return   dwNumOfColor * sizeof(RGBQUAD);
        }
        //=======================================================
        // 函数功能: 获取位图颜色表
        // 输入参数: 无
        // 返回值:    LPRGBQUAD表示位图颜色表指针
        //=======================================================
        LPRGBQUAD CDib::GetRgbQuad()
        {
            return m_lpRgbQuad;
        }
        //=======================================================
        // 函数功能: 获取位图数据
        // 输入参数: 无
        // 返回值:    LPBYTE表示位图数据指针
        //=======================================================
        LPBYTE CDib::GetData()
        {
          return m_lpData;
        }
        //=======================================================
        // 函数功能: 根据颜色表生成调色板
        // 输入参数: 无
        // 返回值:lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> BOOL-TRUE表示成功,FALSE表示失败
        //=======================================================
        BOOL CDib::MakePalette()
        {
          DWORD dwRgbQuadLength = CalcRgbQuadLength();    // 计算颜色表长度
          if(dwRgbQuadLengthlt@span b=1> ==lt@span b=1> 0)lt@span b=1> returnlt@span b=1> FALSE;lt@span b=1> lt@span b=1> lt@span b=1> // 如果颜色表长度为0,则不生成逻辑调色板
          if(m_hPalette != NULL)   //删除旧的调色板对象
          {
            DeleteObject(m_hPalette);
            m_hPalette = NULL;
          }
          // 申请缓冲区,初始化为0
          DWORD dwNumOfColor = GetNumOfColor();
          DWORD dwSize = 2 * sizeof(WORD) + dwNumOfColor * sizeof(PALETTEENTRY);
          LPLOGPALETTE lpLogPalette = (LPLOGPALETTE) new BYTE[dwSize];
          memset(lpLogPalette, 0, dwSize);
          // 生成逻辑调色板
          lpLogPalette->palVersion = 0x300;
          lpLogPalette->palNumEntries = dwNumOfColor;
          LPRGBQUAD lpRgbQuad = (LPRGBQUAD) m_lpRgbQuad;
          for(int i = 0; i < dwNumOfColor; i++)
          {
            lpLogPalette->palPalEntry[i].peRed = lpRgbQuad->rgbRed;
            lpLogPalette->palPalEntry[i].peGreen = lpRgbQuad->rgbGreen;
            lpLogPalette->palPalEntry[i].peBlue = lpRgbQuad->rgbBlue;
            lpLogPalette->palPalEntry[i].peFlags = 0;
            lpRgbQuad++;
          }
          m_hPalette = CreatePalette(lpLogPalette);   // 创建逻辑调色板
          deletelt@span b=1> []lt@span b=1> lpLogPalette;lt@span b=1> lt@span b=1> lt@span b=1> // 释放缓冲区
          return TRUE;
        }
        //=======================================================
        //lt@span b=1> 函数功能: 显示位图
        // 输入参数:
        //         CDC *pDC表示设备环境指针
        //         CPoint origin表示显示矩形区域的左上角
        //         CSize size表示显示矩形区域的尺寸
        // 返回值:
        //         BOOL-TRUE表示成功,FALSE表示失败
        //=======================================================
        BOOL CDib::Draw(CDC *pDC, CPoint origin, CSize size)
        {
          if(!IsValid())   return FALSE;   // 位图无效,无法绘制,返回错误
          HPALETTE hOldPalette = NULL;   // 旧的调色板句柄
          if(m_lpDib == NULL)   return FALSE; // 如果位图指针为空,则返回FALSE
          if(m_hPalette != NULL)    // 如果位图有调色板,则选进设备环境中
          {
          hOldPalette = SelectPalette(pDC->GetSafeHdc(), m_hPalette, TRUE);
          }
          pDC->SetStretchBltMode(COLORONCOLOR);   // 设置位图伸缩模式
          // 将位图在pDC所指向的设备上进行显示
          StretchDIBits(pDC->GetSafeHdc(), origin.x, origin.y, size.cx, size.cy,0, 0,
            GetWidth(), GetHeight(), m_lpData, m_lpBmpInfo, DIB_RGB_COLORS, SRCCOPY);
          if(hOldPalette != NULL)
          {
            SelectPalette(pDC->GetSafeHdc(), hOldPalette, TRUE); // 恢复旧的调色板
          }
          return TRUE;
        }
        //=======================================================
        // 函数功能: 24位彩色位图转8位灰度位图
        //lt@span b=1> 输入参数:
        // 返回值:lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> BOOL-TRUE表示成功,FALSE表示失败
        //=======================================================
        BOOL CDib::RgbToGrade()
        {
          if(!IsValid())   return FALSE;   // 位图无效失败返回
          if(GetBitCount() != 24)   return FALSE; // 不是24位位图,失败返回
          if(m_lpBmpInfoHeader->biCompression != BI_RGB)   return FALSE;
          //压缩位图失败返回
          if(!IsGrade())   // 如果不是灰度位图,才需要转换
          {
            // 获取源位图信息
            LONG lHeight = GetHeight();
            LONG lWidth = GetWidth();
            UINT uLineByte = GetLineByte();
            // 计算灰度位图数据所需空间
            UINT uGradeBmpLineByte = (lWidth + 3) / 4 * 4;
            DWORD dwGradeBmpDataSize = uGradeBmpLineByte * lHeight;
            // 计算灰度位图所需空间
            DWORD dwGradeBmpSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 +
            dwGradeBmpDataSize;
            // 设置灰度位图文件头
            LPBITMAPFILEHEADER lpGradeBmpFileHeader = (LPBITMAPFILEHEADER)new
            BYTE[sizeof(BITMAPFILEHEADER)];
            memset(lpGradeBmpFileHeader, 0, sizeof(BITMAPFILEHEADER));
            lpGradeBmpFileHeader->bfType = 0x4d42;
            lpGradeBmpFileHeader->bfSize = sizeof(BITMAPFILEHEADER) + dwGradeBmpSize;
            lpGradeBmpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) +
            sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;
            lpGradeBmpFileHeader->bfReserved1 = 0;
            lpGradeBmpFileHeader->bfReserved2 = 0;
            LPBYTE lpGradeBmp = (LPBYTE)new BYTE[dwGradeBmpSize];  // 为灰度位图分配空间
            memset(lpGradeBmp, 0, dwGradeBmpSize);   // 初始化为0
            // 设置灰度位图信息头
            LPBITMAPINFOHEADER lpGradeBmpInfoHeader = (LPBITMAPINFOHEADER)(lpGradeBmp);
            lpGradeBmpInfoHeader->biBitCount = 8;
            lpGradeBmpInfoHeader->biClrImportant = 0;
            lpGradeBmpInfoHeader->biClrUsed = 256;
            lpGradeBmpInfoHeader->biCompression = BI_RGB;
            lpGradeBmpInfoHeader->biHeight = lHeight;
            lpGradeBmpInfoHeader->biPlanes = 1;
            lpGradeBmpInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
            lpGradeBmpInfoHeader->biSizeImage = dwGradeBmpDataSize;
            lpGradeBmpInfoHeader->biWidth = lWidth;
            lpGradeBmpInfoHeader->biXPelsPerMeter = m_lpBmpInfoHeader->biXPelsPerMeter;
            lpGradeBmpInfoHeader->biYPelsPerMeter = m_lpBmpInfoHeader->biYPelsPerMeter;
            // 设置灰度位图颜色表
            LPRGBQUAD lpGradeBmpRgbQuad = (LPRGBQUAD)(lpGradeBmp +
            sizeof(BITMAPINFOHEADER));
            // 初始化8位灰度图的调色板信息
            LPRGBQUAD lpRgbQuad;
            for(int k = 0; k < 256; k++)
            {
                lpRgbQuad = (LPRGBQUAD)(lpGradeBmpRgbQuad + k);
                lpRgbQuad->rgbBlue = k;
                lpRgbQuad->rgbGreen = k;
                lpRgbQuad->rgbRed = k;
                lpRgbQuad->rgbReserved = 0;
            }
            BYTE r, g, b;
            LPBYTE lpGradeBmpData = (LPBYTE)(lpGradeBmp + sizeof(BITMAPINFOHEADER)
                            + sizeof(RGBQUAD) * 256);    // 灰度位图数据处理
            for(int i = 0; i < lHeight; i++)   // 进行颜色转换
            {
                for(int j = 0; j < lWidth; j++)
                {
                  b = m_lpData[i * uLineByte + 3 * j];
                  g = m_lpData[i * uLineByte + 3 * j + 1];
                  r = m_lpData[i * uLineByte + 3 * j + 2];
                  lpGradeBmpData[i * uGradeBmpLineByte + j] = (BYTE)(0.299 * r +
                  0.587 * g + 0.114 * b);
                }
            }
            Empty(FALSE);   // 释放原有位图空间
            // 重新设定源位图指针指向
            m_lpBmpFileHeader = lpGradeBmpFileHeader;
            m_lpDib = lpGradeBmp;
            m_lpBmpInfo = (LPBITMAPINFO)(lpGradeBmp);
            m_lpBmpInfoHeader = lpGradeBmpInfoHeader;
            m_lpRgbQuad = lpGradeBmpRgbQuad;
            m_lpData = lpGradeBmpData;
            m_bHasRgbQuad = TRUE;      // 设置颜色表标志
            m_bValid = TRUE;   // 设置位图有效标志
            MakePalette();    // 生成调色板
          }
          return TRUE;
        }
        //=======================================================
        // 函数功能:lt@span b=1> 8位灰度位图转24位彩色位图
        // 输入参数: 无
        // 返回值:    BOOL-TRUE表示成功,FALSE表示失败
        //=======================================================
        BOOL CDib::GradeToRgb()
        {
          if(!IsValid())   return FALSE; // 位图无效失败退出
          if(GetBitCount() != 8)   return FALSE;   // 不是8位位图失败退出
          if(m_lpBmpInfoHeader->biCompression != BI_RGB) return FALSE; //压缩位图失败返回
          if(IsGrade()) // 是灰度图时,才需转换
          {
            // 获取源位图信息
            LONG lHeight = GetHeight();
            LONG lWidth = GetWidth();
            UINT uLineByte = GetLineByte();
            // 计算彩色位图数据所需空间
            UINT uColorBmpLineByte = (lWidth * 24 / 8 + 3) / 4 * 4;
            DWORD dwColorBmpDataSize = uColorBmpLineByte * lHeight;
            // 计算彩色位图所需空间
            DWORD dwColorBmpSize = sizeof(BITMAPINFOHEADER) + dwColorBmpDataSize;
            // 设置彩色位图文件头
            LPBITMAPFILEHEADER lpColorBmpFileHeader = (LPBITMAPFILEHEADER)new
            BYTE[sizeof(BITMAPFILEHEADER)];
            memset(lpColorBmpFileHeader, 0, sizeof(BITMAPFILEHEADER));
            lpColorBmpFileHeader->bfType = 0x4d42;
            lpColorBmpFileHeader->bfSize = sizeof(BITMAPFILEHEADER) + dwColorBmpSize;
            lpColorBmpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) +
            sizeof(BITMAPINFOHEADER);
            lpColorBmpFileHeader->bfReserved1 = 0;
            lpColorBmpFileHeader->bfReserved2 = 0;
            // 为彩色位图分配空间,并初始化为0
            LPBYTE lpColorBmp = (LPBYTE)new BYTE[dwColorBmpSize];
            memset(lpColorBmp, 0, dwColorBmpSize);
            // 设置彩色位图信息头
            LPBITMAPINFOHEADER lpColorBmpInfoHeader = (LPBITMAPINFOHEADER)(lpColorBmp);
            lpColorBmpInfoHeader->biBitCount = 24;
            lpColorBmpInfoHeader->biClrImportant = 0;
            lpColorBmpInfoHeader->biClrUsed = 0;
            lpColorBmpInfoHeader->biCompression = BI_RGB;
            lpColorBmpInfoHeader->biHeight = lHeight;
            lpColorBmpInfoHeader->biPlanes = 1;
            lpColorBmpInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
            lpColorBmpInfoHeader->biSizeImage = dwColorBmpDataSize;
            lpColorBmpInfoHeader->biWidth = lWidth;
            lpColorBmpInfoHeader->biXPelsPerMeter = m_lpBmpInfoHeader->biXPelsPerMeter;
              lpColorBmpInfoHeader->biYPelsPerMeter = m_lpBmpInfoHeader->biYPelsPerMeter;
            // 彩色位图数据处理
            LPBYTE lpColorBmpData = (LPBYTE)(lpColorBmp + sizeof(BITMAPINFOHEADER));
            for(int i = 0; i < lHeight; i++)   // 进行颜色转换
            {
                for(int j = 0; j < lWidth; j++)
                {
                      BYTE btValue = m_lpData[i * uLineByte + j];
                      lpColorBmpData[i * uColorBmpLineByte + 3 * j] = btValue;
                      lpColorBmpData[i * uColorBmpLineByte + 3 * j + 1] = btValue;
                      lpColorBmpData[i * uColorBmpLineByte + 3 * j + 2] = btValue;
                  }
              }
              Empty(FALSE);   // 释放原有位图空间
              // 重新设定源位图指针指向
              m_lpBmpFileHeader = lpColorBmpFileHeader;
              m_lpDib = lpColorBmp;
              m_lpBmpInfo = (LPBITMAPINFO)(lpColorBmp);
              m_lpBmpInfoHeader = lpColorBmpInfoHeader;
              m_lpRgbQuad = NULL;
              m_lpData = lpColorBmpData;
              m_bHasRgbQuadlt@span b=1> =lt@span b=1> FALSE;lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> lt@span b=1> // 设置颜色表标志
              m_bValid = TRUE;           // 设置位图有效标志
          }
          return TRUE;
        }
        //=======================================================
        // 函数功能: 判断是否含有颜色表
        // 输入参数: 无
        // 返回值:    TRUE表示含有颜色表,FALSE表示不含颜色表
        //=======================================================
        BOOL CDib::HasRgbQuad()
        {
          return m_bHasRgbQuad;
        }
        //=======================================================
        // 函数功能: 判断是否是灰度图
        // 输入参数: 无
        // 返回值:    TRUE表示是灰度图,FALSE表示是彩色图
        //=======================================================
        BOOL CDib::IsGrade()
        {
          return (GetBitCount() < 9 && GetBitCount() > 0);
        }
        //=======================================================
        // 函数功能: 判断位图是否有效
        // 输入参数: 无
        // 返回值:    TRUE表示位图有效,FALSE表示位图无效
        //=======================================================
        BOOL CDib::IsValid()
        {
          return m_bValid;
        }
        //=======================================================
        // 函数功能: 清理空间
        // 输入参数: BOOL bFlag-TRUE表示全部清空,FALSE表示部分清空
        // 返回值:    
        //=======================================================
        void CDib::Empty(BOOL bFlag)
        {
          if(bFlag)   strcpy(m_fileName, ""); // 文件名清空
          if(m_lpBmpFileHeader != NULL)
          {
              delete [] m_lpBmpFileHeader;
              m_lpBmpFileHeader = NULL;
          }        // 释放位图文件头指针空间
          if(m_lpDib != NULL)
          {
              delete [] m_lpDib;
              m_lpDib = NULL;
              m_lpBmpInfo = NULL;
              m_lpBmpInfoHeader = NULL;
              m_lpRgbQuad = NULL;
              m_lpData = NULL;
          }           // 释放位图指针空间
          if(m_hPalette != NULL)
          {
              DeleteObject(m_hPalette);
              m_hPalette = NULL;
          }         // 释放调色板
          m_bHasRgbQuad = FALSE;     // 设置不含颜色表
          m_bValid = FALSE;           // 设置位图无效
        }

1.6 综合实例—图像浏览器

综合前文介绍的内容,本节将给出一个实例。该实例是一个类似ACDSee的图像浏览工具,其主要实现4种功能。

1) 类似ACDSee的图像浏览功能。用户只需要打开一个图片,就能利用菜单栏、工具栏或快捷键等快捷地浏览该图像所在目录的所有图像。使用方法与ACDSee类似。

2) 图像的缩放显示功能。图像可以支持4种显示模式。

▓ 原始大小:以原始大小显示图像。如果图像大小比视图小,则显示在正中;如果图像大小比视图大,则显示部分图像。用户可以利用鼠标拖动图像以显示其他部分。

▓ 适合宽度:保持图像长宽比例缩放图像,使图像宽度等于视图宽度。

▓ 适合高度:保持图像长宽比例缩放图像,使图像高度等于视图高度。

▓ 适合屏幕:自动判断图像的显示方式,使图像能在当前视图上完整显示。

3) 文件目录窗口。通过目录树可以直接打开图像文件。

4) 位图浏览窗口。以缩略图的形式显示图像。

【例1-3】 图像浏览器

[1] 创建新项目。启动Visual Studio 2005,选择【文件】|【新建】|【项目】命令。在出现的【新建项目】对话框中选择“MFC应用程序”模板,并在【名称】文本框中输入“GraphShower”,然后单击【确定】按钮,如图1-4所示。

图1-4 【新建项目】对话框

在弹出的【MFC应用程序向导】对话框中需要改变的设置是“应用程序类型”。本实例需要选择“单文档”类型,其余使用默认配置生成项目,如图1-5所示。

图1-5 【MFC应用程序向导】对话框

[2] 在GraphShower.h中创建CGraphShowerApp类的定义,其代码如下:

        // GraphShower.h : GraphShower 应用程序的主头文件
        #pragma once
        #ifndef __AFXWIN_H__
        #error "在包含此文件之前包含stdafx.h以生成 PCH文件"
        #endif
        #include "resource.h"         // 主符号
        class CGraphShowerApp : public CWinApp// 有关此类的实现,请参阅GraphShower.cpp
        {
          public:
          CGraphShowerApp();
          public:
          virtual BOOL InitInstance();
          afx_msg void OnAppAbout();
          DECLARE_MESSAGE_MAP();
          private:
          GdiplusStartupInput m_GdiplusStartupInput;
          ULONG_PTR m_GdiplusToken;
          Public:
          virtual int ExitInstance();
        };
        extern CGraphShowerApp theApp;

[3] 文件GraphShower.cpp中包含CGraphShowerApp类中各函数的实现,实现代码如下:

        #include "stdafx.h"
        #include "GraphShower.h"
        #include "MainFrm.h"
        #include "GraphShowerDoc.h"
        #include "GraphShowerView.h"
        #ifdef _DEBUG
        #define new DEBUG_NEW
        #endif
        BEGIN_MESSAGE_MAP(CGraphShowerApp, CWinApp)
        ON_COMMAND(ID_APP_ABOUT, &CGraphShowerApp::OnAppAbout)
        // 基于文件的标准文档命令
        ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
        ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
        // 标准打印设置命令
        ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinApp::OnFilePrintSetup)
        END_MESSAGE_MAP()
        CGraphShowerApp::CGraphShowerApp() // CGraphShowerApp 构造
        {
          // TODO: 在此处添加构造代码,将所有重要的初始化放置在 InitInstance 
        }
        CGraphShowerApp theApp;// 唯一的一个 CGraphShowerApp 对象
        BOOL CGraphShowerApp::InitInstance() // CGraphShowerApp 初始化
        {
          // 如果一个运行在 Windows XP 上的应用程序清单指定要使用 ComCtl32.dll 版本 6 或更高
          // 版本来启用可视化方式,则需要 InitCommonControlsEx()。否则,将无法创建窗口。
          INITCOMMONCONTROLSEX InitCtrls;
          InitCtrls.dwSize = sizeof(InitCtrls);
          // 将它设置为包括所有要在应用程序中使用的公共控件类。
          InitCtrls.dwICC = ICC_WIN95_CLASSES;
          InitCommonControlsEx(&InitCtrls);
          CWinApp::InitInstance();
          if (!AfxOleInit())   // 初始化 OLE 
          {
              AfxMessageBox(IDP_OLE_INIT_FAILED);
              return FALSE;
          }
          AfxEnableControlContainer();
          // 标准初始化,如果未使用这些功能并希望减小最终可执行文件的大小,
          // 则应移除下列不需要的特定初始化例程,更改用于存储设置的注册表项
          // TODO: 应适当修改该字符串,例如修改为公司或组织名
          SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
          LoadStdProfileSettings(4);   // 加载标准 INI 文件选项(包括 MRU)
          // 注册应用程序的文档模板。文档模板将用于文档、框架窗口和视图之间的连接
          CSingleDocTemplate* pDocTemplate;
          pDocTemplate = new CSingleDocTemplate(
          IDR_MAINFRAME,
          RUNTIME_CLASS(CGraphShowerDoc),
          RUNTIME_CLASS(CMainFrame),         //  SDI 框架窗口
          RUNTIME_CLASS(CGraphShowerView));
          if (!pDocTemplate)
            return FALSE;
            AddDocTemplate(pDocTemplate);
          //------------------------------------------------------------------------------
          // GDI+图像库初始化
          GdiplusStartup(&m_GdiplusToken, &m_GdiplusStartupInput, NULL);
          //---------------------------------------------------------------------
          // 分析标准外壳命令、DDE、打开文件操作的命令行
          CCommandLineInfo cmdInfo;
          ParseCommandLine(cmdInfo);
          // 调用在命令行中指定的命令。如果
          // /RegServer、/Register、/Unregserver  /Unregister 启动应用程序,则返回 FALSE。
          if (!ProcessShellCommand(cmdInfo))
              return FALSE;
          // 唯一的一个窗口已初始化,因此显示它并对其进行更新
          m_pMainWnd->ShowWindow(SW_SHOW);
          m_pMainWnd->UpdateWindow();
          // 仅当具有后缀时才调用DragAcceptFiles
          //  SDI 应用程序中,这应在ProcessShellCommand之后发生
          return TRUE;
        }
        class CAboutDlg : public CDialog// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
        {
          public:
          CAboutDlg();
          enum { IDD = IDD_ABOUTBOX };   // 对话框数据
          protected:
          virtual void DoDataExchange(CDataExchange* pDX);     // DDX/DDV支持
          protected:
          DECLARE_MESSAGE_MAP()
        };
        CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
        {
        }
        void CAboutDlg::DoDataExchange(CDataExchange* pDX)
        {
          CDialog::DoDataExchange(pDX);
        }
        BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
        END_MESSAGE_MAP()
        void CGraphShowerApp::OnAppAbout() // 用于运行对话框的应用程序命令
        {
          CAboutDlg aboutDlg;
          aboutDlg.DoModal();
        }
        int CGraphShowerApp::ExitInstance() // CGraphShowerApp 消息处理程序
        {
          GdiplusShutdown(m_GdiplusToken);   // 关闭GDI+图像库
          return CWinApp::ExitInstance();
        }

[4] 运行该程序,便可以启动GraphShower图像浏览器,其主界面如图1-6所示。包括标题栏、主菜单、工具栏、控制窗口、显示窗口及状态栏。

图1-6 GraphShower浏览器界面

希望浏览一幅图像时,有3种打开方式可供选择。第一种方式为选择主菜单中的【文件】|【打开】选项,在弹出的对话框中选择相应的图像文件;第二种方式为单击工具栏中的图标,寻找希望浏览的图像文件;第三种方式为在控制窗口中直接对需要打开的图像文件做出选择,这也是最为直接的一种方式,如图1-7所示。当在控制窗口中选择了一幅图像时,该图像将在显示窗口中显示,但是其显示的是图像的原始大小。这个浏览器提供了4种不同的图像显示模式,分别为原始大小、适合宽度、适合高度以及适合屏幕,可以通过主菜单中的【显示模式】选择自己需要的图像尺寸。这4种显示模式的效果图如图1-8所示。

图1-7 选择并打开某个图像文件

图1-8 GraphShower不同显示模式的效果图

另外,本例还在控制窗口中的【位图浏览】选项中提供了浏览位图的功能,可以在窗口左侧对图像进行缩略显示,如图1-9所示。

图1-9 GraphShower位图浏览效果图

1.7 实践拓展

与本章有关的常见问题如下,希望帮助读者加深对本章知识的理解。

1. 数字图像处理包含哪些主要内容

数字图像处理主要包含以下内容:点运算、几何变换、图像增强、图像复原、图像重建、图像形态学处理、图像分割、图像编码及图像匹配等。

2. 数字图像处理有哪些主要应用领域

目前数字图像处理的应用范围越来越广泛,已经渗透到工业、航空航天、医疗保健、军事、交通、国家安全、刑侦等各个领域,在国民经济中发挥着越来越大的作用。可以说各个学科都会或多或少地运用图像处理的技术。

3. RGB与HSI颜色模式有何异同

RGB颜色模式以自然界中常见的三原色,红、绿、蓝的不同组合来表示各种颜色,而HSI色模式主要是从人的视觉系统出发,用色调、色饱和度及亮度来描述色彩。HSI颜色模式与RGB颜色模式相比更符合人的视觉特性,且可以大大简化图像分析和处理的工作量。二者可以互相转化。

4. 何时需要对Windows调色板进行处理

只有用户需要以每像素8位以下模式运行程序时,才需要处理调色板。

5. 位图文件由哪些结构组成,各个结构都起到什么作用

位图文件由文件头、位图信息头、颜色表和位图数据4部分组成。其中,文件头结构标识文件的类型、文件大小和位图起始位置等信息;位图信息头用于说明位图的尺寸等信息;颜色表说明位图所使用的颜色信息;位图数据则记录了位图的每一个像素值,记录顺序为在扫描行内是从左到右,扫描行之间是从下到上。

6. GDI+在GDI基础上在哪些方面增加了新特性

与GDI相比,GDI+在以下几个方面增加了新特性:渐变画刷、样条曲线、持久的路径对象、矩阵和矩阵变换以及Alpha调色。

7. DIB与DDB之间有何不同

DDB依赖于具体的设备,它的颜色模式必须与输出设备相一致。因为它对设备的依赖性,所以DDB只能存于内存之中。而DIB的颜色模式与设备无关,它可以运行于各种颜色模式下,因而通常保存于磁盘中。与DDB相比较,DIB具有更强的灵活性,可以用来永久保存图像,并且可以使运行在不同输出设备下的应用程序交换图像。