第十一章 图像和位图

分类:DotNet    发布时间:2015/6/21 23:08:44

绘图(Draw)程序制作矢量图形,绘画(paint)程序制作光栅图形。照片(photo)程序是绘图程序的变化格式。

矢量图形进行大小和方向的转换不会损失分辨率。光栅图形则通常依赖于设备。

将矢量图形转换为光栅图像是容易的。反之则很困难。

11.1 位图支持概述

System.Drawing名称空间有两个类,即Image和Bitmap,Bitmap类以及Metafile类是从Image类派生的。

Image类是抽象类,您不能使用构造函数将其实例化。不过Image有2个静态方法,他们可以从文件或流中加载位图或图元文件。还可以加载gif,jpeg,png,tiff。Image还有一个静态方法,允许你从Win32位图句柄创建一个Bitmap对象。

一旦有了Image对象,就可以用他来做一些事情。可以使用Graphics类的DrawImage方法。也可以使用Graphics的FromImage静态方法返回一个应用于图像的Graphics对象。Image还有内置的格式转换功能。

如果只需要加载并显示位图图像,那么Image类可能就是你需要的一切。Bitmap类通过提供几个可以创建特定大小和颜色格式的新的位图构造函数来扩展Image。Bitmap类还允许你直接读取和写入某个单个像素,并将位图做为一块内存来访问。

Bitmap类还包括一个构造函数,它允许你加载一个嵌入到.exe文件、做为“资源的”的位图。还可以使用这项技术加载图标并自定义光标。

有时,你会犹豫,对于特定的任务而言,你应该使用Image类还是Bitmap类。如果需要的每一样东西都可以使用Image类来完成,则应该使用Image类。额外的好处是代码也会适用于图元文件。

11.2 位图文件格式

位图是位的均型阵列,与图像输出设备的像素相对应。位图具有以像素为单位的特定高度和宽度。位图还具有特定的颜色深度,是每个像素的位数(通常将其缩写为bpp)。位图中每个像素都有相同数量的位,他决定了图像中有多少不同的颜色

颜色数=2的每像素位数次方

每像素位数的取值范围一般是1-32.

在Windows这样的图形环境中,颜色通常用ARGB表示,攻4个字节,32位。

每像素一位的位图存储双值或单色图像。

在Windows早期,每像素4位的图像很流行,现在也可以找到他们。例如:图标通常是16色图像。16色一般是RGB三原色的常规版本和深色版的组合。

一种非常常见的位图格式是每像素8位,通常图像是灰度格式,8位对应于从黑到白的256级灰度。

每像素16位的位图一般对R G B级别各使用5位,还有一位未用。

全彩色位图每像素有24位:一般来说,如果32位处理器访问32位内存边界中的32位值,那么它是最有效的。

一个每像素32位的位图实际上是一个每像素24位图像加上出于性能考虑的每像素没有使用的1个字节。这个额外的字节也可以用来提供颜色透明度。被称为alpha信道。

图像压缩技术

Image类支持的位图文件格式是由在System.Drawing.Imaging名称空间中定义的ImageFormat类的静态属性确定的,如表11-1所示。

ImageFormat的静态属性

ImageFormat Bmp 读取 
ImageFormat MemoryBmp
ImageFormat Icon
ImageFormat Gif
ImageFormat Jpeg
ImageFormat Png
ImageFormat Tiff
ImageFormat Exif
ImageFormat Wmf
ImageFormat Emf

11.3 加载和绘制

Image的静态方法

Image Image.Fromfile(string strFilename)
Image Image.FromFile(string strFilename,bool bUseImageColorManagement)
Image Image.FromStream(Stream stream)
Image Image.FromStream(Stream stream,bool bUseImageColorManagement)
Bitmap Image.Frombitmap(IntPtr hBitmap)
Bitmap Image.FromBitmap(IntPtr hBitMap,IntPtr hPalette)

除非你正在与Win32代码连接,否则可能不会使用最后两个方法,不过前两个方法是很强大的。

Image image=Image.FromFile("CuteCat.jpg");

好的特性是,他使用文件内容而不是扩展名来确定文件格式。

Stream是System.IO名称空间中的抽象类。实现了Read Write Seek这样的方法。许多情况下,一个流就是一个打开的文件。不过,一个Stream对象还可以表示存储在内存中或在网络上传输的连续信息。

布尔参数是用于解决颜色管理问题的。

虽 然记载着前4个静态方法返回Image类型的对象,但是如果对返回值调用GetType,那么您会发现返回值要么是 System.Drawing.Bitmap类型,要么是System.Drawing.Imaging.Metafile类型。这取决于已经加载的文件 或流的类型。


11.4 图像信息

