C# 指针操作图像

原文地址:https://www.cnblogs.com/lixunwu/articles/6692007.html

读取图片时,请参考bolg文章:C#图片的像素格式问题研究 (http://www.cnblogs.com/lixunwu/articles/6691962.html)

 

private unsafe void ProcessFilter(Bitmap imageT)
        {
                PixelFormat pixelFormat = imageT.PixelFormat;
                // get image dimension
                int width = imageT.Width;
                int height = imageT.Height;
                Rectangle rect = new Rectangle(0, 0, width, height);
                BitmapData image = new BitmapData();
                image = imageT.LockBits(rect, ImageLockMode.ReadWrite, pixelFormat);
                // pixel value

                if (
                    (pixelFormat == PixelFormat.Format8bppIndexed) ||
                    (pixelFormat == PixelFormat.Format24bppRgb) ||
                    (pixelFormat == PixelFormat.Format32bppRgb) ||
                    (pixelFormat == PixelFormat.Format32bppArgb))
                {
                    // initialize other variables
                    int pixelSize = (pixelFormat == PixelFormat.Format8bppIndexed) ? 1 :
                        (pixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4;
                    int lineSize = width * pixelSize;
                    int srcOffset = image.Stride - lineSize;

                    // do the job
                    byte* ptr = (byte*)image.Scan0.ToPointer();
                    // for each line
                    for (int y = 0; y < height; y++)
                    {
                        // for each pixel
                        for (int x = 0; x < lineSize; x++, ptr += pixelSize)
                        {
                            //ptr[0] ptr[1] ptr[2]
                            //int temp = *ptr;
                            //countPixel[temp]++;
                            //if (countPixel[temp] > maxPixel)
                            //{
                            //    maxPixel = countPixel[temp];
                            //}
                        }
                        ptr += srcOffset;
                    }
                    imageT.UnlockBits(image);
                    ptr = null;
                    imageT = null;
                    image = null;
                }
// else 部分的代码未修正
        else
               {
                // initialize other variables
                int pixelSize = ( pixelFormat == PixelFormat.Format16bppGrayScale ) ? 1 :
                    ( pixelFormat == PixelFormat.Format48bppRgb ) ? 3 : 4;
                int lineSize  = width * pixelSize;
                int srcStride = image.Stride;
                int ovrStride = overlay.Stride;

                // do the job
                byte* basePtr = (byte*) image.ImageData.ToPointer( );
                byte* baseOvr = (byte*) overlay.ImageData.ToPointer( );

                // for each line
                for ( int y = 0; y < height; y++ )
                {
                    ushort * ptr = (ushort*) ( basePtr + y * srcStride );
                    ushort * ovr = (ushort*) ( baseOvr + y * ovrStride );

                    // for each pixel
                    for ( int x = 0; x < lineSize; x++, ptr++, ovr++ )
                    {
                        // abs(sub)
                        v = (int) *ptr - (int) *ovr;
                        *ptr = ( v < 0 ) ? (ushort) -v : (ushort) v;
                    }
                }
            }

================================================================================================================================================================================================

本文讨论了C#图像处理中Bitmap类、BitmapData类和unsafe代码的使用以及字节对齐问题。

Bitmap类 命名空间:System.Drawing 封装 GDI+ 位图,此位图由图形图像及其属性的像素数据组成。Bitmap 是用于处理由像素数据定义的图像的对象。     

利用C#类进行图像处理,最方便的是使用Bitmap类,使用该类的GetPixel()与SetPixel()来访问图像的每个像素点。下面是MSDN中的示例代码:

 unsafe
            {
                bmp = ComFun.ComFunction.ReadImageFile(strpath);
                Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
                System.Drawing.Imaging.BitmapData bmpData =
                    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
                    bmp.PixelFormat);
                // Get the address of the first line. 
                IntPtr ptr = bmpData.Scan0;     // Declare an array to hold the bytes of the bitmap. 
                                                // This code is specific to a bitmap with 24 bits per pixels. 
                int pixelSize=3;
                if (
                    (bmp.PixelFormat == PixelFormat.Format8bppIndexed) ||
                    (bmp.PixelFormat == PixelFormat.Format24bppRgb) ||
                    (bmp.PixelFormat == PixelFormat.Format32bppRgb) ||
                    (bmp.PixelFormat == PixelFormat.Format32bppArgb))
                {
                    // initialize other variables
                    pixelSize = (bmp.PixelFormat == PixelFormat.Format8bppIndexed) ? 1 :
                        (bmp.PixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4;
                    int lineSize = bmpData.Width * pixelSize;
                    int srcOffset = bmpData.Stride - lineSize;
                }
                #region 方法修改像素值
                int bytes = bmp.Width * bmp.Height * pixelSize;

                byte[] rgbValues = new byte[bytes];     // Copy the RGB values into the array. 
                System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
                //Set every red value to 255.
                for (int counter = 0; counter < rgbValues.Length; counter += pixelSize)
                {
                    rgbValues[counter + 1] = 20;
                    rgbValues[counter + 2] = 255;
                    rgbValues[counter + 0] = 20;
                }
                //Copy the RGB values back to the bitmap
                System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);     // Unlock the bits. 
                #endregion
                               bmp.UnlockBits(bmpData);     // Draw the modified image. 

            }
            pictureBox.Image = bmp;
        }

