#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;
}
BMP转YUV
最新推荐文章于 2022-07-08 13:07:29 发布