Image的属性(节选)
Size Size 读取 以像素为单位
int  Width 读取
int  Height 读取

大多数现代的位图格式都包括一些用每英寸的点数等单位表示的图像分辨率信息。

分辨率是从哪里来的?通常来自于最初创建位图的程序。

Image的属性(节选)
float HorizontalResolution 读取 每英寸的点数
float VerticalResolution  读取
SizeF PhysicalDumension 读取 百毫米

如 果图像没有分辨率信息,HorizontalResolution和VerticalResolution属性返回视频显示器的分辨率。你可能希望忽略 PhysicalDimension(因为他在Windows Forms的早期版本中无法正常工作),并自己计算图像的测量大小。

下面的语句是以每英寸为单位计算图像的大小:

float cxInches=image.Width / image.HorizontalResolution;
float cyInches=image.Height / image.VerticalResolution;

显示图像

g.DrawImage(image,x,y)

该方法根据他的测量大小缩放图像!

Image的属性
PixelFormat PixelFormat 读取

PixelFormat属性指明了图像的像素格式,指明了颜色深度以及像素如何与颜色相对应。

PixelFormat属性返回在Drawing.Imaging 名称空间中定义的枚举

PixelFormat枚举(节选)

Undefined or DontCare 0x00000000
Format16bppRgb555     0x00021005
Format16bppRgb565     0x00021006
Format24bppRgb        0x00021808
Format32bppRgb        0x00022009
...

format 后面的数字表示每像素的位数:1 4 8 16 32 48 or 64.包含字母Rgb的格式对每一个像素存储 红R,蓝G,绿B的值,Argb还包含一个透明度的alpha的值。PArgb格式包含预先乘以alpha值的R G B的值。初看上去,枚举成员的数字值可能都是随机出现的,但是仔细观察,会发现一些确定的模式。看看 右边“值”的末两位16进数字,每个值都是唯一的。从0x00到0x0E. 倒数第三位和倒数第四位,他们表示每像素的位数:0x01,0x04,0x08,0x10,0x18,0x20,0x30 or 0x40。其余的位都是标志。PixelFormat枚举值包括Max,他表示格式的数量(包含Undefined)是15+解释标志的含义的值。

PixelFormat枚举(节选)
Max 0x0000000F 格式数
Indexed 0x00010000 像素位是调色板索引
Gdi 0X00020000 Windows GDI格式
Alpha 0x00040000 包含透明位或字节
PAlpha 0x00080000 包含预先相乘的透明字节
Extended 0x00100000 对每原色或灰度使用1个以上的字符
Canonical 0x00200000 标准格式

Image类还有几个静态方法,让你不用深入研究位就可以讹获得其中的大多数信息。

int GetPixelFormatSize(PixelFormat pf) 返回每像素的位数
bool IsAlphaPixelFormat(PixelFormat pf)
bool IsCanonicalPixelFormat(PixelFormat pf)
bool IsExtendedPixelFormat(PixelFormat pf)

如果图像是索引的---可以通过对PixelFormat属性和PixelForm.Indexed执行按位的AND来确定这点,那么图像就有一个调色板。

Image的属性

ColorPalette Palette 读取/设置  获取图像的调色板

Palette相当独特,她是可写的。不过ColorPalette类本身是封闭的。在整个.NET中,没有获得ColorPalette对象的办法,除非做为Image类的属性。

ColorPalette本身只有2个只读属性。Entries属性返回图像调色板中的颜色数组

ColorPalette的属性
Color[] Entries 读取
int Flags  读取

Image类的另一个属性表示文件格式

Image的属性(节选)
ImageFormat RawFormat 读取

不过使用Image类的RawFormat属性要困难一些。必须与ImageFormat类的唯一实例属性Guid(即唯一的非静态属性)一起使用他,他返回ImageFormat对象的全局唯一标识符(GUID)

ImageFormat的实例属性
Guid Guid 读取

您的程序只能测定特定的Image对象是否是一种特定的ImageFormat类型。

例如:如果有一个名为image的Image对象是从JPEG文件中加载的,那么表达式

image.RawFormat.Guid==ImageFormat.Png.Guid 
将返回false。而

image.RawFormat.Guid==ImageFormat.Jpeg.Guid
将返回true

表达式
image.RawFormat.ToString()返回字符串。
[ImageFormat:b96b3cae-0728-11d3-9d7b-0000181ef32e]

而表达式
ImageFormat.Jpeg.ToString()返回字符串
Jpeg

11.5 绘制图像

DrawImage有30多个版本!具有很大的灵活性。另一个绘图方法是DrawImageUnscaled,您也可以使用它,但是他没有提供DrawImage之外的功能。

