BMP转YUV

#include <stdio.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <iostream>
#pragma pack(2)  
using namespace std;

typedef struct tagBITMAPFILEHEADER {
	uint16_t    bfType;
	uint32_t   bfSize;
	uint16_t    bfReserved1;
	uint16_t    bfReserved2;
	uint32_t   bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
	uint32_t      biSize;
	uint32_t       biWidth;
	uint32_t       biHeight;
	uint16_t       biPlanes;
	uint16_t       biBitCount;
	uint32_t      biCompression;
	uint32_t      biSizeImage;
	uint32_t       biXPelsPerMeter;
	uint32_t       biYPelsPerMeter;
	uint32_t      biClrUsed;
	uint32_t      biClrImportant;
} BITMAPINFOHEADER;

typedef struct tagRGBQUAD {
	uint8_t    rgbBlue;
	uint8_t    rgbGreen;
	uint8_t    rgbRed;
	uint8_t    rgbReserved;
} RGBQUAD;


//参数: bmp文件,该文件的位图文件头,该文件的位图信息头,输出buffer(bmp文件开头的调色板数据)
bool MakePalette(FILE* pFile, const BITMAPFILEHEADER &file_h, const BITMAPINFOHEADER &info_h, RGBQUAD *pRGB_out)
{
	//先判断是否存在调色板:有效数据开始处离文件开头的距离-位图文件头大小-位图信息头大小=调色数据大小
	if ((file_h.bfOffBits - sizeof(BITMAPFILEHEADER) - info_h.biSize) == sizeof(RGBQUAD)*pow((float)2, info_h.biBitCount))
	{
		fseek(pFile, sizeof(BITMAPFILEHEADER) + info_h.biSize, 0);
		//把调色数据放到pRGB_out中,完成make palette工作
		if (fread(pRGB_out, sizeof(RGBQUAD), (uint32_t)pow((float)2, info_h.biBitCount), pFile) != (uint32_t)pow((float)2, info_h.biBitCount))
		{
			printf("Failto read RGBQUAD!\n");
		}
		return true;
	}
	else
		return false;
}

bool WriteYUV(uint8_t*Y, uint8_t*U, uint8_t*V, uint32_t size, FILE *outFile)
{
	if (fwrite(Y, 1, size, outFile) != size)
		return false;
	if (fwrite(U, 1, size / 4, outFile) != size / 4)
		return false;
	if (fwrite(V, 1, size / 4, outFile) != size / 4)
		return false;
	return true;
}

