zoukankan      html  css  js  c++  java
  • GDI+中常见的几个问题(4)

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

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

     1 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
     2 Image img = Image.FromStream(fs, falsefalse);
     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, falsefalse);
     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(00, 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会快很多,那是图形学的内容了,偶不懂,不多说,怕被人用棒子打。

  • 相关阅读:
    Python常用第三方库总结
    Python爬虫技术--入门篇--爬虫介绍
    X sql解惑 25 里程碑问题 答案
    X sql解惑 34 咨询顾问收入问题
    从小变大的照片
    获取属性的顺序
    for...in
    判断元素是否存在
    自由的元素名称
    ES6语法糖-简洁属性表示
  • 原文地址:https://www.cnblogs.com/hotcan/p/1317752.html
Copyright © 2011-2022 走看看