一 副图像也有大小,甚至有2个大小:像素大小和测量大小。在你试图以设备无关的方式处理图像时,以其测量大小(是最简单版本的DrawImage的工作)显 示图像是有用的。不过,如果打算以像素为单位绘制,则需要运用一点数学知识来预估这样一副显示图像的像素大小。有时你会希望以像素大小显示图像。 DrawImage不会自动使用图像的像素大小,但是让这个方法以像素大小绘制图像是很容易的。

在过去,位图一直是不能进行旋转变换的。在Windows Forms和GDI+中,位图的显示也受世界变换影响。

所有的DrawImage版本第一个参数都是ImageFormat类型的对象。最起码,这个方法也总是包括一个坐标点,可以是整数,浮点数,一个point 一个RectangleF表示左上角的位置

西面4个DrawImage方法根据图像的测量尺寸设置其大小。图像的大小不受任何页面变换的影响,但是受世界变换的影响。获得的图像在视频显示器和打印机上有相同的大小。

void DrawImage (Image image,int x,int y)
void DrawImage (Image image,float x,float y)
void DrawImage (Image image,Point pt)
void DrawImage (Image image,PointF ptf)

要把实际测量大小转换为像素大小,可以通过下面的一段小程序

g.PageUnit=GraphicsUnit.Pixel;
g.PageScale=1;

RectangeF rectf=g.VisibleClipBounds;
//如果只是跟显示器打交道,可以直接用ClientSize的宽高,但是如果要打印的话,必须通过VisbibleClipBounds

float cxImage=g.DpiX * image.Widht/image.HorizontalResolution;
//图像的宽度除以水平分辨率获得宽度的英寸长度,再乘以DpiX得到像素数。

float cyImage=g.DpiY * image.Height/image.VerticalResolution;

g.DrawImage(image,(rectf.Width-cxImage)/2,(rectf.Height-cyImage)/2);


11.6 匹配矩形

下面4个方法指定了一个矩形

void DrawImage(Image image,int x,int y,int cx,int cy)
void DrawImage(Image image,float x,float y,float cx,float cy)
void DrawImage(Image image,Rectangle rect)
void DrawImage(Image image,RectangleF rectf)

这些方法将图像缩放到矩形的大小,即可以拉伸,也可以压缩,以便与矩形匹配。这些方法的一种常见用法是以图像的像素大小而不是测量大小来显示图像。如果页面的单位是像素,那么只需要调用

g.DrawImage(image,x,y,image.Width,image.Height)

因为视频分辨率很可能大于72dpi,所以图像很可能比DrawImage绘制的要小,因为如果图像的宽度是72,图像的分辨率是72dpi,那么图像的实际宽度应该是1英寸,如果视频显示器的分辨率是100,那么图像的宽度就是72/100英寸,小于1英寸。

矩形目标版本的DrawImage方法除了拉伸图像之外,还可以有更多的窍门。如果将宽度指定为负值,那么图像就会沿着垂直轴翻转;高度为负值,图像沿水平轴翻转。


11.7 旋转和剪切

下面2个方法可以对图像做更多的变形。转换,缩放,剪切或旋转为平行四边形。

void DrawImage(Image image,Point[] apt)
void DrawImage(Image image,PointF[] aptf)

数组参数必须正好包含3个点。

apt[0]=图像左上角的最终位置
apt[1]=图像右上角的最终位置
apt[2]=图像左下角的最终位置

因为最后的图像是平行四边形,所以可以推算出图像右下角的最终位置。

这不是旋转或剪切位图图像的唯一方法,还可以使用常用的世界变换。

11.8 显示部分图像

需要在位图上指定一个要显示的子选定区域,该区域为矩形。您应该相对于图像左上角以像素为单位指定这个子选定区域。对于一个名为“image”的Image对象,矩形

new Rectangle(0,0,image.Width,image.Height)
表示整幅图像,

new Rectangle(image.Width-10,image.Height-10,10,10)
表示图像右下角边长为10像素的正方形。

void DrawImage(Image image,int xDst,int yDst,Rectangle rectSrc,GraphicsUnit gu)

void DrawImage(Image image,float xDst,float yDst,RectangleF rectSrc,GraphicsUnit gu)

这 里的概念比这些方法的古怪定义看上去要简单。首先,总是应该以像素为单位指定源矩形。(因此,以RectangleF结构而不是Rectangle结构定 义的DrawImage版本是没有意义的) 其次,GraphicsUnit参数必须是GraphicsUnit.Pixel.

例如
Rectangle rect=new Rectangle(95,5,50,55);
g.DrawImage(image,0,0,rect,GraphicsUnit.Pixel);
//将在客户区左上角绘制部分图像, 假如图像的分辨率为72dpi,那么图像的大小为50/72英寸宽,55/72英寸高。

