C#中Image , Bitmap 和 BitmapData
先说Image,Image 就是个图像,不能实例化,提供了位图和源文件操作的函数。本篇文章他就是来打酱油的,这里提供一个Bitmap转成BitmapSource的方法。
1 [DllImport("gdi32")] 2 static extern int DeleteObject(IntPtr o); 3 /// <summary> 4 /// bitmap转换为bitmapsource 以适应wpf的image 5 /// </summary> 6 /// <param name="pic"></param> 7 /// <returns></returns> 8 public static BitmapSource GetMapSource(Bitmap pic) 9 { 10 IntPtr ip = pic.GetHbitmap(); 11 BitmapSource bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( 12 ip, IntPtr.Zero, Int32Rect.Empty, 13 System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()); 14 DeleteObject(ip); 15 return bitmapSource; 16 }
接下来说Bitmap和BitmapData。
Bitmap类
Bitmap对象封装了GDI+中的一个位图,此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义的图像的对象.该类的主要方法和属性如下:
1. GetPixel方法和SetPixel方法:获取和设置一个图像的指定像素的颜色.
2. PixelFormat属性:返回图像的像素格式.
3. Palette属性:获取和设置图像所使用的颜色调色板.
4. Height Width属性:返回图像的高度和宽度.
5. LockBits方法和UnlockBits方法:分别锁定和解锁系统内存中的位图像素.在基于像素点的图像处理方法中使用LockBits和UnlockBits是一个很好的方式,这两种方法可以使我们指定像素的范围来控制位图的任意一部分,从而消除了通过循环对位图的像素逐个进行处理,每调用LockBits之后都应该调用一次UnlockBits.
BitmapData类
BitmapData对象指定了位图的属性
1. Height属性:被锁定位图的高度.
2. Width属性:被锁定位图的高度.
3. PixelFormat属性:数据的实际像素格式.
4. Scan0属性:被锁定数组的首字节地址,如果整个图像被锁定,则是图像的第一个字节地址.
5. Stride属性:步幅,也称为扫描宽度.
这里要重点说说Stride属性,这个和Width有什么区别呢,可以这么说,如果你的图片大小也就是图片字节是4的整数倍,那么Stride与Width是相等的,否则Stride就是大于Width的最小4的整数倍。在处理过程中,Stride肯定是4的整数倍,这里是个坑啊。。。
盗张图,连接写在文章底部
先看看BitmapData的应用,我的场景是,我有一个一维像素点阵数组,里面放的是每个像素点的灰度值,知道宽和高,要转换成bitmap
1 /// <summary> 2 /// 像素点阵转换为bitmap 3 /// </summary> 4 /// <param name="rawValues">byte[]数组</param> 5 /// <param name="width">图片的宽度</param> 6 /// <param name="height">图片的高度</param> 7 /// <returns>bitmap图片</returns> 8 public static Bitmap ToGrayBitmap(byte[] rawValues, int width, int height) 9 { 10 Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); 11 BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); 12 //// 获取图像参数 13 //bmpData.Stride = width; 14 int stride = bmpData.Stride; // 扫描线的宽度 15 int offset = stride - width; // 显示宽度与扫描线宽度的间隙 16 IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置 17 int scanBytes = stride * height;// 用stride宽度,表示这是内存区域的大小 18 //// 下面把原始的显示大小字节数组转换为内存中实际存放的字节数组 19 int posScan = 0, posReal = 0;// 分别设置两个位置指针,指向源数组和目标数组 20 byte[] pixelValues = new byte[scanBytes]; //为目标数组分配内存 21 for (int x = 0; x < height; x++) 22 { 23 //// 下面的循环节是模拟行扫描 24 for (int y = 0; y < width; y++) 25 { 26 pixelValues[posScan++] = rawValues[posReal++]; 27 } 28 posScan += offset; //行扫描结束,要将目标位置指针移过那段“间隙” 29 } 30 //// 用Marshal的Copy方法,将刚才得到的内存字节数组复制到BitmapData中 31 System.Runtime.InteropServices.Marshal.Copy(pixelValues, 0, iptr, scanBytes); 32 bmp.UnlockBits(bmpData); // 解锁内存区域 33 //// 下面的代码是为了修改生成位图的索引表,从伪彩修改为灰度 34 ColorPalette tempPalette; 35 using (Bitmap tempBmp = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed)) 36 { 37 tempPalette = tempBmp.Palette; 38 } 39 for (int i = 0; i < 256; i++) 40 { 41 tempPalette.Entries[i] = System.Drawing.Color.FromArgb(i, i, i); 42 } 43 44 bmp.Palette = tempPalette; 45 46 //// 算法到此结束,返回结果 47 return bmp; 48 }
这代码也是网上找的,具体哪里已经忘记了。至于24位位图数据其实就是 一个像素点有rgb三个值而已,道理一样。
同样,我们也可以根据图片得到他的灰度数组
1 //8位位图得到除去文件头信息的一位灰度数组 2 3 4 BitmapData bmpData = map.LockBits(new System.Drawing.Rectangle(0, 0, map.Width, map.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); 5 6 //// 获取图像参数 7 8 int stride = bmpData.Stride; // 扫描线的宽度 9 10 int offset = stride - map.Width; // 显示宽度与扫描线宽度的间隙 11 12 IntPtr iptr = bmpData.Scan0; // 获取bmpData的内存起始位置 13 14 int scanBytes = stride * map.Height;// 用stride宽度,表示这是内存区域的大小 15 16 //// 下面把原始的显示大小字节数组转换为内存中实际存放的字节数组 17 18 mapdata = new byte[scanBytes]; //为目标数组分配内存 19 20 System.Runtime.InteropServices.Marshal.Copy(iptr, mapdata, 0, scanBytes); //copy内存中数据到数组中
这里对与bitmapdata的操作方式是ReadOnly
为什么说stride是坑呢,因为在工作中,我有一个大小不为4的整数倍的文件,通过上面方法将他们转为图片,然后操作之后我需要存回去,继续存成文件的形式,如果你直接存回去你会发现你的文件变大了。这时候就需要避开stride。其实stride占据的空间什么都没有做,我们如何遍历构建图片,就如何反遍历回数组就可以了
public static byte[] GetMapData(byte[] MapData,int width,int height) { var length = MapData.Length; if(width==length/height) { return MapData; } int offset=length/height-width; var scanBytes = width * height; byte[] RawMapData = new byte[scanBytes]; int posScan = 0, posReal = 0; for(int x=0;x<height;x++) { for (int y=0;y<width;y++) { RawMapData[posScan++] = MapData[posReal++]; } posReal += offset; } return RawMapData; }
至于24位位图转8位位图,还是看这位博主的博客,他总结了很多,我还是觉得opencv比较快捷方便。
http://blog.csdn.net/jiangxinyu/article/details/6222302
另外还看到了一下c#处理图片的方法,比如光照,雾化,浮雕等,请移步下面链接
http://www.pin5i.com/showtopic-20228.html
种一棵树最好的时间是十年前,其次是现在。