在网上看了好多解析JPEG图片的文章,多多少少都有问题,下面是我参考过的文章链接:

首先,解析的步骤

  • 1.读取文件的信息
  • 2.Huffman编码解码
  • 3.直流交流编码解析
    • 然而,读取多少个8×8矩阵才能解析出一个MCU呢?
  • 4.反量化
  • 5.反Zig-Zag变化
  • 6.反DCT变化
  • 8.YCbCr转RGB
  • 效果图

1.读取文件的信息

JPEG格式中信息是以段(数据结构)来存储的。
段的格式如下

名称 字节数 数据 说明
段标识 1 FF 每个新段的开始标识
段类型 1 类型编码(称作“标记码”)
段长度 2 包括段内容和段长度本身,不包括段标识和段类型
段内容 ≤65533字节

其余具体信息请见以下链接,我就不当复读机了。
JPEG标记的说明
格式介绍
值得注意的一点是一个字节的高位在左边,而且直流分量重置标记一共有8个,其他的格式说明在第二个链接中已经足够详细了

这些段中必须要读取的段:SOS, DHT, DQT, SOF, DRI,其他的只是锦上添花
这里面可能会出现多个SOF段,我们需要拿到这几个段中图片高度和宽度的最大值,和YCbCr的水平,垂直采样因子的最大值分别记为Hmax,Vmax,之后会用到

DRI中的开始间隔指的就是直流分量重置间隔,我们记为reset

2.Huffman编码解码

首先Huffman编码分直流表(DC)和交流表(AC),他们一般各自有两张表,具体使用哪张表是通过SOS里面的对应关系来的,一般Y对应第一张表,CbCr对应第二、三张表。

因为规定huffman编码最多16位,所以huffman编码的最大值位65535
以下代码为我的解码方式,直流交流均如此

int curPos = 16, curCode = 0;
for (int i = 0; i < 16; i++) {int count = temp[i];//count为二进制位数为i+1的个数curCode <<= 1;     //curCode为当前huffman编码数值while (count--) {    //一次循环生成一个uint16_t code=curCode;uint8_t bit=i+1;//比特位有几位 00为2位uint8_t weight=temp[curPos];//权重是按照顺序排列的,如比特位为两位的编码有两个,设为00,01,后面权重排列为1,2,则00对应1,01对应2pair<uint8_t, uint8_t> t1(bit,weight);//<code,<bit,weight>pair<uint16_t, pair<uint8_t, uint8_t>> t2(curCode,t1);table.insert(t2);curCode++;curPos++;}
}

3.直流交流编码解析

SOS段之后就是真正的图片压缩数据了,可以选择一次性读取到内存中,也可以边读数据边做后面的解析步骤,我是选择了第二种。每读取一个MCU后做一次解析(我使用的是缓存队列)。在图片编码的时候需要划分MCU(最小编码单位),每个MCU由多个8×8矩阵组成,通过编码将二维数组转换为一维的,所以当读取的数据达到了64个,就代表一个8×8的块解析完成,直到读取到0xFFD9结束

然而,读取多少个8×8矩阵才能解析出一个MCU呢?

MCU里面的8×8矩阵个数,如果从编码角度来说的话,8×8矩阵个数是Hmax*Vmax个,但是从解码角度来说,因为此时的YCbCr已经分开成为了三张表,所以8×8矩阵个数应该是三个分量的水平、垂直采样因子的乘积之和(先乘积,再求和)记为SUMmcu,所以读取一次要读取SUMmcu个8×8矩阵(此时这里面有YCbCr三种表,之后通过公式将YCbCr转换为RGB数值)

好了,到这里我们知道了要读取多少个8×8的矩阵 (实际上,因为没有反Zig-Zag编码,此时还是有64个数据的一维数组)
接下来开始解析,解析需要使用上一步解码出来的Huffman编码。
解析方式如下:
1、对于直流(差分编码)按照一个比特位来读取图片压缩数据,若在Huffman表中发现该编码,并且位数相等,则读取该编码所对应的权重,该权重代表接下来读取多少个比特位作为直流分量的值,你以为这就完了?还要加上差分矫正变量 (YCbCr每张表都有一个,所以一共有3个)
2、对于交流(游程编码),其他部分都一样(这个没有差分矫正变量),不同的地方举个例子,设读取的直流分量为0x37,则低4位(这里为7)代表接下来7个比特位是该交流分量的值,而高4位(此处为3)代表此交流分量前有3个0 (这里就不用加上前面的了)。注意直流交流使用的Huffman表不同
3、接下来就是循环读取交流分量了,那么什么时候退出呢?
有两个条件,只要达成一个就可以退出

  1. 读取了63个交流分量
  2. 交流分量的权值为0,此位后面全是0

对于根据权重所读出来的值(不区分直流交流),对于最高位(最左边)若为0则是负数,否则为正数,判断代码如下,curValue为读取的值,curValueLength为读取的值有多少位

curValue = (curValue >= pow(2, curValueLength - 1) ? curValue : curValue - pow(2, curValueLength) + 1);