还有4个方法允许你同时指定一个源矩形和一个目标举行。源矩形以像素为单位,目标矩形以世界坐标表示。GraphicsUnit参数必须是GraphicsUnit.Pixel.

void DrawImage(Image image,Rectangle rectDst,int xSrc,int ySrc,int cxSrc,int cySrc,GraphicsUnit gu,*)

void DrawImage(Image image,Rectangle rectDst,Rectangle rectSrc,GraphicsUnit gu)

void DrawImage(Image image,Rectangle rectDst,float x,float y,float cx,float cy,GraphicsUnit gu,*)

void DrawImage(Image image,RectangleF rectfDst,RectangleF rectSrc,GraphicsUnit gu)

加*号的方法表示还可以有三个可选的参数:一个ImageAttribute对象,一个用于中止图像绘制的回调函数,以及传递给回调函数的数据。因此,这两个带星号的方法都有三个额外的版本。

同样地,目标矩形和源矩形大小不一致的时候,图像将被拉伸 变形,可以设置目标矩形=源矩形,来按像素大小绘制,或按照比例缩放图像。

对于DrawImage方法,还可以使用3个点的数组显示部分图像。*号表示每一个方法实际上都是4个方法。

void DrawImage(Image image,Point[] aptDst,Rectangle rectSrc,GraphicsUnit gu,*)

void DrawImage(Image image,PointF[] aptfDst,RectangleF rectfSrc,GraphicsUnit gu,*)

同样,gu必须是GraphicsUnit.Pixel.

到现在位置,我们已经完成了DrawImage的30个方法。

11.9 在图像上绘制

如 果你考虑一下,当Windows在视频显示器上绘制时,他实际上是在视频显示器卡的内存中存储的一个大的位图上绘制。同样,许多打印机都是根据像位图一样 组织的内存内容产生输出的。因此,Windows程序可以使用对视频显示器和打印机使用的相同图形输出函数在任何位图上绘制是有道理的。

要想在任何图像上绘制,需要获得指向这个图像的Graphics对象。你可以从Graphics类的一个静态方法来获得Graphics对象。

Graphics Graphics.FromImage(Image image)

例如:

Graphics grfxImage=Graphics.FromImage(image)

使用完这个Graphics对象后,调用Dispose方法撤销他。

Grahics.FromImage 方法不能在所有图像格式下使用。如果图像的PixelFormat属性是以下PixelFormat成 员:Format1bppIndexed,Format4bppIndexed,Format8bppIndexed,Format16bppGrayScale 或Format16Argb1555,那么这个方法就不能正常工作,并会产生一个异常。这种限制可能对你是有意义的。

你也不能从早期的Windows图元文件(WMF)或Windows增强图元文件(EMF)加载的图像获得Graphics对象。

如 果查看基于图像的Graphics对象的DpiX和DpiY属性,则会发现他们等于Image对象HorizontalResolution和 VerticalResolution属性。默认页面变换是PageUnit的值为GraphicsUnit.Display,并且PageScale为 1,对于图像,他与GraphicsUnit.Pixels是相同的。默认情况下,这个Graphics对象的VisibleClipBounds属性等 于以像素为单位的图像宽度和高度。

你可以为Graphics对象设置不通的页面变换。如果更改了PageUnit和PageScale属性,那么VisibleClipBounds属性表示以页面单位计算的图像大小。例如,如果设置:

grfxImage.PageUnit=GraphicsUnit.Inch;
grfxImage.PageScale=1;

那么,VisibleClipBounds表示以英寸为单位的图像大小,还可以将图像的像素大小除以HorizontalResolution和VerticalResolution属性来进行计算。

在少数情况下,我打算在位图上绘制文本。这样问题就出现了?我应该使用哪一种字体?窗体的默认Font属性让人满意吗?

窗 体的默认Font是一种8磅字体。如果用这种字体在图像上绘制文本,再以图像的测量大小(而不是像素大小)显示他,那么文本就像直接在客户区上绘制的一样 大。不过,如果图像的分辨率小于视频显示器的分辨率,那么文本看上去肯定会不同。图像上的文本会比直接在窗口上显示的文本粗糙一些。

考虑下面的例子。示例位图是72dpi,也就是每磅等于1像素。这意味着9磅字体大约为8像素高。如果调用

Font.GetHeight(72)
或者,
Font.GetHeight(grfxImage)

那么您将获得8.83像素的行距,符合大约8像素的字符高度。

