1、前言
此代码的功能是传入BMP格式的数据,传出提取的RGB数据。BMP图像支持8bit、16bit、24bit、32bit的格式,得到的都是24bit的RGB格式图像,如果想得到YUV格式的图像,可以将得到RGB再转为YUV格式。RGB转YUV格式参考博客《C语言实现RGB packet格式转YUV(NV21)格式》。
2、示例代码
//BMP图片的压缩方式
#define BI_RGB (0)
#define BI_BITFIELDS (3)
//RGB555的掩码
#define RGB555_RED_MASK (31744)
#define RGB555_GREEN_MASK (992)
#define RGB555_BLUE_MASK (31)
//RGB565的掩码
#define RGB565_RED_MASK (63488)
#define RGB565_GREEN_MASK (2016)
#define RGB565_BLUE_MASK (31)
//BMP图片的像素位数
#define BMP_8_BIT (8)
#define BMP_16_BIT (16)
#define BMP_24_BIT (24)
#define BMP_32_BIT (32)
//16位BMP图片从文件头到图像数据的偏移量
#define BMP_16_BIT_BI_BITFIELDS_OFFSET (66)
#define BMP_16_BIT_BI_RGB_OFFSET (54)
//8位BMP图片调色板的大小
#define BMP_8_BIT_OFFSET (1024)
//BMP图片的文件头,固定14字节
#define BMP_FILE_HEADER_LENGTH (14)
#define isNull(ptr) (NULL == (ptr))
#define isNotNull(ptr) (NULL != (ptr))
// BMP 文件头
typedef struct
{
//unsigned short bfType; // 2
unsigned long bfSize; // 4
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned long bfOffBits;
} ClBitMapFileHeader;
//BMP图片的信息头
typedef struct InfoHeader
{
unsigned int bisize; //信息头的大小,一般是40
int biWidth; //位图的宽
int biHeight; //位图的高
unsigned short biPlanes; //固定值1
unsigned short biBitCount; //每个像素的位数
unsigned int biCompression; //压缩方式,BI_RGB(0)为不压缩, BI_BITFIELDS(3)用于16位、32位位图
unsigned int biSizeImage; //位图全部像素占用的字节
int biXpelsPerMeter; //水平分辨率
int biYPelsPerMeter; //垂直分辨率
unsigned int biClrUsed; //位图使用的颜色数。0代表颜色数为2的biBitCount次方
unsigned int biClrImportant; //重要的颜色数,0代表所有颜色都重要
}BmpInfoHeader;
/*
*函数功能:将bmp图片传入,输出BGR图像,图像方向是正的
*传参: pSrcData(in) ——输入bmp图像数据
* pDstData(out) ——输出BGR图像
* pWidth(out) ——bmp图像的宽
* pHeight (out) ——bmp图像的高
*
*返回值:成功返回0,失败返回-1
*/
int BMPExtractBGR(char* pSrcData, char* pDstData, unsigned int *pWidth, unsigned int *pHeight)
{
BmpInfoHeader BmpInfoHeader;
unsigned int width, height;
int i, j;
int offSet;
int RGB24 = 0;
unsigned short RGB16 = 0;
int count = 0;
ClBitMapFileHeader fHeader;
if(isNull(pSrcData) || isNull(pDstData))
{
printf("input parameter invalid\n");
return -1;
}
//根据前两个字节,判断是不是BMP图片
if ((pSrcData[0] != 'B') || (pSrcData[1] != 'M'))
{
printf( "not a bmp picture.\n");
return -1;
}
//获取文件头,要跳过前两个字节
memcpy(&fHeader, pSrcData + 2, sizeof(ClBitMapFileHeader));
//获取信息头
memcpy(&BmpInfoHeader, pSrcData + BMP_FILE_HEADER_LENGTH, sizeof(BmpInfoHeader));
width = BmpInfoHeader.biWidth;
height = BmpInfoHeader.biHeight;
//将宽高信息传递出去
*pWidth = width;
*pHeight = height;
printf("biBitCount = %d, bisize = %d, wxh=%dx%d, bfOffBits=%d, bfSize=%d\n", \
BmpInfoHeader.biBitCount, BmpInfoHeader.bisize, width, height, fHeader.bfOffBits, fHeader.bfSize);
//判断BMP图片像素的位数
if(BmpInfoHeader.biBitCount == BMP_24_BIT)
{
offSet = fHeader.bfOffBits;//文件头到BGR数据的偏移量
j = 0;
//读取文件的BRG数据
for(i = height - 1; i >= 0; i-- )
{
memcpy(pDstData + j * width * 3, pSrcData + offSet + i * width * 3, width * 3);
j++;
}
}
else if(BmpInfoHeader.biBitCount == BMP_8_BIT)
{
offSet = BMP_FILE_HEADER_LENGTH + BmpInfoHeader.bisize + BMP_8_BIT_OFFSET;
for(i = height - 1; i >= 0; i --)
{
for(j = 0; j < width; j++)
{
memcpy(pDstData + count * 3, pSrcData + BMP_FILE_HEADER_LENGTH + BmpInfoHeader.bisize + 4 * (int)pSrcData[offSet + i * width + j], 3);
count++;
}
}
}
else if(BmpInfoHeader.biBitCount == BMP_16_BIT)
{
if(BmpInfoHeader.biCompression == BI_BITFIELDS)
{
//根据掩码判断是RGB555
if((*((int *)(pSrcData + 54)) == RGB555_RED_MASK) && (*((int *)(pSrcData + 58)) == RGB555_GREEN_MASK) \
&& (*((int *)(pSrcData + 62)) == RGB555_BLUE_MASK))
{
//提取BGR数据
for(i = height - 1; i >= 0; i--)
{
for(j = 0; j < width; j++)
{
RGB24 = 0;
RGB16 = *((unsigned short *)(pSrcData + BMP_16_BIT_BI_BITFIELDS_OFFSET + 2 * (i * width + j)));
RGB24 |= ((RGB16 & RGB555_RED_MASK) << 9) | ((RGB16 & RGB555_GREEN_MASK) << 6) | ((RGB16 & RGB555_BLUE_MASK) << 3);
memcpy(pDstData + count * 3, &RGB24, 3);
count++;
}
}
}
//根据掩码判断是RGB565
else if((*((int *)(pSrcData + 54)) == RGB565_RED_MASK) && (*((int *)(pSrcData + 58)) == RGB565_GREEN_MASK) \
&& (*((int *)(pSrcData + 62)) == RGB565_BLUE_MASK))
{
for(i = height - 1; i >= 0; i--)
{
for(j = 0; j < width; j++)
{
RGB24 = 0;
RGB16 = *((unsigned short *)(pSrcData + BMP_16_BIT_BI_BITFIELDS_OFFSET + 2 * (i * width + j)));
RGB24 |= ((RGB16 & RGB565_RED_MASK) << 8) | ((RGB16 & RGB565_GREEN_MASK) << 5) | ((RGB16 & RGB565_BLUE_MASK) << 3);
memcpy(pDstData + count * 3, &RGB24, 3);
count++;
}
}
}
}
else if(BmpInfoHeader.biCompression == BI_RGB)
{
//提取BGR数据
for(i = height - 1; i >= 0; i--)
{
for(j = 0; j < width; j++)
{
RGB24 = 0;
RGB16 = *((unsigned short *)(pSrcData + BMP_16_BIT_BI_RGB_OFFSET + 2 * (i * width + j)));
RGB24 |= ((RGB16 & RGB555_RED_MASK) << 9) | ((RGB16 & RGB555_GREEN_MASK) << 6) | ((RGB16 & RGB555_BLUE_MASK) << 3);
memcpy(pDstData + count * 3, &RGB24, 3);
count++;
}
}
}
}
else if(BmpInfoHeader.biBitCount == BMP_32_BIT)
{
offSet = BMP_FILE_HEADER_LENGTH + BmpInfoHeader.bisize;//文件头到BGR数据的偏移量
count = 0;
for(i = height - 1; i >= 0; i--)
{
for(j = 0; j < width; j++)
{
memcpy(pDstData + 3 *count, pSrcData + offSet + 4 *(i * width + j), 3);
count++;
}
}
}
else
{
printf("not support bmp format\n");
return -1;
}
return 0;
}
3、代码说明
(1)BMPExtractBGR()函数功能是从BMP图像中提取出BGR数据,并将图像旋转180度,具体参数含义见注释;
(2)BMPExtractBGR()函数支持8bit、16bit、24bit、32bit的BMP图像格式;
(3)想要能直接编译成可执行程序的源码,可在我的资源里下载,该源码可直接将bmp格式转换成bgr格式
;
(3)需要完善的地方:上面的BMPExtractBGR()函数代码太多,可以将8bit、16bit、24bit、32bit的BMP格式分别拆分成单个函数,我不想花时间再调试,如果是要移植到公司的项目中,切记要拆分,不然函数代码这么长会被批斗的。
4、代码的易错点
(1)在BMP的文件头中前两个字节是’BM’,是BMP图片的特征值,本来在文件头ClBitMapFileHeader结构体中应该用short变量去表示这两个字节,但实际在文件头结构体中将bfType变量注释掉,因为2个字节是没有对齐的,如果不注释掉则会因为结构体的变量对齐导致数据错位;
(2)文件头ClBitMapFileHeader结构体中使用了long型变量,long在32位机器中是4字节,在64位机器中是8字节,上面的代码只能在32位的机器上运行,在64位机器上运行会出错;