BMP转JPG RGB数据经过YUV交织

本文详细介绍JPEG编码过程,包括从BMP文件读取图像数据、色彩空间转换、图像分割成8x8像素块、量化表设置、Huffman表构建等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 BMP原图:

JPG结果图:

第一步、获得JPEG编码需要的bmp数据结构并获得数据。

(1)获取BMP文件输出缓冲区信息这部分相对简单,就是从文件流读取BITMAPFILEHEADER信息与BITMAPINFOHEADER信息,获得8或16整数倍的宽与高;

它是通过GetBMBuffSize函数实现的。

  1. // 获取BMP文件输出缓冲区信息   
  2. BMBUFINFO JEnc::GetBMBuffSize(FILE* pFile)  
  3.  {  
  4.   BITMAPFILEHEADER bmHead;                  //文件头信息块    
  5.   BITMAPINFOHEADER bmInfo;                  //图像描述信息块   
  6.   BMBUFINFO   bmBuffInfo;  
  7.   UINT colSize = 0;  
  8.   UINT rowSize = 0;  
  9.   
  10.   fseek(pFile,0,SEEK_SET);                  //将读写指针指向文件头部   
  11.   fread(&bmHead,sizeof(bmHead),1,pFile);    //读取文件头信息块   
  12.   fread(&bmInfo,sizeof(bmInfo),1,pFile);    //读取位图信息块   
  13.   
  14.   // 计算填充后列数,jpeg编码要求缓冲区的高和宽为8或16的倍数   
  15.   if (bmInfo.biWidth % 8 == 0)  
  16.   {  
  17.    colSize = bmInfo.biWidth;  
  18.   }  
  19.   else  
  20.   {  
  21.    colSize = bmInfo.biWidth + 8 - (bmInfo.biWidth % 8);  
  22.   }  
  23.   
  24.   // 计算填充后行数   
  25.   if (bmInfo.biHeight % 8 == 0)  
  26.   {  
  27.    rowSize = bmInfo.biHeight;  
  28.   }  
  29.   else  
  30.   {  
  31.    rowSize = bmInfo.biHeight + 8 - (bmInfo.biHeight % 8);  
  32.   }  
  33.   
  34.   bmBuffInfo.BitCount = 24;  
  35.   bmBuffInfo.buffHeight = rowSize;          // 缓冲区高   
  36.   bmBuffInfo.buffWidth = colSize;           // 缓冲区宽   
  37.   bmBuffInfo.imgHeight = bmInfo.biHeight;   // 图像高   
  38.   bmBuffInfo.imgWidth = bmInfo.biWidth;     // 图像宽   
  39.   
  40.   return bmBuffInfo;  
  41.  }  
// 获取BMP文件输出缓冲区信息
BMBUFINFO JEnc::GetBMBuffSize(FILE* pFile)
 {
  BITMAPFILEHEADER bmHead;					//文件头信息块 
  BITMAPINFOHEADER bmInfo;					//图像描述信息块
  BMBUFINFO   bmBuffInfo;
  UINT colSize = 0;
  UINT rowSize = 0;

  fseek(pFile,0,SEEK_SET);					//将读写指针指向文件头部
  fread(&bmHead,sizeof(bmHead),1,pFile);    //读取文件头信息块
  fread(&bmInfo,sizeof(bmInfo),1,pFile);    //读取位图信息块

  // 计算填充后列数,jpeg编码要求缓冲区的高和宽为8或16的倍数
  if (bmInfo.biWidth % 8 == 0)
  {
   colSize = bmInfo.biWidth;
  }
  else
  {
   colSize = bmInfo.biWidth + 8 - (bmInfo.biWidth % 8);
  }

  // 计算填充后行数
  if (bmInfo.biHeight % 8 == 0)
  {
   rowSize = bmInfo.biHeight;
  }
  else
  {
   rowSize = bmInfo.biHeight + 8 - (bmInfo.biHeight % 8);
  }

  bmBuffInfo.BitCount = 24;
  bmBuffInfo.buffHeight = rowSize;			// 缓冲区高
  bmBuffInfo.buffWidth = colSize;			// 缓冲区宽
  bmBuffInfo.imgHeight = bmInfo.biHeight;	// 图像高
  bmBuffInfo.imgWidth = bmInfo.biWidth;		// 图像宽

  return bmBuffInfo;
 }