这里面还有两个坑(若DRI读出来的直流分量重置间隔reset为0,不用管这步)

  1. 假设reset为332(这是我图片的间隔),就是隔了332个MCU(也就是332×SUMmcu个8×8的矩阵),需要将
    所有差分矫正变量全部置为0,并且当这332个MCU读取完后,你要读取两个字节(这两个字节是一个段),这两个字节应该正好是0xFF 0xD0~0xD7,并且D0到D7是按顺序出现的,例如,上一个是0xFFD0那么下一个肯定是0xD1,到D7后下一个是D0,若对不上那就有问题了。还有,这读出来的两个字节不是图片的压缩数据不需要解码
  2. 若读取到了0xFF00则00忽略

到此,我们得到了一个有64个元素的一维数组

4.反量化

我们用之前读出来的量化表(也是64个元素的,你说巧不巧嘿嘿)与上面解码得到的元素对应项相乘,反量化完成!!!

5.反Zig-Zag变化

编码方式如下

我使用的模拟法,将一维数组转为8×8矩阵
函数如下,写的不好

double** UnZigZag(int* originArray){double** table=new double*[ROW];for(int i=0;i<ROW;i++) table[i]=new double[COL];int cur=0,x=0,y=0;bool flag = true;//true是右上 false是左下while (cur < 64) {table[y][x] = originArray[cur++];if (flag) { x++; y--; }else { x--; y++; }if (x < 0 || y < 0 || x>7 || y>7) flag = !flag;if (x < 0 && y>7) { x = 1; y = 7; }if (x < 0) x = 0;else if (x > 7) { x = 7; y += 2; }if (y < 0) y = 0;else if (y > 7) { y = 7; x += 2; }}return table;

也可以使用另外一种方法,手动记录一个数组,将位置写好,转换只需要4行代码

6.反DCT变化

那个公式太慢了,有这个公式的简化版本,公式可以化为矩阵乘法,只需要一个转换矩阵
矩阵我是用下面的代码计算得到的

double** JPEGData::createDCTAndIDCTArray(int row){double** res=new double*[row];for(int i=0;i<row;i++) res[i]=new double[row];// cout<<endl;for(int i=0;i<row;i++){for(int j=0;j<row;j++){double t=0;if(i==0) t=sqrt(1.0/row);else t=sqrt(2.0/row);res[i][j]=t*cos(M_PI*(j+0.5)*i/row);// cout<<res[i][j]<<" ";}// cout<<endl;}return res;
}
//设返回的矩阵为A
//DCT原理 Y=A*X*A'(X为正变换输入,Y是输出)
//IDCT原理X=A'*Y*A(Y是逆变换输入,X是输出'是转置)
void JPEGData::IDCT(double** originMatrix){vector<vector<double>> temp(ROW,vector<double>(COL,0));for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=DCTAndIDCTArray[k][i]*originMatrix[k][j];}temp[i][j]=sum;}}for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=temp[i][k]*DCTAndIDCTArray[k][j];}originMatrix[i][j]=sum;}}
}
void JPEGData::DCT(double** originMatrix){vector<vector<double>> temp(ROW,vector<double>(COL,0));for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=DCTAndIDCTArray[i][k]*originMatrix[k][j];}temp[i][j]=sum;}}for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=temp[i][k]*DCTAndIDCTArray[j][k];}originMatrix[i][j]=sum;}}
}

8.YCbCr转RGB

公式如下,这个是真好使
R=128+y+1.402 cr
G=128+y-0.71414
cr-0.34414*cb
B=128+y+1.772 *cb

struct RGB{uint8_t red;uint8_t green;uint8_t blue;
};
RGB** JPEGData::YCbCrToRGB(const int* YUV){RGB **res = new RGB *[ROW * max_v_samp_factor];int matrixCount = YUV[0] + YUV[1] + YUV[2];int crCount = 0, cbCount = 0;//1=Y, 2=Cb, 3=Cr//式子 scale*x,scale*ydouble cb_h_samp_scale=component[1].h_samp_factor*1.0/max_h_samp_factor,cb_v_samp_scale=component[1].v_samp_factor*1.0/max_v_samp_factor,cr_h_samp_scale=component[2].h_samp_factor*1.0/max_h_samp_factor,cr_v_samp_scale=component[2].v_samp_factor*1.0/max_v_samp_factor;for (int i = 0; i < ROW * max_v_samp_factor; i++)res[i] = new RGB[COL * max_h_samp_factor];//此处直接生成rgb值//注意,此处YCbCr的对应关系与采样因子有关//这个ycbcr存的是一个MCU,假设YUV为411,那么ycbcr有6个//这种方式转换不了YUV为420的,因为数组越界了,不过可以加个判断,我懒得改了// cout<<endl;for(int j=0;j<ROW * max_v_samp_factor;j++){for(int k=0;k<COL * max_h_samp_factor;k++){int yPos = (j / ROW) * component[0].h_samp_factor + (k / COL);int cbPos = YUV[0] + (int)((k / ROW) * cb_v_samp_scale) + (int)((j / COL) * cb_h_samp_scale);int crPos = YUV[0] + YUV[1] + (int)((k / ROW) * cr_v_samp_scale) + (int)((j / COL) * cr_h_samp_scale);double y = ycbcr[yPos][j % ROW][k % COL];double cb = ycbcr[cbPos][(int)(j * cb_v_samp_scale)][(int)(k * cb_h_samp_scale)];double cr = ycbcr[crPos][(int)(j * cr_v_samp_scale)][(int)(k * cr_h_samp_scale)];res[j][k].red   =RGBValueLimit(128+y+1.402  *cr);res[j][k].green =RGBValueLimit(128+y-0.71414*cr-0.34414*cb);res[j][k].blue  =RGBValueLimit(128+y+1.772  *cb);// 输出当前选择的矩阵//cout<<dec<<yPos<<" "<<cbPos<<" "<<crPos<<" ";// cout<<hex<<setw(2)<<setfill('0')<<(int)res[j][k].red//          <<setw(2)<<setfill('0')<<(int)res[j][k].green//          <<setw(2)<<setfill('0')<<(int)res[j][k].blue<<" ";}// cout<<endl;}// cout<<endl;return res;
}

