微软的新的.NET平台为开发者带来了许多新的 诸如GDI+、Globalization之类的编程机制,同时 还发明了一门全新的类似Java的编程语言-C#。对于这些新知 识,我们应尽快了解、掌握并试图运用到实践项目中去,而通过实例 学习的方法无疑是一个非常有效的途径。本文就通过一个简单的实例 ,向大家展示了在Visual C#中如何运用GDI+和Uns afe代码类等技术以实现简单的数字图像处理。
一. 概述:
本文的实例是一个数字图像处理的应用程序,它 完成的功能包括对图像颜色的翻转、对图像进行灰度处理和对图像进 行增亮处理。该程序对图像进行处理部分的代码包含在一个专门的F ilters类里面,通过调用该类里的静态成员函数,我们就可以 实现相应的图像处理功能了。为实现图像处理,我们要对图像进行逐 个象素处理。我们知道图像是由一个个的象素点组成的,对一幅图像 的每个象素进行了相应的处理,最后整个图像也就处理好了。在这个 过程中,我们只需对每个象素点进行相应的处理,在处理过程中却不 需要考虑周围象素点对其的影响,所以相对来说程序的实现就变得简 单多了。
由于GDI+中的BitmapData类不 提供对图像内部数据的直接访问的方法,我们唯一的办法就是使用指 针来获得图像的内部数据,这时我们就得运用unsafe这个关键 字来指明函数中访问图像内部数据的代码块了。在程序中,我还运用 了打开文件和保存文件等选项,以使我们的辛勤劳动不付之东流。< br>
二.程序的实现:
1.打开Vis ual Studio.net,新建一个Visual C#的项 目,在模板中选择"Windows 应用程序"即可,项目名称可 自定(这里为ImageProcessor)。
2. 为使窗体能显示图像,我们需要重载窗体的OnPaint()事件 函数,在该函数中我们将一个图像绘制在程序的主窗体上,为了使窗 体能显示不同尺寸大小的图像,我们还将窗体的AutoScrol l属性设置为true。这样,根据图像的尺寸,窗体两边就会出现 相应的滚动条。该函数的实现如下:
private void Form1_Paint (object sender, System.Windows .Forms.PaintEventArgs e)
{< br> Graphics g = e.Graphics;
g.DrawImage(m_Bitmap, new R ectangle(this.AutoScrollPositi on.X, this.AutoScrollPosition. Y,
(int)(m_Bitmap.Width), (int)(m_Bitmap.Height)));
}
3.给主窗体添加一个 主菜单,该主菜单完成了一些基本的操作,包括"打开文件"、"保 存文件"、"退出"、"翻转操作"、"灰度操作"、"增亮操作" 等。前面三个操作完成图像文件的打开和保存以及程序的退出功能, 相应的事件处理函数如下:
private void menuItemOpen_Cl ick(object sender, System.Even tArgs e)
{
OpenFileDia log openFileDialog = new OpenF ileDialog();
openFileDialo g.Filter = "
Bitmap文件(*.bm p)|*.bmp|
Jpeg文件(*.jpg)|*.jpg|
所有合适文件(*.bmp/*.jpg)|*.bmp/*.jp g"
;
openFileDialog.Fi lterIndex = 2 ;
openFileDi alog.RestoreDirectory = true ;
if(DialogResult.OK == ope nFileDialog.ShowDialog())
{
m_Bitmap = (Bitmap)Bit map.FromFile(openFileDialog.Fi leName, false);
this.Aut oScroll = true;
this.Aut oScrollMinSize=new Size ((int) (m_Bitmap.Width),(int)
m_Bitmap.Height));
this.Invalidate();
}
}
其中,m _Bitmap为主窗体类的一个数据成员,声明为private System.Drawing.Bitmap m_Bitma p;
(注:因为程序中用到了相关的类,所以在程序文件的开始处应 添加using System.Drawing.Imaging ;
)同时,在该类的构造函数中,我们必须先给它new一个Bit map对象:m_Bitmap = new Bitmap(2, 2);
上述代码中的this.Invalidate();
完成主 窗体的重绘工作,它调用了主窗体的OnPaint()函数,结果 就将打开的图像文件显示在主窗体上。
< br> private void menuItemSa ve_Click(object sender, System .EventArgs e)
{
SaveFi leDialog saveFileDialog = new SaveFileDialog();
saveFile Dialog.Filter = "
Bitmap文件 (*.bmp)|*.bmp|
Jpeg文件(*.jpg)|*.jpg|
所有合适文件(*.bmp/*.jpg)| *.bmp/*.jpg"
;
saveFil eDialog.FilterIndex = 1 ;
saveFileDialog.RestoreDirector y = true ;
if(DialogResult .OK == saveFileDialog.ShowDial og())
{
m_Bitmap.Save (saveFileDialog.FileName);
}
}
其中m _Bitmap.Save(saveFileDialog.Fi leName);
一句完成了图像文件的保存,正是运用了GDI+ 的强大功能,我们只需这么一条简单的语句就完成了以前很大工作量 的任务,所以合理运用.NET中的新机制一定会大大简化我们的工 作的。
private void menuItemExit_Click(objec t sender, System.EventArgs e)< br> {
this.Close();
}
接下来,三个主要 操作的事件处理函数如下:
private void menuItemInvert_ Click(object sender, System.Ev entArgs e)
{
if(Filte rs.Invert(m_Bitmap))
thi s.Invalidate();
}
pri vate void menuItemGray_Click(o bject sender, System.EventArgs e)
{
if(Filters.Gra y(m_Bitmap))
this.Invali date();
}
private voi d menuItemBright_Click(object sender, System.EventArgs e)
{
Parameter dlg = new Parameter();
dlg.nValue = 0;
if (DialogResult.OK = = dlg.ShowDialog())
{
if(Filters.Brightness(m_Bit map, dlg.nValue))
this. Invalidate();
}
}
三个函数中分别调用了相应的图像 处理函数Invert()、Gray()、Brightness ()等三个函数。这三个函数Filters类中的三个类型为pu blic的静态函数(含有static关键字),它们的返回值类 型均是bool型的,根据返回值我们可以决定是否进行主窗体的重 绘工作。
Invert()、Gray()、Brig htness()等三个函数均包含在Filters类里面,In vert()函数的算法如下:
public static bool Invert( Bitmap b)
{
BitmapData bmData = b.LockBits(new Recta ngle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWri te, PixelFormat.Format24bppRgb );
int stride = bmData.Str ide;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(voi d *)Scan0;
int nOffset = stride - b.Width*3;
int n Width = b.Width * 3;
for( int y=0;
y<
b.Height;
++y)
{
for(int x=0;
x <
nWidth;
++x )
{
p [0] = (byte)(255-p[0]);
++p;
}
p += nOffs et;
}
}
b.UnlockB its(bmData);
return true;
< br> }
该函数以及 后面的函数的参数都是Bitmap类型的,它们传值的对象就是程 序中所打开的图像文件了。该函数中的BitmapData类型的 bmData包含了图像文件的内部信息,bmData的Stri de属性指明了一条线的宽度,而它的Scan0属性则是指向图像 内部信息的指针。本函数完成的功能是图像颜色的翻转,实现的方法 即用255减去图像中的每个象素点的值,并将所得值设置为原象素 点处的值,对每个象素点进行如此的操作,只到整幅图像都处理完毕 。函数中的unsafe代码块是整个函数的主体部分,首先我们取 得图像内部数据的指针,然后设置好偏移量,同时设置nWidth 为b.Width*3,因为每个象素点包含了三种颜色成分,对每 个象素点进行处理时便要进行三次处理。接下来运用两个嵌套的fo r循环完成对每个象素点的处理,处理的核心便是一句:p[0] = (byte)(255-p[0]);
。在unsafe代码块 后,便可运用b.UnlockBits(bmData)进行图像 资源的释放。函数执行成功,最后返回true值。注:由于是要编 译不安全代码,所以得将项目属性页中的"允许不安全代码块"属性 设置为true,图示如下:
该函数实现的程 序效果如下:
(处理前)
(处理后)
Gray()函数的算法如下:
public static bool Gray(Bitmap b)
{
BitmapData bmData = b.L ockBits(new Rectangle(0, 0, b. Width, b.Height),
ImageLockMode.ReadWr ite, PixelFormat.Format24bppRg b);
int stride = bmData.St ride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte * p = (byte *)(vo id *)Scan0;
int nOffset = stride - b.Width*3;
byte red, green, blue;
for(in t y=0;
y<
b.Height;
++y)
{
for(int x=0;
x <
b. Width;
++x )
{
blue = p[0];
green = p[1];
red = p[2];
p[0] = p[1] = p[2] = (byte)(.299 * re d + .587 * green + .114 * blue );
p += 3;
}
p += nOffset;
}
}
b .UnlockBits(bmData);
retur n true;
}
本函数完成的功能是对图像进行灰度处理,我们的基本想法可 是将每个象素点的三种颜色成分的值取平均值。然而由于人眼的敏感 性,这样完全取平均值的做法的效果并不好,所以在程序中我取了三 个效果最好的参数:.299,.587,.114。不过在这里要 向读者指明的是,在GDI+中图像存储的格式是BGR而非RGB ,即其顺序为:Blue、Green、Red。所以在for循环 内部一定要设置好red、green、blue等变量的值,切不 可颠倒。函数执行成功后,同样返回true值。
该函 数实现的程序效果如下:
(处理前 )
(处理后)
Bright ness()函数的算法如下:
public static bool Bri ghtness(Bitmap b, int nBrightn ess)
{
if (nBrightnes s <
-255 || nBrightness > ;
255)
return false;
BitmapData bmData = b.LockBi ts(new Rectangle(0, 0, b.Width ,
b.Height), ImageLoc kMode.ReadWrite,
Pix elFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmDa ta.Scan0;
int nVal = 0;
unsafe
{
byte * p = (byte *)(void *)Scan0;
int nOffset = stride - b.W idth*3;
int nWidth = b.W idth * 3;
for(int y=0;
y& lt;
b.Height;
++y)
{
for(int x=0;
x <
nWidth ;
++x )
{
nVa l = (int) (p[0] + nBrightness) ;
if (nVal <
0) nVal = 0;
if (nVal >
255 ) nVal = 255;
p[0] = (b yte)nVal;
++p;
}
p += nOffset;
}
}
b.UnlockBit s(bmData);
return true;
}
本函数完成的 功能是对图像进行增亮处理,它比上面两个函数多了一个增亮参数- nBrightness,该参数由用户输入,范围为-255~2 55。在取得了增亮参数后,函数的unsafe代码部分对每个象 素点的不同颜色成分进行逐个处理,即在原来值的基础上加上一个增 亮参数以获得新的值。同时代码中还有一个防止成分值越界的操作, 因为RGB成分值的范围为0~255,一旦超过了这个范围就要重 新设置。函数最后执行成功后,同样得返回true值。
该函数实现的程序效果如下:
首先 ,我们把图像增亮的参数设置为100(其范围为-255~255 ),然后执行效果如下,读者也可尝试其他的参数值。
(处理前)
(处理 后)
三.小结:
本文通过一个简单的实例 向大家展现了用Visual C#以及GDI+完成数字图像处理 的基本方法,通过实例,我们不难发现合理运用新技术不仅可以大大 简化我们的编程工作,还可以提高编程的效率。不过我们在运用新技 术的同时也得明白掌握基本的编程思想才是最主要的,不同的语言、 不同的机制只是实现的具体方式不同而已,其内在的思想还是相通的 。对于上面的例子,掌握了编写图像处理函数的算法,用其他的方式 实现也应该是可行的。同时,在上面的基础上,读者不妨试着举一反 三,编写出更多的图像处理的函数来,以充实并完善这个简单的实例 。