C# BitmapData和Marshal.Copy()用法

C# BitmapData和Marshal.Copy()用法

//此函数用法例子如下:

 

 

public static byte[] GetGrayArray(Bitmap srcBmp, Rectangle rect)
{
    //将Bitmap锁定到系统内存中,获得BitmapData
    //这里的第三个参数确定了该图像信息时rgb存储还是Argb存储
    BitmapData srcBmpData = srcBmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
    //位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行
    IntPtr srcPtr = srcBmpData.Scan0;
    //将Bitmap对象的信息存放到byte数组中
    int scanWidth = srcBmpData.Width * 3;
    int src_bytes = scanWidth * rect.Height;
    //int srcStride = srcBmpData.Stride;
    byte[] srcValues = new byte[src_bytes];
    byte[] grayValues = new byte[rect.Width * rect.Height];
    //RGB[] rgb = new RGB[srcBmp.Width * rows];
    //复制GRB信息到byte数组
    Marshal.Copy(srcPtr, srcValues, 0, src_bytes);
    //LogHelper.OutputArray(srcValues, rect.Width * 3, rect.Height, true);
    //Marshal.Copy(dstPtr, dstValues, 0, dst_bytes);
    //解锁位图
    srcBmp.UnlockBits(srcBmpData);
    //灰度化处理
    int m = 0, j = 0;
    int k = 0;
    byte gray;
    //根据Y = 0.299*R + 0.587*G + 0.114*B,intensity为亮度
    for (int i = 0; i < rect.Height; i++)  //行
    {
        for (j = 0; j < rect.Width; j++)  //列
        {
            //注意位图结构中RGB按BGR的顺序存储
            k = 3 * j;
            gray = (byte)(srcValues[i * scanWidth + k + 2] * 0.299
                 + srcValues[i * scanWidth + k + 1] * 0.587
                 + srcValues[i * scanWidth + k + 0] * 0.114);
            grayValues[m] = gray;  //将灰度值存到byte一维数组中
            m++;
        }
    }
    return grayValues;
}

 

 

 

 

/*
以上方法,主要是实现一个图像指定矩形区域的信息进行处理,
以一维数组形式返回指定矩形区域的灰度值,
此方法会遇到一个很棘手的问题
假设一张图像的大小256*256,想要处理图像的上下部分区域为256*10,左右部分区域10*256的信息,
则创建矩形区域为:
*/
// 上部分区域:
Rectangle rectTop = new Rectangle(0,0,256,10);
// 下部分区域:
Rectangle rectBottom = new Rectangle(0,256-10,256,10);
// 左部分区域:
Rectangle rectLeft = new Rectangle(0,0,10,256);
// 有部分区域:
Rectangle rectRight = new Rectangle(256-10,0,10,256);

/*
此时上下部分没有问题,左右部分则会出现问题,原因很简单
阐述如下:
是BitmapData,Marshal.Copy()函数造成的
C#处理图像时使用BitmapData,这个是将处理的图像锁定到内存中,为了提高效率,将图像锁定要内存
IntPtr srcPtr = srcBmpData.Scan0;这个是指向锁定图像的第一个地址,然后按照顺序读取像素数据
,这时,左右部分区域的宽度只有10,如果这一行读取完之后,应该此时换行读取下一行像素数据,
但是实际上它没有,而是一直按照顺序读取,没有换行,此时问题就出现了,
因为左区域是图像的一部分,整个图像比方如下:
“-”表示左区域,即要处理的部分;“+”表示图像的剩余部分
---+++++++++++++++++++++++
---+++++++++++++++++++++++
---+++++++++++++++++++++++
---+++++++++++++++++++++++
---+++++++++++++++++++++++
---+++++++++++++++++++++++
扫描时第一行读取到第三个“-”号之后,应该换行,但是没有,它继续读取“++++”,一直读取到行尾,
这才换行,问题就是这么产生的,
上图如果没有理解的,接下来我画一个图如下:

 

思考一下,两种办法如下
[A]首先,想到的最好的办法我要锁定哪一部分区域就要取得相应区域的像素数据,但是目前这个
我没有实现
[B]其次,我用了两外一种方法解决了,就是将源图像的要计算的区域或者要处理的区域图像裁切好,
因为源图像后面是要用的,所以裁切后得到的图像另保存之,传递给方法,然后将上述方法修改为
*/


///这里的srcBmp是裁切后要处理的区域的图像 public static byte[] GetGrayArray(Bitmap srcBmp) { //将Bitmap锁定到系统内存中,获得BitmapData //这里的第三个参数确定了该图像信息时rgb存储还是Argb存储 Rectangle rect = new Rectangle(0,0,srcBmp.Width,srcBmp.Height); //表示要锁定全图 BitmapData srcBmpData = srcBmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); //位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行 IntPtr srcPtr = srcBmpData.Scan0; //将Bitmap对象的信息存放到byte数组中 int scanWidth = srcBmpData.Width * 3; int src_bytes = scanWidth * rect.Height; //int srcStride = srcBmpData.Stride; byte[] srcValues = new byte[src_bytes]; byte[] grayValues = new byte[rect.Width * rect.Height]; //RGB[] rgb = new RGB[srcBmp.Width * rows]; //复制GRB信息到byte数组 Marshal.Copy(srcPtr, srcValues, 0, src_bytes); //LogHelper.OutputArray(srcValues, rect.Width * 3, rect.Height, true); //Marshal.Copy(dstPtr, dstValues, 0, dst_bytes); //解锁位图 srcBmp.UnlockBits(srcBmpData); //灰度化处理 int m = 0, j = 0; int k = 0; byte gray; //根据Y = 0.299*R + 0.587*G + 0.114*B,intensity为亮度 for (int i = 0; i < rect.Height; i++) //行 { for (j = 0; j < rect.Width; j++) //列 { //注意位图结构中RGB按BGR的顺序存储 k = 3 * j; gray = (byte)(srcValues[i * scanWidth + k + 2] * 0.299 + srcValues[i * scanWidth + k + 1] * 0.587 + srcValues[i * scanWidth + k + 0] * 0.114); grayValues[m] = gray; //将灰度值存到byte一维数组中 m++; } } return grayValues; }

  

//这样也能解决上述问题,求哪位大神赐教方法[A]的实现,谢谢,这个问题先记录到此。

 

//修改部分:
//今天又发现一个问题,左右部分图像裁切后处理的时候自动才每一行后面加上两个0,0
//这就导致计算出现问题了,为了解决此问题,可以讲上面的方法再进行修改
//只需要将上述方法中的
int scanWidth = srcBmpData.Width * 3;
//修改为
int scanWidth = srcBmpData.Stride;

//为什么用srcBmpData.Width * 3作为扫描行宽度我当时是这么理解的,上面的
BitmapData srcBmpData = srcBmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
//用的是PixelFormat.Format24bppRgb这个,这个表示每个像素是24为,红色,绿色,蓝色各使用8位
//那么byte[]数组用来存储的时候相当于宽度*3,其实这里如果是截图图像的水平区域的话没有问题
//要是截取的是图像的左右两侧部分区域则会出现上述问题,即每一行后面会多出两个字节,值为0,0
//至此测试通过,问题也算完美的解决了

posted @ 2015-03-11 17:53  ching126  阅读(3415)  评论(1编辑  收藏  举报
一个小小的平凡的事情坚持10年,回头看看,你会收获惊喜;坚持20年,回头看看,你的命运已经发生质的蜕变;坚持30年甚至更多年,回头看看,你的人生已经发生惊天动地的变化。