效果图

这个是JPEG

这是位图

最最后,如何把图片显示出来呢?,我将信息转换为位图,就能看见了。
下面源码附上
Image.h

#pragma once
#define _USE_MATH_DEFINES
#include <cmath>
#include <fstream>
#include <stdint.h>
#include <utility>
#ifndef _IMAGE_
#define _IMAGE_#include "Util.h"
#include <string>
#include <vector>
#include <iostream>
using namespace std;NAME_SPACE_START(myUtil)#define ROW 8
#define COL 8
#define HUFFMAN_DECODE_DEQUE_CACHE 64//单位:位
// #define _DEBUG_
// #define _DEBUGOUT_
#define FREE_VECTOR_LP(vectorName) \for(auto item : vectorName){    \for(int i=0;i<ROW;i++)\delete [] item[i];\delete [] item;    \}\vectorName.clear();//释放二维指针
#define FREE_LP_2(lpName,row) \for(int i=0;i<row;i++){\delete [] lpName[i];\}\delete [] lpName;//段类型
enum JPEGPType{SOF0    = 0xC0,     //帧开始SOF1    = 0xC1,     //帧开始SOF2    = 0xC2,     //帧开始DHT     = 0xC4,     //哈夫曼表SOI     = 0xD8,     //文件头EOI     = 0xD9,     //文件尾SOS     = 0xDA,     //扫描行开始DQT     = 0xDB,     //定义量化表DRI     = 0xDD,     //定义重新开始间隔APP0    = 0xE0,     //定义交换格式和图像识别信息APP1    = 0xE1,     //定义交换格式和图像识别信息APP2    = 0xE2,     //定义交换格式和图像识别信息COM     = 0xFE      //注释
};//将一维数组变为二维数组
double** UnZigZag(int* originArray);struct RGB{uint8_t red;uint8_t green;uint8_t blue;
};//SOS
class JPEGScan{public://componentId,<DC,AC>map<uint8_t,pair<uint8_t,uint8_t>> componentHuffmanMap;bool Init(fstream& file,uint16_t len);
};//APP
class JPEGInfo{public:uint16_t version;};
//DHT
class JPEGHuffmanCode{public:using iterator = map<uint16_t,pair<uint8_t,uint8_t>>::iterator;//<code,<bit,weight>map<uint16_t,pair<uint8_t,uint8_t>> table;//init huffman tablebool Init(fstream& file,uint16_t len);//find-true not find-falsebool findKey(const uint16_t& code,const uint8_t& bit,iterator& it);
};
//DQT
//quality table
class JPEGQuality{public:uint8_t precision;uint8_t id;vector<uint16_t> table;bool Init(fstream& file,uint16_t len);
};//SOF segment
class JPEGComponent{public://1=Y, 2=Cb, 3=Cr, 4=I, 5=Quint8_t colorId;uint8_t h_samp_factor;uint8_t v_samp_factor;uint8_t qualityId;bool Init(fstream& file,uint16_t len);
};class JPEGData{int max_h_samp_factor;//行MCUint max_v_samp_factor;//列MCUint width;int height;int precision;bool isYUV411=false;bool isYUV422=false;bool isYUV111=false;uint8_t curDRI=0;//当前重置直流分量标识,这里只取个位方便计算uint16_t resetInterval=0;//单位是MCUint preDCValue[3]={0};  //用于直流差分矫正//量化表vector<JPEGQuality> quality;//huffman码表vector<JPEGHuffmanCode> dc_huffman;vector<JPEGHuffmanCode> ac_huffman;//component每个颜色分量vector<JPEGComponent> component;JPEGScan scan;//vector<int**> deHuffman;vector<double**> ycbcr;vector<RGB**> rgb;double** DCTAndIDCTArray;streampos pos;bool EOI{false};
public:JPEGData():max_h_samp_factor(0),max_v_samp_factor(0),width(0),height(0),precision(0){DCTAndIDCTArray=createDCTAndIDCTArray(ROW);}~JPEGData(){FREE_LP_2(DCTAndIDCTArray,ROW-1)// FREE_LP_2(DCTArray,ROW-1)// FREE_LP_2(IDCTArray,ROW-1)FREE_VECTOR_LP(rgb)}bool readJPEG(const char* filePath);int getWidth() const {return width;}int getHeight() const {return height;}vector<RGB**> getRGB() const {return rgb;}int getMaxHSampFactor() const {return max_h_samp_factor;}int getMaxVSampFactor() const {return max_v_samp_factor;}double** createDCTAndIDCTArray(int row);//double** createIDCTArray(int row);void DCT(double** originMatrix);void IDCT(double** originMatrix);
protected:bool readSOF(fstream& file,uint16_t len);bool readData(fstream& file);bool huffmanDecode(fstream& file);void deQuality(double** originMatrix,int qualityID);//隔行正负纠正void PAndNCorrect(double** originMatrix);RGB** YCbCrToRGB(const int* YUV);//标记位检查 是否结束,是否重置直流矫正数值,返回要添加的数值string FlagCkeck(fstream& file,int byteInfo);uint16_t ReadByte(fstream& file,int len);uint16_t findHuffmanCodeByBit(fstream& file,int& length,int& pos,string& deque,int curValue,int& curValLen);
};NAME_SPACE_END()#endif //!_IMAGE_

Image.cpp

#include "Image.h"
#include "Util.h"
#include <algorithm>
#include <cmath>
#include <exception>
#include <fstream>
#include <stdint.h>
#include <bitset>
#include <stdlib.h>
#include <utility>
#include <cstring>
#include <vector>
#include <iomanip>NAME_SPACE_START(myUtil)int RGBValueLimit(double input){if(input<0) return 0;else if(input>255) return 255;// 四舍五入、取整均可// return (int)(input);return round(input);
}void print(double** originMatrix){cout<<endl;for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){cout<<originMatrix[i][j]<<" ";}cout<<endl;}cout<<endl;
}double** UnZigZag(int* originArray){double** table=new double*[ROW];for(int i=0;i<ROW;i++) table[i]=new double[COL];int cur=0,x=0,y=0;bool flag = true;//true是右上 false是左下while (cur < 64) {table[y][x] = originArray[cur++];if (flag) { x++; y--; }else { x--; y++; }if (x < 0 || y < 0 || x>7 || y>7) flag = !flag;if (x < 0 && y>7) { x = 1; y = 7; }if (x < 0) x = 0;else if (x > 7) { x = 7; y += 2; }if (y < 0) y = 0;else if (y > 7) { y = 7; x += 2; }}return table;
}bool JPEGScan::Init(fstream &file, uint16_t len){try {uint8_t count=file.get();len--;while(count--){uint8_t componentId=file.get();uint8_t table=file.get();uint8_t dcId=table>>4;uint8_t acId=table&0x0f;pair<uint8_t, uint8_t> info1(dcId,acId);pair<uint8_t, pair<uint8_t, uint8_t>> info2(componentId,info1);componentHuffmanMap.insert(info2);}} catch (...) {return false;}return true;
}bool JPEGHuffmanCode::Init(fstream &file, uint16_t len){try{vector<uint8_t> temp;while(len--){int info=file.get();temp.push_back(info);}int curPos = 16, curCode = 0;for (int i = 0; i < 16; i++) {int count = temp[i];curCode <<= 1;while (count--) {uint16_t code=curCode;uint8_t bit=i+1;uint8_t weight=temp[curPos];pair<uint8_t, uint8_t> t1(bit,weight);pair<uint16_t, pair<uint8_t, uint8_t>> t2(curCode,t1);table.insert(t2);curCode++;curPos++;}}}catch(...){return false;}return true;
}bool JPEGHuffmanCode::findKey(const uint16_t& code,const uint8_t& bit,iterator& it)
{it=table.find(code);if(it==table.end()) return true;return it->second.first!=bit;
}
bool JPEGQuality::Init(fstream &file, uint16_t len){try{int info=file.get();precision=info>>4;id=info&0x0f;len--;while(len--){int t=file.get();table.push_back(t);}}catch(...){return false;}return true;
}bool JPEGComponent::Init(fstream &file, uint16_t len){try {int info1=file.get();int info2=file.get();int info3=file.get();colorId=info1;h_samp_factor=info2>>4;v_samp_factor=info2&0x0f;qualityId=info3;} catch (...) {return false;}return true;
}bool JPEGData::readJPEG(const char *filePath){fstream file(filePath,ios::in|ios::binary);if(file.fail()) return false;file.seekg(0,ios::end);pos = file.tellg();file.seekg(2,ios::beg);dc_huffman.resize(2);ac_huffman.resize(2);try {//do read data through using other methoduint16_t pLen=0;uint16_t pMarker=0xFF;uint16_t pType=0x00;while(!file.eof()){pMarker=file.get();pType=file.get();if(pType==EOI) break;pLen=file.get();pLen=(pLen<<8)+file.get();// cout<<hex<<pMarker<<" "<<pType<<" "<<pLen<<endl;if(pMarker!=0xFF) throw exception();bool flag=true;switch (pType) {case SOF0:case SOF1:case SOF2:{flag=readSOF(file, pLen-2);break;}case DHT:{JPEGHuffmanCode huf;int info=file.get();int tableId=info&0x0f;// cout<<hex<<info<<" ";flag=huf.Init(file, pLen-3);if((info>>4)&1) ac_huffman[tableId]=huf;else dc_huffman[tableId]=huf;break;}//case SOI://case EOI:case SOS:{flag=scan.Init(file, pLen-2);int count=3;// cout<<endl;while(count--) file.get();// cout<<endl;//正式读取数据if(!flag) break;flag=readData(file);break;}case DQT:{JPEGQuality q;flag=q.Init(file, pLen-2);quality.push_back(q);break;}case DRI:{resetInterval=ReadByte(file, 2);break;}case APP0:case APP1:case APP2:case COM:{pLen-=2;while(pLen--){file.get();}break;}default:pLen-=2;while(pLen--){file.get();}break;}if(!flag) throw exception();// cout<<endl;}} catch (...) {file.close();return false;}file.close();return true;
}bool JPEGData::readSOF(fstream& file,uint16_t len){try {precision=file.get();height=max(height,(int)ReadByte(file, 2));width=max(width,(int)ReadByte(file, 2));int count=ReadByte(file, 1);if(count!=3) return false;len-=6;component.resize(count);for(int i=0;i<count;i++){JPEGComponent com;com.Init(file, len/3);max_h_samp_factor=max(max_h_samp_factor,(int)com.h_samp_factor);max_v_samp_factor=max(max_v_samp_factor,(int)com.v_samp_factor);component[i]=com;}if((component[0].h_samp_factor*component[0].v_samp_factor)/(component[1].h_samp_factor*component[1].v_samp_factor)==4){isYUV411=true;}else if((component[0].h_samp_factor*component[0].v_samp_factor)/(component[1].h_samp_factor*component[1].v_samp_factor)==2){isYUV422=true;}else if((component[0].h_samp_factor*component[0].v_samp_factor)/(component[1].h_samp_factor*component[1].v_samp_factor)==1){isYUV111=true;}} catch (...) {return false;}return true;
}bool JPEGData::readData(fstream& file){bool flag=true;try{//使用huffman表来解出RLE编码,接着转回长度为64的矩阵flag=huffmanDecode(file);if(!flag) return false;//反量化,即上面的64矩阵×对应位置的量化表//flag=deQuantity();//if(!flag) return false;//反zig-zag排序//flag=deZSort();//if(!flag) return false;//反离散余弦变换//if(!flag) return false;//YCbCr转RGB//if(!flag) return false;}catch(...){return false;}return true;
}bool JPEGData::huffmanDecode(fstream& file){try {//原图像一个MCU有多少8*8矩阵(此时是YCbCr还没有分开)//int MCUBlockCount=max_h_samp_factor*max_v_samp_factor;//顺序YCbCrint YUV[]={component[0].h_samp_factor*component[0].v_samp_factor,component[1].h_samp_factor*component[1].v_samp_factor,component[2].h_samp_factor*component[2].v_samp_factor};int curMCUCount=1;      //当前MCU数量int curValueLength=0;   //当前值有多少位int curValue=0;         //当前的值int curBitDequeLength=8;//当前curBitDeque长度int curBitPos=0;        //当前string读取到第几位int restart=resetInterval;//直流分量重置string curBitDeque="";  //用来存储读出来的2进制数//一次循环解析一个MCUcurBitDeque.append(bitset<8>(file.get()).to_string());curBitDequeLength=8;// cout<<curBitDeque;while(!EOI||(pos-file.tellg())!=2){// cout<<endl;int count=1;for(int i=0;i<3;i++){for(int j=0;j<YUV[i];j++){// cout<<count++<<" ";int matrix[64]={0};int valCount=0;uint8_t dcID = scan.componentHuffmanMap[component[i].colorId].first;uint8_t acID = scan.componentHuffmanMap[component[i].colorId].second;int qualityId=component[i].qualityId;if(qualityId>=quality.size()) qualityId=0;// cout<<endl;while(valCount<64){//用curBitDeque和curBit去找权重,curValue作为当前键值JPEGHuffmanCode::iterator it;JPEGHuffmanCode &huffman = valCount==0?dc_huffman[dcID]:ac_huffman[acID];while(curValueLength<=16&&huffman.findKey(curValue,curValueLength,it)){curValue=findHuffmanCodeByBit(file,curBitDequeLength,curBitPos,curBitDeque,curValue,curValueLength);}if(curValueLength>16) return true;#ifdef _DEBUGOUT_//cout<<dec<<" "<<curBitPos<<" "<<curBitDequeLength<<" ";cout<<"key="<<hex<<curValue<<" len="<<curValueLength<<endl;#endif//已经找到了权重和位宽uint8_t weight,zeroCount=0;if(valCount==0) weight = it->second.second;else { weight = it->second.second & 0x0f; zeroCount = it->second.second >> 4;}curValue=0;//这里变为dc或ac值curValueLength=0;if(valCount!=0&&weight==0&&zeroCount==0) break;//后面全是0// 读取真正的值for(int k=0;k<weight;k++){curValue=findHuffmanCodeByBit(file,curBitDequeLength,curBitPos,curBitDeque,curValue,curValueLength);}curValue = (curValue >= pow(2, curValueLength - 1) ? curValue : curValue - pow(2, curValueLength) + 1);// cout<<curValue<<endl;int writeValue=valCount==0?(preDCValue[i]+=curValue):curValue;valCount+=zeroCount;writeValue*=quality[qualityId].table[valCount];//反量化matrix[valCount]=writeValue;curValue=0;curValueLength=0;valCount++;}double** tempZ = UnZigZag(matrix);//反zig-zag编码//反量化,在反zig-zag编码前后差别,前面:RGB数值与编辑器比偏小,反之偏大,这也与最后取整时的方式有关// deQuality(tempZ,qualityId);// print(tempZ);//隔行正负纠正,有的博客说了,但是没感觉有啥帮助// PAndNCorrect(tempZ);IDCT(tempZ);                    //dct逆变换ycbcr.push_back(tempZ);#ifdef _DEBUG_for(int k=0;k<ROW;k++){for(int l=0;l<COL;l++){cout.width(3);cout<<dec<<tempZ[k][j]<<" ";}cout<<endl;}cout<<endl;#endif}}// if(count!=6){//     cout<<" ";// }RGB** lpRGB = YCbCrToRGB(YUV);FREE_VECTOR_LP(ycbcr)rgb.push_back(lpRGB);// 直流分量重置间隔不为0的if(restart>0){resetInterval--;if(resetInterval==0){resetInterval=restart;curDRI+=1;curDRI&=0x7;//需要在此处读取两字节信息,看是否为重置标识file.get();if(file.get()==0xD9) EOI=true;curBitPos=curBitDequeLength;preDCValue[0]=0;preDCValue[1]=0;preDCValue[2]=0;}}// cout<<"curMCUCount="<<dec<<curMCUCount++<<" pos="<<pos<<"/"<<file.tellg()<<" "<<file.tellg()*100.0/pos<<"%\n";if(pos-file.tellg()==2) break;}cout<<"\nsuccessfully\n";} catch (exception ex) {cout<<ex.what();return false;}return true;
}RGB** JPEGData::YCbCrToRGB(const int* YUV){RGB **res = new RGB *[ROW * max_v_samp_factor];int matrixCount = YUV[0] + YUV[1] + YUV[2];int crCount = 0, cbCount = 0;//1=Y, 2=Cb, 3=Cr//式子 scale*x,scale*ydouble cb_h_samp_scale=component[1].h_samp_factor*1.0/max_h_samp_factor,cb_v_samp_scale=component[1].v_samp_factor*1.0/max_v_samp_factor,cr_h_samp_scale=component[2].h_samp_factor*1.0/max_h_samp_factor,cr_v_samp_scale=component[2].v_samp_factor*1.0/max_v_samp_factor;for (int i = 0; i < ROW * max_v_samp_factor; i++)res[i] = new RGB[COL * max_h_samp_factor];//此处直接生成rgb值//注意,此处YCbCr的对应关系与采样因子有关// cout<<endl;for(int j=0;j<ROW * max_v_samp_factor;j++){for(int k=0;k<COL * max_h_samp_factor;k++){int yPos = (j / ROW) * component[0].h_samp_factor + (k / COL);int cbPos = YUV[0] + (int)((k / ROW) * cb_v_samp_scale) + (int)((j / COL) * cb_h_samp_scale);int crPos = YUV[0] + YUV[1] + (int)((k / ROW) * cr_v_samp_scale) + (int)((j / COL) * cr_h_samp_scale);double y = ycbcr[yPos][j % ROW][k % COL];double cb = ycbcr[cbPos][(int)(j * cb_v_samp_scale)][(int)(k * cb_h_samp_scale)];double cr = ycbcr[crPos][(int)(j * cr_v_samp_scale)][(int)(k * cr_h_samp_scale)];res[j][k].red   =RGBValueLimit(128+y+1.402  *cr);res[j][k].green =RGBValueLimit(128+y-0.71414*cr-0.34414*cb);res[j][k].blue  =RGBValueLimit(128+y+1.772  *cb);// 输出当前选择的矩阵//cout<<dec<<yPos<<" "<<cbPos<<" "<<crPos<<" ";// cout<<hex<<setw(2)<<setfill('0')<<(int)res[j][k].red//          <<setw(2)<<setfill('0')<<(int)res[j][k].green//          <<setw(2)<<setfill('0')<<(int)res[j][k].blue<<" ";}// cout<<endl;}// cout<<endl;return res;
}double** JPEGData::createDCTAndIDCTArray(int row){double** res=new double*[row];for(int i=0;i<row;i++) res[i]=new double[row];// cout<<endl;for(int i=0;i<row;i++){for(int j=0;j<row;j++){double t=0;if(i==0) t=sqrt(1.0/row);else t=sqrt(2.0/row);res[i][j]=t*cos(M_PI*(j+0.5)*i/row);// cout<<res[i][j]<<" ";}// cout<<endl;}return res;
}void JPEGData::DCT(double** originMatrix){//原理 Y=A*X*A'vector<vector<double>> temp(ROW,vector<double>(COL,0));for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=DCTAndIDCTArray[i][k]*originMatrix[k][j];}temp[i][j]=sum;}}for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=temp[i][k]*DCTAndIDCTArray[j][k];}originMatrix[i][j]=sum;}}
}void JPEGData::IDCT(double** originMatrix){//原理X=A'*Y*Avector<vector<double>> temp(ROW,vector<double>(COL,0));for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=DCTAndIDCTArray[k][i]*originMatrix[k][j];}temp[i][j]=sum;}}for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){double sum=0;for(int k=0;k<COL;k++){sum+=temp[i][k]*DCTAndIDCTArray[k][j];}originMatrix[i][j]=sum;}}
}void JPEGData::deQuality(double** originMatrix,int qualityID){for(int i=0;i<ROW;i++){for(int j=0;j<COL;j++){originMatrix[i][j]*=quality[qualityID].table[i*ROW+j];}}
}void JPEGData::PAndNCorrect(double** originMatrix){for(int i=0;i<ROW;i++)if(i%2==1)for(int j=0;j<COL;j++) originMatrix[i][j]=-originMatrix[i][j];
}string JPEGData::FlagCkeck(fstream& file,int byteInfo){if(byteInfo==0xff){uint8_t info=file.get();string res=bitset<8>(0xFF).to_string();if(info==0xD9) {EOI=true;return "false";}else if(info==0x00) return res;return res + bitset<8>(info).to_string();}return bitset<8>(byteInfo).to_string();
}uint16_t JPEGData::ReadByte(fstream& file,int len){uint16_t res=file.get();if(len!=1){res=(res<<8)+(uint8_t)file.get();}return res;
}uint16_t JPEGData::findHuffmanCodeByBit(fstream& file,int& length,int& pos,string& deque,int curValue,int& curValLen){if(pos==length&&length>=HUFFMAN_DECODE_DEQUE_CACHE){//达到最大缓存deque = deque.substr(pos);int info=file.get();string res=FlagCkeck(file,info);string str=bitset<8>(info).to_string();if(res=="false") res=bitset<8>(file.get()).to_string();deque.append(res);length = deque.length();pos = 0;}else if(length==0 || pos>=length){if(length==0){deque="";pos=0;}int info=file.get();string res=FlagCkeck(file,info);string str=bitset<8>(info).to_string();if(res=="false") res=bitset<8>(file.get()).to_string();deque.append(res);length+=8;}curValue = (curValue << 1) + (uint8_t)(deque.at(pos++) - '0');curValLen++;return curValue;
}
NAME_SPACE_END()

BmpEncoder.h


#pragma once
#include <stdio.h>
#include <iostream>
#include "Image.h"
using namespace myUtil;/* Bitmap Header, 54 Bytes  */
static
unsigned char BmpHeader[54] =
{0x42, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x01, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,0x00, 0x00, 0x60, 0xCD, 0x04, 0x00, 0x23, 0x2E, 0x00, 0x00, 0x23, 0x2E, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};void SetBitmapInfo(unsigned int size, int height, int width)
{for (int i = 0; i < 4; i++){// size of image ( header + data )BmpHeader[2 + i] = size & 0xff;size >>= 8;// width of imageBmpHeader[18 + i] = width & 0xff;width >>= 8;// height of imageBmpHeader[22  + i] = height & 0xff;height >>= 8;}
}/* BGR format 这是我粘来的改了映射部分代码 */
unsigned char *Encoder(const vector<RGB**>& buf, int height, int width, int mcu_height, int mcu_width, int &size)
{uint8_t *bitmap = nullptr;int rowSize = (24 * width + 31) / 32 * 4;// compute the size of total bytes of imagesize = rowSize * height + 54; // data size + header sizebitmap = new uint8_t [ size ];// set the header infoSetBitmapInfo(size, height, width);// fill the header areafor (int i = 0; i < 54; i++){bitmap[i] = BmpHeader[i];}// fill the data areafor (int i = 0; i < height; i++){// compute the offset of destination bitmap and source imageint idx = height - 1 - i;int offsetDst = idx * rowSize + 54; // 54 means the header length// int offsetSrc = i * width;int offsetHeight = (int)floor(i*1.0/mcu_height)*(int)ceil(width*1.0/mcu_width);// fill datafor (int j = 0; j < width * 3; j++){int pos=(j/3)/mcu_width+offsetHeight;if(pos>=buf.size()) pos=buf.size()-1;RGB temp=buf[pos][i%mcu_height][(j/3)%mcu_height];if(j%3==0) bitmap[offsetDst + j] = temp.blue;else if(j%3==1) bitmap[offsetDst + j] = temp.green;else if(j%3==2) bitmap[offsetDst + j] = temp.red;// cout<<dec<<pos<<" ";}// fill 0x0, this part can be ignoredfor (int j = width * 3; j < rowSize; j++){bitmap[offsetDst +j] = 0x0;}}return bitmap;
}/* Save to file */
void Write(const char *fileName, uint8_t *buf, int &size)
{FILE *fp = fopen(fileName, "wb+");fwrite(buf, 1, size, fp);fclose(fp);
}

主程序

#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <locale>
#include <sstream>
#include <stdlib.h>
#include "Image.h"
#include "BmpEncoder.h"
using namespace std;
using namespace myUtil;// void print(double** input){//  cout<<endl;
//  for(int i=0;i<8;i++){//      for(int j=0;j<8;j++){//          cout<<input[i][j]<<" ";
//      }
//      cout<<endl;
//  }
//  cout<<endl;
// }int main(){string str="../img/Image/3.jpg";JPEGData data;clock_t startTime=clock();data.readJPEG(str.c_str());int size;unsigned char *bitmap = Encoder(data.getRGB(), data.getHeight(), data.getWidth(),8*data.getMaxHSampFactor(),8*data.getMaxVSampFactor(), size);Write("out.bmp", bitmap, size);cout<<dec<<clock()-startTime<<"ms"<<endl;// DCT正反变换测试// JPEGData data;// double** arr=new double*[8];// for(int i=0;i<8;i++){//     arr[i]=new double[8];//    for(int j=0;j<8;j++){//       arr[i][j]=(int)(rand()%100);//     }// }// print(arr);// data.DCT(arr);// print(arr);// data.IDCT(arr);// print(arr);// FREE_LP_2(arr,8)return 0;
}

项目环境gcc 7.3.0 工具CMake,源码链接

C++实现JPEG格式图片解析(附代码)相关推荐

  1. Freemarker下载Word文档(文字+图片+表格)Idea示例 (附word展示图片异常解决方案)(附JPEG格式图片通过imageio.read方法读取为null解决方案)

    流程:拿到word模板 转为ftl格式并填充占位符参数,调用java代码填充参数即可 (文末附word打开图片显示异常,wps打开却显示正常的解决方案) (文末附Jpeg格式图片获取为null解决方案 ...

  2. Python实现base64编码文件转化为jpg/png/jpeg/格式图片

    Python实现base64编码文件转化为jpg/png/jpeg/格式图片 这个base64文件是图片转化的才行,不是随便找个base64文件都行的 base64图片文件qiaoba.py /9j/ ...

  3. 查看BMP格式图片的十六进制代码

    查看BMP格式图片的十六进制代码 1.下载notepad++: 下载地址 2.在notepad++中安装Hex-Editor插件: 打开notepad++ --> 点击插件按钮 --> 插 ...

  4. ajax js图片上传到php,Ajax上传并预览图片(附代码)

    这次给大家带来Ajax上传并预览图片(附代码),Ajax上传并预览图片的注意事项有哪些,下面就是实战案例,一起来看一下. 1. 直接上最简单的 一种 ajax 异步上传图片,并预览 html: 图片上 ...

  5. Java【冒泡排序】算法, 大白话式图文解析(附代码)

    文章目录 前言 一.排序相关概念 1, 什么是排序 2, 什么是排序的稳定性 3, 七大排序分类 二.冒泡排序 1, 图文解析 2, 代码实现 3, 冒泡排序的优化 三.性能分析 四.七大排序算法总体 ...

  6. 把多张 PNG 图片拼在一起合成一张 RAW 格式的图片(附代码)

    把多张 PNG 图片拼在一起合成一张 RAW 格式的图片设定好输入和输出的路径即可 代码如下 import numpy import os # import argparse from PIL imp ...

  7. 制作自己的COCO格式数据集,附代码!

    最近做了一个细胞检测的练习项目.之前的思路是参考其他大神的代码,后来发现其他人的代码有很多自定义的内容,包括读取的数据格式等等,小白表示看不懂所以改变思路,用最简单的方法--选择mmdetection ...

  8. Winform中实现对照片添加文字和图片水印(附代码下载)

    场景 项目运行效果 注: 博客主页: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 新建 ...

  9. android word转html标签,如何将Word转换为网页html格式的方法(附代码清理方法)

    Word是我们常用的的办公软件,广泛被运用,那么我们怎么把Word转换为网页html格式? 需要软件: word2003 或 wps 个人建议用wps更方便,word生成的图片容易重复 editplu ...

最新文章

  1. pytorch如何计算导数_Pytorch的自动求导机制与使用方法(一)
  2. ios pusher使用_如何使用JavaScript和Pusher构建实时图
  3. mysql之desc 与asc
  4. cacti监控mysql
  5. android通讯录加密,Android获取通讯录并上传(包含通讯录加密)
  6. 用puttygen工具把私钥id_rsa转换成公钥id_rsa.ppk
  7. HP Proliant DL360 G9使用业务网卡登录ILO管理
  8. Python-Level2-day11:TCP客户端/服务端传输(循环模型之短连接与长连接形态);TCP粘包问题;与UDP对比;数据传输过程原理;
  9. Java使用BufferedImage裁剪图片
  10. 【Unity】在Inspector上显示自定义的位掩码枚举(Flags)
  11. API等级和Android版本对应关系以及历史
  12. STM32使用外部中断控制led灯亮灭
  13. 单片机开发无线控制系列-单片机端代码实现
  14. 基于超声波的库位重定位算法
  15. linux开放外部端口访问
  16. 双剑合璧保障数据库安全
  17. 【THUWC2019模拟2019.1.18】Counting
  18. 算法设计与分析--贪心算法
  19. matlab院校,MATLAB要来了?!
  20. 3种解决安卓(android)手机锁屏密码忘记的方法!

热门文章

  1. 正确开展Scrum评审会议
  2. 【MySQL】练习二 关系数据库
  3. 求助应用Netlogo做交通出行方式选择仿真
  4. 使用gluLookAt发生无法解析符号的错误
  5. 类的初始化以及实例化
  6. 笔计算机维修,电子计算器常见故障 计算器常用维修方法
  7. linux查询当前时间
  8. 本质安全设备标准(IEC60079-11)的理解(一)
  9. 谷歌如何注册账号?手机号无法验证处理方法!2023年最新教程!
  10. 企业AD域(域控服务器)的安装和配置详细教程