可见,Bitmap类使用一种优雅的方式来操作图像,但是带来的性能的降低却是不可忽略的。比如对一个800*600的彩色图像灰度化,其耗费的时间都要以秒为单位来计算。在实际项目中进行图像处理,这种速度是决对不可忍受的。

 

BitmapData类 命名空间:System.Drawing.Imaging 指定位图图像的属性。BitmapData 类由 Bitmap 类的 LockBits 和 UnlockBits 方法使用。不可继承。    

好在我们还有BitmapData类,通过BitmapData BitmapData LockBits ( )可将 Bitmap 锁定到系统内存中。该类的公共属性有:

  • Width           获取或设置 Bitmap 对象的像素宽度。这也可以看作是一个扫描行中的像素数。
  • Height          获取或设置 Bitmap 对象的像素高度。有时也称作扫描行数。
  • PixelFormat 获取或设置返回此 BitmapData 对象的 Bitmap 对象中像素信息的格式。
  • Scan0            获取或设置位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行。
  • Stride            获取或设置 Bitmap 对象的跨距宽度(也称为扫描宽度)。

    下面的MSDN中的示例代码演示了如何使用 PixelFormat、Height、Width 和 Scan0 属性;LockBits 和 UnlockBits 方法;以及 ImageLockMode 枚举。

private void LockUnlockBitsExample(PaintEventArgs e) { // Create a new bitmap. Bitmap bmp = new Bitmap("c://fakePhoto.jpg"); // Lock the bitmap's bits. Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat); // Get the address of the first line. IntPtr ptr = bmpData.Scan0; // Declare an array to hold the bytes of the bitmap. // This code is specific to a bitmap with 24 bits per pixels. int bytes = bmp.Width * bmp.Height * 3; byte[] rgbValues = new byte[bytes]; // Copy the RGB values into the array. System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); // Set every red value to 255. for (int counter = 0; counter < rgbValues.Length; counter+=3) rgbValues[counter] = 255; // Copy the RGB values back to the bitmap System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes); // Unlock the bits. bmp.UnlockBits(bmpData); // Draw the modified image. e.Graphics.DrawImage(bmp, 0, 150); }

上面的代码演示了如何用数组的方式来访问一幅图像,而不在使用低效的GetPixel()和SetPixel()。  

 

unsafe代码    

而在实际中上面的做法仍然不能满足我们的要求,图像处理是一种运算量比较大的操作,不同于我们写的一般的应用程序。我们需要的是一种性能可以同C++程序相媲美的图像处理程序。C++是怎么提高效率的呢,答曰:指针。幸运的是.Net也允许我们使用指针,只能在非安全代码块中使用指针。

何谓非安全代码?     为了保持类型安全,默认情况下,C# 不支持指针运算。不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。在公共语言运行库 (CLR) 中,不安全代码是指无法验证的代码。C# 中的不安全代码不一定是危险的,只是其安全性无法由 CLR 进行验证的代码。因此,CLR 只对在完全受信任的程序集中的不安全代码执行操作。如果使用不安全代码,由您负责确保您的代码不会引起安全风险或指针错误。不安全代码具有下列属性:

  • 方法、类型和可被定义为不安全的代码块。
  • 在某些情况下,通过移除数组界限检查,不安全代码可提高应用程序的性能。
  • 当调用需要指针的本机函数时,需要使用不安全代码。
  • 使用不安全代码将引起安全风险和稳定性风险。
  • 在 C# 中,为了编译不安全代码,必须用 /unsafe 编译应用程序。

    正如《C#语言规范》中所说无论从开发人员还是从用户角度来看,不安全代码事实上都是一种“安全”功能。不安全代码必须用修饰符 unsafe 明确地标记,这样开发人员就不会误用不安全功能,而执行引擎将确保不会在不受信任的环境中执行不安全代码。     以下代码演示如何借助BitmapData类采用指针的方式来遍历一幅图像,这里的unsafe代码块中的代码就是非安全代码。

//å建å¾å Bitmap image = new Bitmap( "c://images//image.gif" ); //è·åå¾åçBitmapData对å BitmapData data = image.LockBits( new Rectangle( 0 , 0 , image.Width , image.Height ) , ImageLockMode.ReadWrite , PixelFormat.Format24bppRgb ); //循ç¯å¤ç unsafe { byte* ptr = ( byte* )( data.Scan0 ); for( int i = 0 ; i < data.Height ; i ++ ) { for( int j = 0 ; j < data.Width ; j ++ ) { // write the logic implementation here ptr += 3; } ptr += data.Stride - data.Width * 3; } }

  毫无疑问,采用这种方式是最快的,所以在实际工程中都是采用指针的方式来访问图像像素的。

 unsafe
            {
                bmp = ComFun.ComFunction.ReadImageFile(strpath);
                Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
                System.Drawing.Imaging.BitmapData bmpData =
                    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
                    bmp.PixelFormat);
                // Get the address of the first line. 
                IntPtr ptr = bmpData.Scan0;     // Declare an array to hold the bytes of the bitmap. 
                                                // This code is specific to a bitmap with 24 bits per pixels. 
                int pixelSize=3;
                if (
                    (bmp.PixelFormat == PixelFormat.Format8bppIndexed) ||
                    (bmp.PixelFormat == PixelFormat.Format24bppRgb) ||
                    (bmp.PixelFormat == PixelFormat.Format32bppRgb) ||
                    (bmp.PixelFormat == PixelFormat.Format32bppArgb))
                {
                    // initialize other variables
                    pixelSize = (bmp.PixelFormat == PixelFormat.Format8bppIndexed) ? 1 :
                        (bmp.PixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4;
                    int lineSize = bmpData.Width * pixelSize;
                    int srcOffset = bmpData.Stride - lineSize;
                }
          
                #region 使用指针修改像素点
                byte* ptr1 = (byte*)(bmpData.Scan0);
                for (int i = 0; i < bmpData.Height; i++)
                {
                    for (int j = 0; j < bmpData.Width; j++)
                    {
                        // write the logic implementation here 
                        *ptr1 = 230;
                        *(ptr1 + 1) = 30;
                        *(ptr1 + 2) = 30;
                        ptr1 += pixelSize;
                    }
                    ptr1 += bmpData.Stride - bmpData.Width * pixelSize;
                }
                #endregion
                bmp.UnlockBits(bmpData);     // Draw the modified image. 

            }
            pictureBox.Image = bmp;
        }

  

 

