.net字节流转换为Bitmap图像
本文背景:由于项目用到了C++库进行开发,该库一个回调函数中将位图数据的图像数据作为byte[]传入,用作显示。由于只有图像数据信息,而没有信息头等,所以直接使用Bitmap bitmap = new Bitmap(stream)来构造位图对象时会报参数错误。网上查找资料也未找到相关原因,不过据报错内容推测,应该是数据格式有误。所以考虑到了Win32下位图的格式,想到微软不会因为语言不同而搞两套不同的格式吧,于是就有了此文。
首先看看位图文件的组成部分:
位图文件主要分为如下3个部分:
块名称 对应Windows结构体定义 大小(Byte)
文件信息头 BITMAPFILEHEADER 14
位图信息头 BITMAPINFOHEADER 40
RGB颜色阵列 BYTE* 由图像长宽尺寸决定
一、文件信息头(BITMAPFILEHEADER)的结构如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
bfType 说明文件的类型,该值必需是0x4D42,也就是字符’BM’。
bfSize 说明该位图文件的大小,用字节为单位
bfReserved1 保留,必须设置为0
bfReserved2 保留,必须设置为0
bfOffBits 说明从文件头开始到实际的图象数据之间的字节的偏移量。这个参数是非常有用的,因为位图信息头和调色板的长度会根据不同情况而变化,所以你可以用这个偏移值迅速的从文件中读取到位数据。一般情况下大小为文件头加上位图信息头,即14+40=54
二、位图信息头(BITMAPINFOHEADER)结构如下:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
biSize 说明BITMAPINFOHEADER结构所需要的字数。
biWidth 说明图象的宽度,以象素为单位。
biHeight 说明图象的高度,以象素为单位。注:这个值除了用于描述图像的高度之外,它还有另一个用处,就是指明该图像是倒向的位图,还是正向的位图。如果该值是一个正数,说明图像是倒向的,如果该值是一个负数,则说明图像是正向的。大多数的BMP文件都是倒向的位图,也就是时,高度值是一个正数。
biPlanes 为目标设备说明位面数,其值将总是被设为1。
biBitCount 说明比特数/象素,其值为1、4、8、16、24、或32。但是由于我们平时用到的图像绝大部分是24位和32位的,所以我们讨论这两类图像。
biCompression 说明图象数据压缩的类型,同样我们只讨论没有压缩的类型:BI_RGB。
biSizeImage 说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0。
biXPelsPerMeter 说明水平分辨率,用象素/米表示。
biYPelsPerMeter 说明垂直分辨率,用象素/米表示。
biClrUsed 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。
biClrImportant 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。
三、位图数据
1、RGB颜色阵列
有关RGB三色空间我想大家都很熟悉,这里我想说的是在Windows下,RGB颜色阵列存储的格式其实BGR。也就是说,对于24位的RGB位图像素数据格式是:
蓝色B值 绿色G值 红色R值
对于32位的RGB位图像素数据格式是:
蓝色B值 绿色G值 红色R值 透明通道A值
透明通道也称Alpha通道,该值是该像素点的透明属性,取值在0(全透明)到255(不透明)之间。对于24位的图像来说,因为没有Alpha通道,故整个图像都不透明。
2、行对齐
由于Windows在进行行扫描的时候最小的单位为4个字节,所以当图片宽 X 每个像素的字节数 != 4的整数倍时,要在每行的后面补上缺少的字节,以0填充(一般来说当图像宽度为2的幂时不需要对齐)。位图文件里的数据在写入的时候已经进行了行对齐,也就是说加载的时候不需要再做行对齐。但是这样一来图片数据的长度就不是:宽 X 高 X 每个像素的字节数 了,我们需要通过下面的方法计算正确的数据长度:
//Calculate the image data size
int LineByteCnt = (((ImageWidth * biBitCount) + 31) >> 5) << 2;
ImageDataSize = LineByteCnt * ImageHeight;
知道了位图的结构以及要求后,手动填充信息头部分完成到Bitmap对象的转换。
public System.Windows.Media.Imaging.BitmapImage GetBitmapFromMemory(byte[] imageData,int length)
{
Bitmap bitmap = null;
// int length = imagedatadetails.Length;
using (MemoryStream stream = new MemoryStream(length*4 + 14 + 40))//为头腾出54个长度的空间
{
//开始写文件信息头
byte[] buffer = new byte[13];
buffer[0] = 0x42;//bitmap 固定常数
buffer[1] = 0x4d;//bitmap 固定常数
stream.Write(buffer, 0, 2);//先写入头的前两个字节
//把我们之前获得的数据流的长度转换成字节,
//这个是用来告诉“头”我们的实际图像数据有多大
byte[] bytes = BitConverter.GetBytes(length*4);
stream.Write(bytes, 0, 4);//把这个长度写入头中去
bytes = BitConverter.GetBytes(0)
stream.Write(buffer, 0, 4);//在写入4个字节长度的数据到头中去
int num2 = 54;//bitmap 固定常数也就是十六进制的0x36
bytes = BitConverter.GetBytes(num2);
stream.Write(bytes, 0, 4);//在写入最后4个字节的长度
//开始写入位图信息头
bytes = BitConverter.GetBytes(40); //写入信息头的长度biSize
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(118); //写入信息头的图像宽度biWidth
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(68); //写入信息头的图像高度biHeight
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes((short)1); //写入信息头的biPlanes
stream.Write(bytes, 0, 2);
bytes = BitConverter.GetBytes((short)32); //写入信息头的biBitCount
stream.Write(bytes, 0, 2);
bytes = BitConverter.GetBytes(0); //写入信息头的biCompression
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(0); //写入信息头的biSizeImage
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(0); //写入信息头的biXPelsPerMeter
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(0); //写入信息头的biYPelsPerMeter
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(0); //写入信息头的biClrUsed
stream.Write(bytes, 0, 4);
bytes = BitConverter.GetBytes(0); //写入信息头的biClrImportant
stream.Write(bytes, 0, 4);
//stream.GetBuffer();
//写入位图数据,由于我的数据是灰度单通道图像,所以复制4个字节数据作为一个RGBA的像素
for (int i = 0; i < length; ++i)
{
byte[] x = new byte[] {imageData[i]};
stream.Write(x, 0, 1);
stream.Write(x, 0, 1);
stream.Write(x, 0, 1);
stream.Write(x, 0, 1);
}
bitmap = new Bitmap(stream);//用内存流构造出一幅bitmap的图片
bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
MemoryStream memstream = new MemoryStream();
bitmap.Save(memstream,System.Drawing.Imaging.ImageFormat.Bmp);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = memstream;
image.EndInit();
stream.Close();
bitmap.Dispose();
return image;//最后就得到了我们想要的图片了
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
如上函数将内存中的字节流转换为了c#中可以使用的Bitmap或BitmapImage对象,可以直接贴到窗口上进行显示。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性