void ReadRGB(FILE *pFile, const BITMAPFILEHEADER &file_h, const BITMAPINFOHEADER &info_h, uint8_t*rgbDataOut)
{
	uint32_t Loop, iLoop, jLoop, width, height, w, h;
	uint8_t mask, *Index_Data, *Data;

	//保证是图像大小是4字节的整数倍,具体理由见下面的注释
	if ((info_h.biWidth % 4) == 0)
		w = info_h.biWidth;
	else
		w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;
	if ((info_h.biHeight % 2) == 0)
		h = info_h.biHeight;
	else
		h = info_h.biHeight + 1;

	//若是24位,则bmp中有效数据大小是长*宽*3字节
	//若是16位,则bmp中有效数据大小是长*宽*2字节
	//若是8位,则bmp中有效数据大小是长*宽字节
	//若是4位,则bmp中有效数据大小是长*宽/2字节
	//若是2位,则bmp中有效数据大小是长*宽/4字节
	//若是1位,则bmp中有效数据大小是长*宽/8字节(这大概是为什么bmp图像的长必须是4的倍数,宽必须是2的倍数的原因吧。。。)
	width = w / 8 * info_h.biBitCount;
	height = h;

	//倒序前数据缓存区
	Index_Data = (uint8_t*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同
	//倒序后数据缓存区,用于存放bmp中的有效数
	Data = (uint8_t*)malloc(height*width);//buffer大小应该与bmp中有效数据大小相同

	//文件指针定位到有效数据起始处,读取有效数据
	fseek(pFile, file_h.bfOffBits, 0);

	if (fread(Index_Data, height*width, 1, pFile) != 1)
	{
		printf("readfile error!");
		exit(0);
	}

	//倒序存放
	for (iLoop = 0; iLoop < height; iLoop++)
		for (jLoop = 0; jLoop < width; jLoop++)
		{
			Data[iLoop*width + jLoop] = Index_Data[(height - iLoop - 1)*width

				+ jLoop];
		}

	//24位:直接把倒序后的缓存区数据复制给输出缓存区
	if (info_h.biBitCount == 24)
	{
		memcpy(rgbDataOut, Data, height*width);
		free(Index_Data);
		free(Data);
		return;
	}

	//非24位:解码生成rgb,需要调色板信息

	//生成调色板数组,数组的下标,对应bmp文件中有效数据,通过下标对应查找,便可得到该数据对应的颜色
	//debug by LiuDong:(unsignedlong long)pow(),pow前的强制类型转换不能转换成unsignedint、unsignedchar等
	//因为pow((float)2, info_h.biBitCount)最大值是2^24,unsigned char最大能表示255,unsigned int最大能表示2^32
	RGBQUAD*pRGB = (RGBQUAD *)malloc(sizeof(RGBQUAD)*(uint32_t)pow((float)2, info_h.biBitCount));
	/*一个单元代表一种颜色
	调色板实际上是一个数组,它所包含的元素与位图所具有的颜色数相同,决定于biClrUsed和biBitCount字段。
	数组中每个元素的类型是一个RGBQUAD结构。真彩色无调色板部分。
	biBitCount;位数/像素,1、2、4、8、24.假如是16位,R占5位,G占5位,B占5位,空出一位,排列组合一共有2^15种颜色,其他位数同理。*/

	//读取位图调色板数据
	if (!MakePalette(pFile, file_h, info_h, pRGB))
		printf("Nopalette!");

	//16位:移位操作,从2字节中取出RGB信息,存到3字节中
	if (info_h.biBitCount == 16)
	{
		for (Loop = 0; Loop < height * width; Loop += 2)
		{
			*rgbDataOut = (Data[Loop] & 0x1F) << 3;//B:用0001 1111取出低字节的右五位,再放到目标字节的高5位(通过右移3位),得到五位的B
			*(rgbDataOut + 1) = ((Data[Loop] & 0xE0) >> 2) + ((Data[Loop + 1] & 0x03) << 6);//G:11100000取出低字节的左三位,00000011取出高字节的右两位,合并后,放到再放到目标字节的高5位,得到五位的G
			*(rgbDataOut + 2) = (Data[Loop + 1] & 0x7C) << 1; //R:0111 1100取出高字节的中间五位,再放到目标字节的高5位,得到5位的R

			rgbDataOut += 3;
		}//RGB都各自位于字节的高5位
	}

	//1~8位:移位操作,从有限固定位中取出RGB信息,存到3字节中
	//循环次数:有效数据字节数
	for (Loop = 0; Loop < width*height; Loop++)
	{
		//根据位深设置掩膜
		switch (info_h.biBitCount)
		{
		case 1://1000 0000,1位,黑白双色图
			mask = 0x80;
			break;
		case 2://1100 0000,2位,4色图
			mask = 0xC0;
			break;
		case 4://1111 0000,4位,16色图
			mask = 0xF0;
			break;
		case 8://1000 0000,8位,256色图
			mask = 0xFF;
		default:
			mask = 0xFF;
		}

		int shiftCnt = 1;//控制mask的移位,决定取字节中哪些数据

		while (mask)//循环一次就是一个字节的解析过程
		{
			uint8_t index = mask == 0xFF ? Data[Loop] : ((Data[Loop] & mask) >> (8 - shiftCnt * info_h.biBitCount));
			//Loop代表第几个带转换的原始有效数据
			//pRGB代表调色板
			//8位:mask=1111 1111,index=data[Loop],即index为bmp中原始有效数据,直接对应调色板数组下标,得到相应颜色

			//查找调色板,取出对应BGR存入目标buffer:rgbDataOut
			*rgbDataOut = pRGB[index].rgbBlue;//B
			*(rgbDataOut + 1) = pRGB[index].rgbGreen;//G
			*(rgbDataOut + 2) = pRGB[index].rgbRed;//R

			if (info_h.biBitCount == 8)
				mask = 0;//如果是8位bmp,一次性取完一个字节的颜色数据,直接跳出循环即可
			else
				mask >>= info_h.biBitCount;//若是1位bmp,则一字节取8次数据;若是2位bmp,则一字节取4次数据;若是4位bmp,则一字节取2次数据
			//debugby LiuDong
			if (Loop == width * height - 1)
			{
				rgbDataOut = rgbDataOut + 3 - width * height * 3;
				break;
			}
			rgbDataOut += 3;
			shiftCnt++;
		}
	}
	if (Index_Data)
		free(Index_Data);
	if (Data)
		free(Data);
	if (pRGB)
		free(pRGB);
}


static void rgb_to_yuv420(uint32_t w, uint32_t h, uint8_t *rgb, uint8_t*y, uint8_t*u, uint8_t*v)
{
	uint32_t pixsize;
	uint32_t pixIndex;
	uint32_t i, j;
	pixsize = w * h;					      //图像大小

	for (i = 0; i < h; i++) {

		for (j = 0; j < w; j++) {
			pixIndex = i * 3 * w + j * 3;
			uint32_t nr = rgb[pixIndex];					//获取每个像素点得r,g,b值
			uint32_t ng = rgb[pixIndex + 1];
			uint32_t nb = rgb[pixIndex + 2];

			*y++ = (uint8_t)((66 * nr + 129 * ng + 25 * nb + 128) >> 8) + 16;

			if ((i % 2 == 1) && (j % 2 == 1)) {
				*u++ = (uint8_t)((-38 * nr - 74 * ng + 112 * nb + 128) >> 8) + 128;
				*v++ = (uint8_t)((112 * nr - 94 * ng - 18 * nb + 128) >> 8) + 128;
			}
		}
	}

	return;
}


int main(int argc, char**argv)
{
	BITMAPFILEHEADER File_header;
	BITMAPINFOHEADER Info_header;
	FILE*bmpFile;
	FILE*yuvFile;

	unsigned char*rgbBuffer, *yBuffer, *uBuffer, *vBuffer;
	//bmp路径
	bmpFile = fopen("E:\\bmpnew\\preview_0_0.bmp","rb");
	if (!bmpFile)
	{
		printf("bmpfile open error!\n");
		exit(0);
	}
	//输出yuv路径
	yuvFile = fopen("E:\\test3.yuv", "wb+");
	//读取位图文件头
	if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1)
	{
		printf("readfile header error!\n");
		exit(0);
	}

	//判断文件类型
	if (File_header.bfType != 0x4D42)
	{
		printf("Not bmp file!\n");
		exit(0);
	}

	//读取位图信息头
	if (fread(&Info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1)
	{
		printf("read info header error!\n");
		exit(0);
	}

	uint32_t width, height;
	if (Info_header.biWidth % 4 == 0)
		width = Info_header.biWidth;
	else
		//为了保证图像大小是4的倍数,每一行的像素数*每像素的位数=一行总位数
		//一行总位数必须是32位的整数倍。
		//为什么呢?这样只有才能保证一行数据量是4字节的整数倍。
		//怎么做呢?(x+31)除以32,再下取整,得到结果以后,再乘以32,就得到比原位数大且是32的倍数的数字,再除以8便得到字节数,肯定是4字节的整数倍。
		width = (Info_header.biWidth*Info_header.biBitCount + 31) / 32 * (32 / 8);

	//保证列数是偶数
	if ((Info_header.biHeight % 2) == 0)
		height = Info_header.biHeight;
	else
		height = Info_header.biHeight + 1;

	rgbBuffer = new uint8_t[height*width * 3];
	yBuffer = new uint8_t[height*width];
	uBuffer = new uint8_t[height*width / 4];
	vBuffer = new uint8_t[height*width / 4];

	ReadRGB(bmpFile, File_header, Info_header, rgbBuffer);
	rgb_to_yuv420(width, height, rgbBuffer, yBuffer, uBuffer, vBuffer);
	if (!WriteYUV(yBuffer, uBuffer, vBuffer, width*height, yuvFile))
		printf("writeYUV file failed!\n");

	if (rgbBuffer)
		delete rgbBuffer;
	if (yBuffer)
		delete yBuffer;
	if (uBuffer)
		delete uBuffer;
	if (vBuffer)
		delete vBuffer;
	
	fclose(yuvFile);
	fclose(bmpFile);

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值