GDI+中常见的几个问题(4)

5.读图是快了,处理怎么还是慢?

GDI+的Bitmap类提供了两个罪恶的函数GetPixel, SetPixel,用来获取某个像素点的颜色值。这个2个函数如果只调用一次两次也就罢了,万一我想把整张图片加红一点,用下面的代码,我估计你等到黄花菜都凉了,还没有算完呢。 看看下面的代码是怎么写的。 

 1  FileStream fs  =   new  FileStream(image, FileMode.Open, FileAccess.Read);
 2  Image img  =  Image.FromStream(fs,  false false );
 3  Bitmap bmp  =   new  Bitmap(img);
 4  img.Dispose();
 5  fs.Close();
 6 
 7  for  ( int  j  =   0 ; j  <  bmp.Height; j ++ )
 8  {
 9        for  ( int  i  =   0 ; i  <  bmp.Width; i ++ )
10       {
11            Color color  =  bmp.GetPixel(i, j);
12            color  =  Color.FromArgb(color.R  +   20 , color.G, color.B);
13            bmp.SetPixel(i, j, color);
14       }
15  } 

 

代码逻辑很清楚,第1到第5行,写得很好,用了我们在前几节里面的方法,读图速度飞快且不锁文件。当然如果不用覆盖原始文件,不用复制都可以,速度就更快了。接下来我们对图像做一个循环,一行一行更新图像的数据。殊不知GetPixel和SetPixel是GDI里面耗费最大的函数之一,此外bmp.Height和bmp.Width也是慢得够呛,如果处理一张500M像素的照片,您可以去喝杯茶,睡一觉再回来了。

Bitmap有个方法叫LockBits,就是把图像的内存区域根据格式锁定,拿到那块内存的首地址。这样就可以直接改写这段内存了。这个方法的设计是挺好,可惜都是C++作为源泉来的,.NET Framework里面根本就不推荐用指针,VB里面根本也就没有指针。最后设计了一个鸡肋的IntPtr,将就掉了这个问题。C#其实还好,可以用unsafe code,也算是间接用了指针,VB.NET就痛苦了,需要用Marshal.Copy把内容Copy到一个byte数组里面,然后处理完了再Copy回去。所以结论就是,要用GDI+做图像处理,最好别用VB.NET,否则内存翻倍。

让我们来看看快速的写法,注意在编译的时候加上unsafe开关,允许C#使用指针:

 1  FileStream fs  =   new  FileStream(image, FileMode.Open, FileAccess.Read);
 2  Image img  =  Image.FromStream(fs,  false false );
 3  Bitmap bmp  =   new  Bitmap(img);
 4  img.Dispose();
 5  fs.Close();
 6 
 7  int  width  =  bmp.Width;
 8  int  height  =  bmp.Height;
 9  BitmapData bmData  =  bmp.LockBits( new  Rectangle( 0 0 , width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
10  byte *  p  =  ( byte * )bmData.Scan0;
11  int  offset  =  bmData.Stride  -  width  *   3 // only correct when PixelFormat is Format24bppRgb
12   
13  for  ( int  j  =   0 ; j  <  height; j ++ )
14  {
15        for  ( int  i  =   0 ; i  <  width; i ++ )
16       {
17            p[ 2 +=   20 // should check boundary
18            p  +=   3 ;
19       }
20       p  +=  offset;
21  }
22   
23  bmp.UnlockBits(bmData);
24  bmp.Dispose();

 

1-5行一样的,不多说了。第7,8行保存一个临时变量,不要每次都调用,bmp.Width, bmp.Height。

第9行是把图像内容锁定到系统内存。这个函数有2个重载,第二种比较复杂,是把用户内存中的内容锁定。这个以后再说。第一种,也就是我们现在使用的这种,是把图像的内容根据一定的格式放到内存里面,这里我们使用的是Format24bppRgb,也就是24位色。在这种格式下3个字节表示一种颜色,也就是我们通常所知道的R,G,B, 所以每个字节表示颜色的一个分量。

第10行用了一个指针,指向这段内存的首地址。 这样我们可以直接来修改图像的内存信息。因为每个颜色分量都是一个字节,所以用byte的指针,如果是Format48bppRgb,每个分量用2个字节,那就该用Short指针了。

第11行需要多说两句,Stride是指图像每一行需要占用的字节数。根据BMP格式的标准,Stride一定要是4的倍数。据个例子,一幅1024*768的24bppRgb的图像,每行有效的像素信息应该是1024*3 = 3072。因为已经是4的倍数,所以Stride就是3072。那么如果这幅图像是35*30,那么一行的有效像素信息是105,但是105不是4的倍数,所以填充空字节,Stride应该是108。这一行计算出来的offset就是3。这里再留个问题,如果是16色图,也就是4位色,一幅50*50的图像,Stride又应该是多少呢?

第13-21行就是循环的处理。其中第17行需要注意以下。这句话其实是错的,因为p是byte,没有符号,最大值是255, 加20可能会溢出。如果你不希望它溢出,那么可以改成行1,如果希望溢出就是最大值,那么可以用行2。

1  p[ 2 =   checked (( byte )(p[ 2 +   20 ));
2  p[ 2 =  ( byte )Math.Min( byte .MaxValue, p[ 2 +   20 );

大多处情况下,图像处理用的都是行2的做法,但是这样的话性能损失还是比较厉害的,需要计算一个最大值,还有一个类型的转换,我还在研究有什么更快的办法。还有一点,BMP图像里面用的是小数端的存储方式(Little Endian),所以实际存储的图像顺序是G,B,R,这里要把图像的红色分量增加一些,改变的是第三个字节而不是第一个。

最后两行就不说了,处理完毕需要Unlock,bitmap必须被dispose掉。

用以上代码进行图像处理的速度已经飞快了,最慢的部分就是调用那个LockBits的函数,这个速度基本上跟GDI是差不多的。但是如果你还是觉得慢,那还有以下2种办法可以提高性能,不过它已经远远超过GDI+的范畴了。

1.别用GDI+的方法读图像,自己把图像文件读出来。如果是BMP倒是好办,要是用JPG,TIF,PNG,GIF....如果你足够牛,可以随手写个FFT或者DCT,那我倒是建议可以自己写写看,把JPG解压缩出来,自己修改修改再压缩回去。要是PNG/GIF/GIF 98,就是那个会动的GIF,我就无语了。GIF的标准是公开的,不过时人家的专利,你写了是要付钱的。所以,还是算了吧。自己读BMP可以非常快地把图像处理掉,连LockBits都不需要。

2.如果图像超级大,你又用了后面的行2进行处理,你对性能的要求又无比苛刻,那还是有条路可以走。直接用MMX/SSE/3D Now指令集,4个字节同时上。你去谢谢Intel/Amd给了那么好的指令集把,可惜C#你就别想了,没有办法直接用的。自己用C++调用MMX指令集写个Dll,然后用interop调用。Interop的过程当中也是有损失的,Managed的内存空间用C#里面的Marshal是需要被转换的。所以如果真的要这样做,我推荐使用C++.NET,两块东西都能写。这里我就不给出用MMX/SSE的例子了。最后再提一句,MMX对于图像处理也足够了,SSE主要是给浮点运算的,如果你有图像渲染的那种,用SSE会快很多,那是图形学的内容了,偶不懂,不多说,怕被人用棒子打。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值