(2)获得图像数据。如下图所示


 

第二步、将RGB信号转换为YUV信号

从上图读出的有效数据中取出R、G、B Byte,然后根据三个分量交织得到Y、U、V分量。

以下函数中pBuf为输入的RGB有效数据,输出的结果分别存在pYBuff、pUBuff、pVBuff中。

  1. // 转换色彩空间BGR-YUV,111采样   
  2. void JEnc::BGR2YUV111(BYTE* pBuf, BYTE* pYBuff, BYTE* pUBuff, BYTE* pVBuff)  
  3. {  
  4.  DOUBLE tmpY   = 0;         //临时变量   
  5.  DOUBLE tmpU   = 0;  
  6.  DOUBLE tmpV   = 0;  
  7.  BYTE tmpB   = 0;         
  8.  BYTE tmpG   = 0;  
  9.  BYTE tmpR   = 0;  
  10.  UINT i    = 0;  
  11.  size_t elemNum = _msize(pBuf) / 3;  //缓冲长度   
  12.   
  13.  for (i = 0; i < elemNum; i++)  
  14.  {  
  15.   tmpB = pBuf[i * 3];  
  16.   tmpG = pBuf[i * 3 + 1];  
  17.   tmpR = pBuf[i * 3 + 2];  
  18.   tmpY = 0.299 * tmpR + 0.587 * tmpG + 0.114 * tmpB;  
  19.   tmpU = -0.1687 * tmpR - 0.3313 * tmpG + 0.5 * tmpB + 128;  
  20.   tmpV = 0.5 * tmpR - 0.4187 * tmpG - 0.0813 * tmpB + 128;  
  21.   //if(tmpY > 255){tmpY = 255;}     //输出限制   
  22.   //if(tmpU > 255){tmpU = 255;}   
  23.   //if(tmpV > 255){tmpV = 255;}   
  24.   //if(tmpY < 0){tmpY = 0;}     
  25.   //if(tmpU < 0){tmpU = 0;}     
  26.   //if(tmpV < 0){tmpV = 0;}   
  27.   pYBuff[i] = tmpY;           //放入输入缓冲   
  28.   pUBuff[i] = tmpU;  
  29.   pVBuff[i] = tmpV;  
  30.  }  
  31. }  
 // 转换色彩空间BGR-YUV,111采样
 void JEnc::BGR2YUV111(BYTE* pBuf, BYTE* pYBuff, BYTE* pUBuff, BYTE* pVBuff)
 {
  DOUBLE tmpY   = 0;         //临时变量
  DOUBLE tmpU   = 0;
  DOUBLE tmpV   = 0;
  BYTE tmpB   = 0;       
  BYTE tmpG   = 0;
  BYTE tmpR   = 0;
  UINT i    = 0;
  size_t elemNum = _msize(pBuf) / 3;  //缓冲长度

  for (i = 0; i < elemNum; i++)
  {
   tmpB = pBuf[i * 3];
   tmpG = pBuf[i * 3 + 1];
   tmpR = pBuf[i * 3 + 2];
   tmpY = 0.299 * tmpR + 0.587 * tmpG + 0.114 * tmpB;
   tmpU = -0.1687 * tmpR - 0.3313 * tmpG + 0.5 * tmpB + 128;
   tmpV = 0.5 * tmpR - 0.4187 * tmpG - 0.0813 * tmpB + 128;
   //if(tmpY > 255){tmpY = 255;}     //输出限制
   //if(tmpU > 255){tmpU = 255;}
   //if(tmpV > 255){tmpV = 255;}
   //if(tmpY < 0){tmpY = 0;}  
   //if(tmpU < 0){tmpU = 0;}  
   //if(tmpV < 0){tmpV = 0;}
   pYBuff[i] = tmpY;           //放入输入缓冲
   pUBuff[i] = tmpU;
   pVBuff[i] = tmpV;
  }
 }