现 在,8像素不是绘制令人满意的字体字符的完整距离。但是,如果在72dpi的图像上使用窗体的默认Font属性,这将是DrawString受到限制的字 体高度。如果视频显示器的分辨率高于72dpi,并且您以像素为单位显示图像,那么图像上的文本将显示的非常小。如果以测量大小为单位显示图像,那么图像 和文本都会更大一些。对于96dpi的显示,图像(以及图像上的文本)会以1又1/3的因子增大。在120dpi的显示上,图像会以1又2/3的因子增 大。所以,那么8像素的文本将被拉伸到默认Font的正常大小。

在图像上的文本,因为图像被拉伸,也会跟着被拉伸。对于变形问题,可以采取 某些措施吗?这个问题来自于使用只有9像素高的字体。如果使用具有更大的像素大小的字体,则可以解决这个问题。这意味着应该使用分辨率大于屏幕分辨率的图 像或者至少将字体放大到12像素左右。后一种方法会使文本看上去更大一些,但至少他是可读的。

如果以像素为单位显示图像,则需要采取不通的方法。要想使图像上的文本与显示在客户区上的正常文本一样大,需要基于屏幕分辨率与图像分辨率之比缩放字体。

Font font=new Font(Font.FontFamily,grfxScreen.DpiY/grfxImage.DpiY * Font.SizeInPoints);

11.10 关于Image类的更多内容

Image类还有其他几个方法,让你以有限的方式保存或操作图像。save的第一个版本让你使用文件扩展名来确定文件格式。

void Save(string strFilename)
void Save(string strFilename,ImageFormat if)
void Save(stream stream,ImageFormat if)

你不能对任何从图元文件或内存位图加载的Image对象使用Save,也不能以图元文件或内存位图格式保存图像。

下面的2个方法可以改变图像的大小,并以某种固定方式相应地旋转和翻转他。

Image GetThumbnailImage(int cx,int cy,image.GetThumbnailImageAbort gtia,intPtr pData)

void RotateFlip(RotateFliptype rft)

上 面GetThumbnailImage方法用于创建图像的缩略图,她是图像的缩小版本,应用程序可以使用他表示图像的内容,从而节省时间和空间。不 过,GetThumbnailImage实际上是一个具有通用目的的调整图像大小的函数。你可以使图像变大,也可以使他变小。最后两个参数用于指定一个回 调函数,但是你可以将其设置为null或0,即使缺少这些参数,该方法也可以正常工作。

GetThumbnailImage方法创建一副新图像,而RotateFlip方法改变现有图像。唯一的参数是RotateFlipType枚举。

RotateFlipType枚举
RotateNoneFlipNone     0     
Rotate80FlipXY

Rotate90FlipNone      1
Rotate270FlipXY  

...

如果需要旋转或翻转图像,但仍然希望保留原来的图像,那么可以首先使用Clone方法制作一份原始的Image对象的副本。

Image imageCopy=(Image)image.Clone();


11.11 Bitmap类

Bitmap类是从Image继承而来的。所有Image属性都适用于Bitmap对象。Bitmap类让你可以深入研究位图的各个位。

Image类没有构造函数,但是Bitmap类有12个构造函数。

Bitmap(string strFilename)
Bitmap(string strFilename,bool bUseImageColorManagement)
Bitmap(Stream stream)
Bitmap(Stream stream,bool bUseImageColorManagement)
Bitmap(Type type,string strResource)

上面4个函数基本重复了在Image中实现的FromFile和FromStream静态方法。最后一个将Bitmap对象做为资源加载,他通常嵌入在应用程序的.exe文件中。

下面的3个函数根据现有的Image对象创建新的Bitmap对象。

Bitmap(image image)
Bitmap(image image,Size size)
Bitmap(image image,int cx,int cy)

虽 然第一个参数被定义为Image,但是他也可以是另一个Bitmap对象。第一个构造函数类似于Image类的Clone方法。第二个第三个构造函数类似 于GetThumbnailImage方法,图像大小发生了变化。在所有情况下,新新位图都继承了现有位图的像素格式。在所有情况下,位图的分辨率都被设 置为视频显示器的分辨率。

最后4个构造函数在Image类中没有对应函数。这些构造函数允许您创建带有空白图像的全新Bitmap对象。

Bitmap(int cx,int cy)
Bitmap(int cx,int cy,PixelFormat pf)
Bitmap(int cx,int cy,Graphics grfx)
Bitmap(int cx,int cy,int cxRowBytes,PixelFormat pf,intPtr pBytes)

上面 前3个 构造函数都将像素初始化为0.值为0的像素对于不同的位图具有不同的意义。对于RGB位图,0意味着黑色。对于ARGB位图,0意味着透明。第4个构造函数还允许您传递一个指向初始化位图图像的字节数组的指针。

