C语言实现BMP格式转RGB格式、YUV格式

2023年5月5日12:09:54

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位机器上运行会出错;

  • 作者:正在起飞的蜗牛
  • 原文链接:https://blog.csdn.net/weixin_42031299/article/details/115267177
    更新时间:2023年5月5日12:09:54 ,共 4928 字。