第三步、将YUV信号分别分割为8x8的块
  1.  //********************************************************************   
  2.  // 方法名称:DivBuff    
  3.  // 最后修订日期:2003.5.3    
  4.  //   
  5.  // 参数说明:   
  6.  // lpBuf:输入缓冲,处理后的数据也存储在这里   
  7.  // width:缓冲X方向长度   
  8.  // height:缓冲Y方向长度   
  9.  // xLen:X方向切割长度   
  10.  // yLen:Y方向切割长度   
  11.  //********************************************************************   
  12. void JEnc::DivBuff(BYTE* pBuf,UINT width,UINT height,UINT xLen,UINT yLen)  
  13. {  
  14.   UINT xBufs   = width / xLen;                      //X轴方向上切割数量   
  15.   UINT yBufs   = height / yLen;                     //Y轴方向上切割数量   
  16.   UINT tmpBufLen  = xBufs * xLen * yLen;            //计算临时缓冲区长度   
  17.   BYTE* tmpBuf  = new BYTE[tmpBufLen];              //创建临时缓冲   
  18.   UINT i    = 0;                                    //临时变量   
  19.   UINT j    = 0;  
  20.   UINT k    = 0;   
  21.   UINT n    = 0;  
  22.   UINT bufOffset  = 0;                              //切割开始的偏移量   
  23.   
  24.   for (i = 0; i < yBufs; ++i)                        //循环Y方向切割数量   
  25.   {  
  26.     n = 0;                                          //复位临时缓冲区偏移量   
  27.     for (j = 0; j < xBufs; ++j)                      //循环X方向切割数量     
  28.     {     
  29.         bufOffset = yLen * xLen * i * xBufs + j * xLen; //计算单元信号块的首行偏移量     
  30.         for (k = 0; k < yLen; ++k)                   //循环块的行数   
  31.         {  
  32.             memcpy(&tmpBuf[n],&pBuf[bufOffset],xLen);      //复制一行到临时缓冲   
  33.             n += xLen;                              //计算临时缓冲区偏移量   
  34.             bufOffset += width;                     //计算输入缓冲区偏移量   
  35.         }  
  36.     }  
  37.     memcpy(&pBuf[i * tmpBufLen],tmpBuf,tmpBufLen);  //复制临时缓冲数据到输入缓冲   
  38.   }   
  39.   delete[] tmpBuf;                                  //删除临时缓冲   
  40. }   
 //********************************************************************
 // 方法名称:DivBuff 
 // 最后修订日期:2003.5.3 
 //
 // 参数说明:
 // lpBuf:输入缓冲,处理后的数据也存储在这里
 // width:缓冲X方向长度
 // height:缓冲Y方向长度
 // xLen:X方向切割长度
 // yLen:Y方向切割长度
 //********************************************************************