上 面 第1个构造函数创建像素格式为PixelFormat.Format32bppArgb的指定大小的Bitmap对象。他是每像素32位,并且带有一个表 示透明度的Alpha信道。水平分辨率和垂直分辨率被设置为视频显示器的分辨率。 第2个构造函数 允许您指定PixelFormat。

第 3个构造函数 让你指定一个Graphics对象。无论是否指定了与视频显示器或打印机相关联的Graphics对象,也无论是否在使用彩色打印机,这个构造函数总是以 PixelFormat.Format32bppPArgb像素格式创建一个Bitmap。注意这种像素格式表示预置多信道。基于Graphics对象创 建位图的真正重要迹象是将Bitmap对象的HorizontalResolution和VerticalResolution属性设置为 Graphics对象的DpiX和DpiY属性。这并不意味着对于打印机是100dpi!!他表示300、600、720或更高。

例如:假 设打印的分辨率是600dpi。你想要根据打印机分辨率创建位图。为什么想要这么做呢?因为如果最终要打印位图,你会希望在这个位图上绘制的所有内容(包 括文本)都达到打印机分辨率所允许的最美观、最完整的程度。但是应该记住,位图大小必须与分辨率兼容。一个2英寸,600dpi的正方形位图要求宽度和高 度都为1200像素,并占用5M以上的内存。一定要保证在屏幕和打印机上显示这种位图时使用的是测量大小!

无论采用什么比例,如果喜欢使用与屏幕和打印机都不匹配的分辨率创建位图,Bitmap类都提供了一个方法,他允许你更改已经加载或创建的位图的分辨率。

void SetResolution(float xDpi,float yDpi)

怎样使内容出现在位图的表面呢?有3种方法:

1),为位图创建一个Graphics对象,并在位图上绘制。
2),可使用Bitmap类的SetPixel和GetPixel方法设置(或读取)位图中单个像素的颜色。
3),可以使用Bitmap类的LockBits和UnlockBits方法获得对实际位图数据的访问。


11.12 使用位图的Hello World

11.13 阴影位图

有 时,实现一个OnPaint方法在处理时间或内存方面的成本是非常高的。例如:客户区可能包含一副复杂的图像,需要很长时间才能载入。对于这种应用程序, 实现一个“阴影位图”通常是一种极好的解决方案。阴影位图是程序在OnPaint方法之外绘制的位图,它包含同一时刻包含在其客户区上的任何内容。因 此,OnPaint方法就精简为对DrawImage的简单调用。

11.14 二进制资源

在应用程序的.exe文件中存储小的二进制文件--特别是位图、图标和自定义的光标--是有好处的。这样,你就再也不会失去他们了。以这种方式存储在可执行文件中的文件被称为“资源”。

Visual C#.NET允许您使用一个交互式图像编辑器来创建二进制资源。从项目菜单中选择Add Item,再选Resource下的BitmFile,CursorFIle或IconFile。

对于位图文件,Properties窗口允许你指定尺寸和颜色格式。对于光标文件,默认格式是32像素正方形和16色。

无论您合适创建想要做为资源使用的位图、光标或图标文件,都要知道下面这条重要的规则。注意看:

在Visual C# .NET中,当你在属于项目一部分的Solution Explorer中选择任何位图、图标或光标文件时,将看到(或者激活)这个文件的Properties窗口。将Build Action属性改为Embedded Resource。

这个属性告诉Visual C# .NET将资源嵌入到项目的.exe文件中。在程序中,你通过使用Bitmap构造函数,Cursor构造函数或Icon构造函数加载这样一个资源。

Icon(Type type,string strResource)

要想加载该图标,可以用这个构造函数
Icon=new Icon(typeof(ProgramWithIcon),"ProgramWithIcon.ProgramWithIcon.ico")

//注:ProgramWithIcon是类名,程序名

下面的Icon构造函数允许你从文件或流中加载图标。如果图标文件或资源包含多个图像,则可以根据现有的图标尝试获得一个指定大小的图标。

Icon(Icon icon,Size size)
Icon(ICon icon,int cx,int cy)

上述这些构造函数试图将可用图标与你所指定的大小进行匹配。他们不会拉伸或压缩图标。你可以使用下面Icon类的属性来确定图标的大小

Size Size 读取
int  Width
int  Height

你还可以使用下面表所示的Graphics方法在客户区上绘制图标。

void DrawIcon(Icon icon,int x,int y)
void DrawIcon(Icon icon,Rectangle rect)
void DrawIconUnstretched(Icon icon,Rectangle rect)

我们来看看

Icon=new Icon(typeof(ProgramWithIcon),"ProgramWithIcon.ProgramWithIcon.ico")

这条语句,

