wpf大图片处理速度优化【指针操作】【并行操作】【几十倍优化】

我一直用GDI+做Winform 的基于指针的图片处理,这次下决心全部移到wpf上(主要是显示布局很方便)
采用的图片是
2512*3307 的大图 830万像素
类库基于 WritableBitmapEx 的wpf版本
函数是我自己写的扩展方法,只是利用了 writableBitmapEx提供的环境 ,我懒得从头到尾自己写了
 
1.标准int32数组遍历计算 release
0.28s
         unsafe  public  static  void TestGray1( this WriteableBitmap bmp)
        {
             using ( var context = bmp.GetBitmapContext())
            {
                 int height = context.Height;
                 int width = context.Width;
                 for ( int y =  0; y < height; y++)
                {                    
                     for ( int x =  0; x < width; x++)
                    {
                         int pos=y * context.Width + x;
                         var c = context.Pixels[pos];
                         var r = ( byte)(c >>  16);
                         var g = ( byte)(c >>  8);
                         var b = ( byte)(c);
                        
                         var gray = ((r *  38 + g *  75 + b *  15) >>  7);

                         var color=( 255 <<  24) | (gray <<  16) | (gray <<  8) | gray;
                        context.Pixels[pos]=color;
                    }
                }
            }
        }

 

2.标准int32指针遍历计算 release

0.04s

         unsafe  public  static  void TestGray2( this WriteableBitmap bmp)
        {
             using ( var context = bmp.GetBitmapContext())
            {
                 var ptr = context.Pixels;

                 int height = context.Height;
                 int width = context.Width;
                 for ( int y =  0; y < height; y++)
                {
                     for ( int x =  0; x < width; x++)
                    {
                         var c = *ptr;
                         var r = ( byte)(c >>  16) ;
                         var g = ( byte)(c >>  8) ;
                         var b = ( byte)(c) ;

                         var gray = ((r *  38 + g *  75 + b *  15) >>  7);

                         var color = ( 255 <<  24) | (gray <<  16) | (gray <<  8) | gray;
                        *ptr = color;

                        ptr++;
                    }
                }
            }
        }

 

3.colorstruct指针 遍历计算

0.02 s

应该是已经到极限速度了[除了后面的并行方式],我已经想不出还有什么方法可以提高处理速度

而且这种方式是最直观的,最容易理解的处理方式,也便于以后维护

 

    [StructLayout(LayoutKind.Sequential)]
     public  struct PixelColor
    {
         public  byte Blue;
         public  byte Green;
         public  byte Red;
         public  byte Alpha;
    } 

         unsafe  public  static  void TestGray3( this WriteableBitmap bmp)
        {
             using ( var context = bmp.GetBitmapContext())
            {
                 var ptr = (PixelColor*)context.Pixels;

                 int height = context.Height;
                 int width = context.Width;
                 for ( int y =  0; y < height; y++)
                {
                     for ( int x =  0; x < width; x++)
                    {
                         var c = *ptr;
                         var gray = ((c.Red *  38 + c.Green *  75 + c.Blue *  15) >>  7);
                        (*ptr).Green=(*ptr).Red=(*ptr).Blue = ( byte)gray;

                        ptr++;
                    }
                }
            }
        }

 

4.作为对比,我又测试了一下 GDI+的 指针处理图片的速度

0.06s

 

         public  static  unsafe Bitmap ToGray(Bitmap img)
        {
             var rect =  new System.Drawing.Rectangle( 00, img.Width, img.Height);

             var data = img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
             var ptr = (ColorType*)data.Scan0.ToPointer();

             var bytes =  new Int32[img.Width * img.Height];

             var height = img.Height;
             var width = img.Width;
             for ( int y =  0; y < height; y++)
            {
                 for ( int x =  0; x < width; x++)
                {
                     var color = *ptr;
                     var gray = ((color.R *  38 + color.G *  75 + color.B *  15) >>  7);

                    (*ptr).R = (*ptr).G = (*ptr).B = ( byte)gray;

                    ptr++;
                 }
            }

            img.UnlockBits(data);

             return img;
        }

 

5.重头戏来了。我一直对Parallel.For 很迷惑,为什么他的消耗时间是普通for的好几倍。今天仔细研究了一下,发现原来是用错了

0.01秒   release

 笔记本i5cpu,如果台式机的I7会更加强悍,速度会成半成半降低。

 

 

主要是利用了微软的任务并行库的循环并行化的方法。

注意:默认的并行循环对于函数体很小的情况是很慢的,这种情况必须用Partitioner 创建循环体,这在MSDN有介绍,是关键之中的关键

 

         unsafe  public  static  void TestGray5( this WriteableBitmap bmp)
        {  
             using ( var context = bmp.GetBitmapContext())
            {
                 int height = context.Height;
                 int width = context.Width;

                Parallel.ForEach(Partitioner.Create( 0, height), (h) =>
                {
                     var ptr = (PixelColor*)context.Pixels;
                    ptr += h.Item1 * width;

                     for ( int y = h.Item1; y < h.Item2; y++)
                    {
                         for ( int x =  0; x < width; x++)
                        {
                             var c = *ptr;
                             var gray = ((c.Red *  38 + c.Green *  75 + c.Blue *  15) >>  7);
                            (*ptr).Green = (*ptr).Red = (*ptr).Blue = ( byte)gray;

                            ptr++;
                        }
                    }

                });

            }
        }

  

感想

1.绝对不要在循环体内使用属性或函数,很有可能会降低数倍计算速度。

因为属性本质上是个函数,而在循环体内最好不要再调用函数,如果确实需要用内联代码的方式,c#没有inline,那么copy代码吧,反正为了速度。

2. 用指针移位操作 似乎比 直接数组访问要快10倍啊

我感觉要么是cache命中的原因,要么是 数组本身存取被属性封装了。相当于又调用了函数。

3.TPL 任务并行库果真好用,看来微软早已考虑过大量数据并行的循环优化问题09年,只是我一直用错了方法,才觉得很慢。

 

天气真好,写完代码后心情舒畅。

你可能感兴趣的:(图片处理)