void JEnc::DivBuff(BYTE* pBuf,UINT width,UINT height,UINT xLen,UINT yLen)
{
  UINT xBufs   = width / xLen;						//X轴方向上切割数量
  UINT yBufs   = height / yLen;						//Y轴方向上切割数量
  UINT tmpBufLen  = xBufs * xLen * yLen;			//计算临时缓冲区长度
  BYTE* tmpBuf  = new BYTE[tmpBufLen];				//创建临时缓冲
  UINT i    = 0;									//临时变量
  UINT j    = 0;
  UINT k    = 0; 
  UINT n    = 0;
  UINT bufOffset  = 0;								//切割开始的偏移量

  for (i = 0; i < yBufs; ++i)						//循环Y方向切割数量
  {
	n = 0;											//复位临时缓冲区偏移量
	for (j = 0; j < xBufs; ++j)						//循环X方向切割数量  
	{   
		bufOffset = yLen * xLen * i * xBufs + j * xLen;	//计算单元信号块的首行偏移量  
		for (k = 0; k < yLen; ++k)					//循环块的行数
		{
			memcpy(&tmpBuf[n],&pBuf[bufOffset],xLen);      //复制一行到临时缓冲
			n += xLen;								//计算临时缓冲区偏移量
			bufOffset += width;						//计算输入缓冲区偏移量
		}
	}
	memcpy(&pBuf[i * tmpBufLen],tmpBuf,tmpBufLen);  //复制临时缓冲数据到输入缓冲
  } 
  delete[] tmpBuf;									//删除临时缓冲
} 
第四步:寝化YUV量化表
  1. // 第四步:寝化YUV量化表   
  2.  SetQuantTable(std_Y_QT, YQT, Q);           // 设置Y量化表   
  3.  SetQuantTable(std_UV_QT,UVQT, Q);          // 设置UV量化表     
  4.  InitQTForAANDCT();                     // 初始化AA&N需要的量化表   
  5.  pVLITAB=VLI_TAB + 2047;                   // 设置VLI_TAB的别名   
  6.  BuildVLITable();                           // 计算VLI表     
 // 第四步:寝化YUV量化表
  SetQuantTable(std_Y_QT, YQT, Q);			// 设置Y量化表
  SetQuantTable(std_UV_QT,UVQT, Q);			// 设置UV量化表  
  InitQTForAANDCT();						// 初始化AA&N需要的量化表
  pVLITAB=VLI_TAB + 2047;                   // 设置VLI_TAB的别名
  BuildVLITable();							// 计算VLI表   

第五步:写入各段
  1. WriteSOI();                
  2. WriteAPP0();  
  3. WriteDQT();  
  4. WriteSOF();  
  5. WriteDHT();  
  6. WriteSOS();  
  WriteSOI();              
  WriteAPP0();
  WriteDQT();
  WriteSOF();
  WriteDHT();
  WriteSOS();

第六步:计算Y/UV信号的交直分量的huffman表