typeof(ProgramWithIcon)等价于 this.GetType()或GetType()

这种等价意味着可以使用稍微短一点的构造函数:

new Icon(GetType(),"ProgramWithIcon.ProgramWithIcon.ico")

第二个参数或多或少就是文件名。引号中第一部分是名称空间,如果省略就是

new Icon(GetType(),"MyIcon.ico") //换一个文件名

如果正在从命令行运行C#编译器,则可以对每一项资源使用/res。例如:

/res:MyIcon.ico

那么就像下面这样加载图标:

new Icon(GetType(),"MyIcon.ico")

也可以在文件名和句点之后为图标赋予一个扩展名:

/res:MyIcon.ico,ProgramWithIcon.MyIcon.ico

然后使用构造函数:加载图标

new Icon(GetType(),"ProgramWithIcon.MyIcon.ico")

如 果只是使用Visual C# .NET为项目指定的默认资源名称空间,则可能会遇到下面的问题:假设你创建一个名为“ProgramWithIcon”的新项目,其中 ProgramWithIconPlus类是从ProgramWithIcon类继承的。在ProgramWithIconPlus项目中,创建一个名为 “ProgramWithIconPlus.cs”的新文件,还要添加一个到现有的ProgramWithIcon.cs文件的链接。但是,你决定不为新 程序创建一个新图标。相反,在ProgramWithIconPlus项目中创建一个到ProgramWithIcon.ico文件的链接。 ProgramWithIcon类中的构造函数继续加载该图标。

当程序试图加载图标时,将出现什么现象呢?他将中止并产生一个异常。那么,到底发生了什么事情?ProgramWithIcon构造函数中的语句像下面这样加载图标:

Icon=new Icon(typeof(ProgramWithIcon),"ProgramWithIcon.ProgramWithIcon.ico");

但 是,ProgramWithIconPlus的默认资源名称空间是ProgramWithIconPlus,而不是ProgramWithIcon。有简 单的解决办法吗?可以将ProgramWithIconPlus项目中的DefaultNamespace字段更改为ProgramWithIcon,也 可以使所有的Default Namespace字段都为空白,并在构造函数中使用无修饰的文件名。

加载光标与加载图标完全相同。Cursor的构造函数

Cursor(Type type,string strResource)

Bitmap(Type type,str strResource)

11.15 动画

Windows Forms和GDI+都缺少一些通常认为对动画很重要的特性。在前面的章节中,介绍了GDI+是如何不支持纯粹的-OR(XOR)绘制的。XOR绘制允许 你绘制一副图像,然后再次绘制他,以擦去刚刚绘制的内容。另一个问题是Windows Forms不允许以任何方式从屏幕中读取像素。在执行动画时,从屏幕读取一块像素做为位图、在其上面画图,然后将其写回屏幕常常是有用的。

但你仍然可以在Windows Forms程序中执行一些初级动画。一种动画是“帧动画”(Frame animation),它可以连续显示相同大小的位图,很像电影。

11.16 图像列表

在下一章中,我们将开始使用控件,特别是按钮、标签和滚动条,接下来还有菜单、列表框、编辑框等等。你会发现,在控件表面常常可以使用位图而不是文本(也可以同时使用位图和文本)。在最极端的情况下,经常出现在应用程序菜单下的工作栏控件通常就是一串小位图。

为 了帮助你处理一组图像,System.Windows.Forms名称空间定义了一个ImageList类。图像列表实际上就是一个灵活的Image对象 数组,这些图像具有相同的大小和颜色格式。你可以将图像放入一个ImageList对象,并像对待数组一样访问他们。放入图像列表的图像在放入时不必具有 相同的大小---但是在提取他们时,他们会缩放成相同的大小。

ImageList的属性

Size ImageSize 读写
ColorDepth ColorDepth 读写
Color TransparentColor 读写
ImageList.ImageCollection Images 读取

默认ImageSize属性值是16像素正方形,在开始向图像列表中添加图像时,他不会自动改变。你可能需要根据要处理的Image对象的大小,以及程序运行时视频显示器的分辨率来自己设置它。

ColorDepth属性是一个ColorDepth的枚举

ColorDepth的枚举
Depth4Bit 4
Depth8Bit 8
Depth16Bit 16
Depth24Bit 24
Depth32Bit 32