字节对齐问题
    上例中ptr += data.Stride - data.Width * 3,表示跨过无用的区域,其原因是图像数据在内存中存储时是按4字节对齐的,具体解释如下:     假设有一张图片宽度为6,假设是Format24bppRgb格式的(每像素3字节,在以下的讨论中,除非特别说明,否则Bitmap都被认为是24位RGB)。显然,每一行需要6*3=18个字节存储。对于Bitmap就是如此。但对于BitmapData,虽然data.Width还是等于image.Width,但大概是出于显示性能的考虑,每行的实际的字节数将变成大于等于它的那个离它最近的4的整倍数,此时的实际字节数就是Stride。就此例而言,18不是4的整倍数,而比18大的离18最近的4的倍数是20,所以这个data.Stride = 20。显然,当宽度本身就是4的倍数时,data.Stride = image.Width * 3。     画个图可能更好理解。R、G、B 分别代表3个原色分量字节,BGR就表示一个像素。为了看起来方便我在们每个像素之间插了个空格,实际上是没有的。X表示补足4的倍数而自动插入的字节。为了符合人类的阅读习惯我分行了,其实在计算机内存中应该看成连续的一大段。

|-------Stride-----------|
|-------Width---------| |
Scan0:
BGR BGR BGR BGR BGR BGR XX
BGR BGR BGR BGR BGR BGR XX
BGR BGR BGR BGR BGR BGR XX
.
.
.     首先用data.Scan0找到第0个像素的第0个分量的地址,这个地址指向的是个byte类型,所以当时定义为byte* ptr。行扫描时,在当前指针位置(不妨看成当前像素的第0个颜色分量)连续取出三个值(3个原色分量。注意,0 1 2代表的次序是B G R。在取指针指向的值时,貌似p[n]和p += n再取p[0]是等价的),然后下移3个位置(ptr += 3,看成指到下一个像素的第0个颜色分量)。做过Bitmap.Width次操作后,就到达了Bitmap.Width * 3的位置,应该要跳过图中标记为X的字节了(共有Stride - Width * 3个字节),代码中就是 ptr += dataIn.Stride - dataIn.Width * 3。     通过阅读本文,相信你已经对使用C#进行图像处理可能用到的几种方法有了一个了解。至于采用哪种方式,取决于你的性能要求。其中第一种方式最优雅;第三种方式最快,但不是安全代码;第二种方式取了个折中,保证是安全代码的同时又提高了效率。熟悉C/C++编程的人可能会比较偏向于第三种方式,我个人也比较喜欢第三种方式。

参考:

1. MSDN2005

2. C#语言规范

3. Basic Image Processing support in C#

4. 使用C#的BitmapData

作者:http://conner-wang.spaces.live.com/转载请注明出处!

彩色变黑白的方法

彩色变黑白的方法

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值