这里使用标准的huffman表,并不是计算得出,缺点是文件略长,但是速度快

  1. BuildSTDHuffTab(STD_DC_Y_NRCODES,STD_DC_Y_VALUES,STD_DC_Y_HT);  
  2.  BuildSTDHuffTab(STD_AC_Y_NRCODES,STD_AC_Y_VALUES,STD_AC_Y_HT);  
  3.  BuildSTDHuffTab(STD_DC_UV_NRCODES,STD_DC_UV_VALUES,STD_DC_UV_HT);  
  4.  BuildSTDHuffTab(STD_AC_UV_NRCODES,STD_AC_UV_VALUES,STD_AC_UV_HT);  
 BuildSTDHuffTab(STD_DC_Y_NRCODES,STD_DC_Y_VALUES,STD_DC_Y_HT);
  BuildSTDHuffTab(STD_AC_Y_NRCODES,STD_AC_Y_VALUES,STD_AC_Y_HT);
  BuildSTDHuffTab(STD_DC_UV_NRCODES,STD_DC_UV_VALUES,STD_DC_UV_HT);
  BuildSTDHuffTab(STD_AC_UV_NRCODES,STD_AC_UV_VALUES,STD_AC_UV_HT);

 第七步:处理单元数据
  1. //********************************************************************   
  2. // 方法名称:ProcessData    
  3. //   
  4. // 方法说明:处理图像数据FDCT-QUANT-HUFFMAN   
  5. //   
  6. // 参数说明:   
  7. // lpYBuf:亮度Y信号输入缓冲   
  8. // lpUBuf:色差U信号输入缓冲   
  9. // lpVBuf:色差V信号输入缓冲   
  10. //********************************************************************   
  11. void JEnc::ProcessData(BYTE* lpYBuf,BYTE* lpUBuf,BYTE* lpVBuf)  
  12. {   
  13.  size_t yBufLen = _msize(lpYBuf);           //亮度Y缓冲长度   
  14.  size_t uBufLen = _msize(lpUBuf);           //色差U缓冲长度             
  15.  size_t vBufLen = _msize(lpVBuf);           //色差V缓冲长度   
  16.  FLOAT dctYBuf[DCTBLOCKSIZE];            //Y信号FDCT编码临时缓冲   
  17.  FLOAT dctUBuf[DCTBLOCKSIZE];            //U信号FDCT编码临时缓冲    
  18.  FLOAT dctVBuf[DCTBLOCKSIZE];            //V信号FDCT编码临时缓冲    
  19.  UINT mcuNum   = 0;             //存放MCU的数量    
  20.  SHORT yDC   = 0;             //Y信号的当前块的DC   
  21.  SHORT uDC   = 0;             //U信号的当前块的DC   
  22.  SHORT vDC   = 0;             //V信号的当前块的DC    
  23.  BYTE yCounter  = 0;             //YUV信号各自的写入计数器   
  24.  BYTE uCounter  = 0;  
  25.  BYTE vCounter  = 0;  
  26.  UINT i    = 0;             //临时变量                 
  27.  UINT j    = 0;                   
  28.  UINT k    = 0;  
  29.  UINT p    = 0;  
  30.  UINT m    = 0;  
  31.  UINT n    = 0;  
  32.  UINT s    = 0;   
  33.   
  34.  mcuNum = (this->buffHeight * this->buffWidth * 3)  
  35.   / (DCTBLOCKSIZE * 3);         //计算MCU的数量   
  36.   
  37.  for (p = 0;p < mcuNum; p++)        //依次生成MCU并写入   
  38.  {  
  39.   yCounter = 1;//MCUIndex[SamplingType][0];   //按采样方式初始化各信号计数器   
  40.   uCounter = 1;//MCUIndex[SamplingType][1];   
  41.   vCounter = 1;//MCUIndex[SamplingType][2];   
  42.   
  43.   for (; i < yBufLen; i += DCTBLOCKSIZE)  
  44.   {  
  45.    for (j = 0; j < DCTBLOCKSIZE; j++)  
  46.    {  
  47.     dctYBuf[j] = FLOAT(lpYBuf[i + j] - 128);  
  48.    }     
  49.    if (yCounter > 0)  
  50.    {      
  51.     --yCounter;  
  52.     ProcessDU(dctYBuf,YQT_DCT,STD_DC_Y_HT,STD_AC_Y_HT,&yDC);       
  53.    }  
  54.    else  
  55.    {  
  56.     break;  
  57.    }  
  58.   }    
  59.   //------------------------------------------------------------------     
  60.   for (; m < uBufLen; m += DCTBLOCKSIZE)  
  61.   {  
  62.    for (n = 0; n < DCTBLOCKSIZE; n++)  
  63.    {  
  64.     dctUBuf[n] = FLOAT(lpUBuf[m + n] - 128);  
  65.    }      
  66.    if (uCounter > 0)  
  67.    {      
  68.     --uCounter;  
  69.     ProcessDU(dctUBuf,UVQT_DCT,STD_DC_UV_HT,STD_AC_UV_HT,&uDC);           
  70.    }  
  71.    else  
  72.    {  
  73.     break;  
  74.    }  
  75.   }    
  76.   //-------------------------------------------------------------------     
  77.   for (; s < vBufLen; s += DCTBLOCKSIZE)  
  78.   {  
  79.    for (k = 0; k < DCTBLOCKSIZE; k++)  
  80.    {  
  81.     dctVBuf[k] = FLOAT(lpVBuf[s + k] - 128);  
  82.    }  
  83.    if (vCounter > 0)  
  84.    {  
  85.     --vCounter;  
  86.     ProcessDU(dctVBuf,UVQT_DCT,STD_DC_UV_HT,STD_AC_UV_HT,&vDC);          
  87.    }  
  88.    else  
  89.    {  
  90.     break;  
  91.    }  
  92.   }    
  93.  }   
  94. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值