ColorDepth 默认属性是Depth8Bit。你想基于正使用的图像手工更改该属性。幸运的是 ColorDepth枚举是以合理的方式定义的,所以,如果有一个想要存储在ImageList中的Image对象(比如称为”image"),则可使用 Image.GetPixelFormatSize静态方法获得像素格式、每像素的位数,并将其强制转换为ColorDepth类型的值:

imglst.ColorDepth=(ColorDepth)Image.GetPixelFormatSize(image.PixelFormat)

第 四个名为"Images”的属性类型是ImageList.ImageCollection。这个长名值意味着他是一个在ImageList类中定义的, 名为“ImageCollection”的类。在应用程序中,你永远也不会引用ImageCollection类:要想使用 ImageCollection类的属性和方法,只需要引用Images属性。Images属性存储了图像列表中的所有图像。

Images 属性的功能也出现在System.Windows.Forms中的其他一些类中。在下一章中,你会遇到名为“Controls”的Control类,他的 类型是Control.ControlCollection.还有“MenuItems”的Menu类的属性,他的类型是 Menu.MenuItemCollection.所有这些属性的工作方式都非常类似。属性的类型都实现了IList,ICollection和 IEnumerable接口,它允许这些像可扩展的数组一样工作。

要想创建ImageList类型的对象,可以调用默认的构造函数:

ImageList imglst=new ImageList();

然后希望设置ImageSize和ColorDepth属性。

ImageList.ImageCollection的方法

void Add(Image image)
void Add(Image image,Color clrTransparent)
void Add(Icon icon)
void Add(Image image)

实际调用情况:

imglst.Images.Add(image);

在添加每一幅图像时,都为它指定一个从0开始的索引。AddStrip方法添加多副图像,其数量取决于传递给方法的图像宽度和ImageSize属性的宽度。

ImageList中图像的数量

int Count 读取

imglst.Images.Count;

最重要的是可以像数组一样引用Images属性。
imglst.Images[2];//返回第三个对象

你还可以使用索引来替换图像列表中的图像:
imglst.Images[3]=image;//不过如果没有4个图像会产生异常。

你还可以确定一副特定的图像是否位于图像列表中,并且获得这个图像的索引

bool Contains(Image image)
int IndexOf(Image image)

例如:

imglst.Images.IndexOf(image)

返回值是从0开始计算的,如果不在表中,返回-1.

删除图像

void RemoveAt(int index)
void Clear()

ImageList的Draw方法

void Draw(Graphics grfx,Point pt,int index)
void Draw(Graphics grfx,int x,int y,int index)
void Draw(Graphics grfx,int x,int y,int cx,int cy,int index)

例如:
imglst.Draw(grfx,x,y,1)

小心传递给Draw方法的坐标:传递给前两个Draw方法的坐标使用设备单位。图像的大小则基于ImageList对象的ImageSize属性,同样也使用设备单位。页面变换或世界变化都不会影响这个两个方法!!


11.17 图片框

另一种与图像有关的控件类是PictureBox。PictureBox类是从Control继承的(因而可以处理键盘和鼠标输入),但是这个控件通常只是显示图像。

PictureBox的属性

Image Image 读写
BorderStyle BorderStyle 读写
PictureBoxSizeMode SizeMode 读写

BorderStyle枚举
None 0
FixedSingle 1
Fixed3D 2

默认值为None.PictureBoxSizeMode 是一个表示图像如何在控件中显示的枚举。

PictureBoxSizeMode枚举
Normal 0
StretchImage 1
AutoSize 2
CenterImage 3

默认值为Normal。

对 于PictureBox,像对其他控件一样,一般使用Location属性设置控件相对于其父控件的位置,使用Size属性设置控件的宽度和长度。如果将 SizeMode指定为PictureBoxSizeMode.Normal或PictureBoxSizeMode.CenterImage,那么图像 就会在图片框内以其像素大小而不是测量大小显示。

对于PictureBoxSizeMode.Normal,图像与控件的左上角对齐。如果 控件比图像的像素大小要大,那么你就会看到围绕着图像的右边和底部的控件的BackColor。如果控件比图像小,那么图像的右边和底部就有一部分被隐 藏。对于PictureBoxSizeMode.Centered,同样,只是隐藏的位置不一样。

如果将PictureBox控件的 ClientSize属性设置为Image对象的Size属性,那么控件将正好适合图像的大小。(控件的ClientSize属性表示边界内部的大小。) 也可以使用PictureBoxSizeMode.AutoSize,以根据Image大小来确定控件大小。

PictureBoxSizeMode.StretchImage模式拉伸图像,以满足控件的大小。不过,正如你所担心的,如果控件的长宽比与图像的长宽比不匹配,那么图像将会出现变形。

那么在各个方向上以相同的比例拉伸图像的PictureBoxSizeMode成员在什么地方呢?唉!!没有这个成员,只能创建一个PictureBoxPlus控件覆盖PictureBox,并添加一个NoDistort属性来强行改正这个缺陷。

最新评论

我要发表评论

名称:
电子邮件:
个人主页:
内容:

 博客分类