Jpeg编码完整流程解析

时间:2022-02-18 21:18:28

本文结github yinjinchao给出的jpeg编码源码demo进行讲解,另外编码原理部分有任何问题,大家可以参照下面的博客,这里主要讲代码实现

github链接为github源码

原理介绍Jpeg编码原理

当我们已经获取到一幅图像的所有像素rgb值时,第一步要做的就是rgb转yuv了,当然以下的过程我们也都仅考虑分解过后单个8 * 8 block的实现

1,颜色空间转换(以RGB到YUV为例)

void JpegEncoder::_convertColorSpace(int xPos, int yPos, char* yData, char* cbData, char* crData)
{
for (int y=0; y<8; y++)
{
unsigned char* p = m_rgbBuffer + (y+yPos)*m_width*3 + xPos*3;
for (int x=0; x<8; x++)
{
unsigned char B = *p++;
unsigned char G = *p++;
unsigned char R = *p++;

yData[y*8+x] = (char)(0.299f * R + 0.587f * G + 0.114f * B - 128);
cbData[y*8+x] = (char)(-0.1687f * R - 0.3313f * G + 0.5f * B );
crData[y*8+x] = (char)(0.5f * R - 0.4187f * G - 0.0813f * B);
}
}
}

这里的逻辑应该是非常直观的,m_rgbBuffer也就是保存rgb数据的数组起始地址了,经过颜色空间转换后,也就得到了三个表,分别为Y, U(Cr), V(Cb)

当然,从这里我们也能看出这里采用的YUV 4:4:4格式

2,DCT

我们将图像通过DCT转到频域范围,从而能够去除图像中的高频分量,获得较高的压缩比

对照DCT的公式,我们看下代码实现

void JpegEncoder::_foword_FDC(const char* channel_data, short* fdc_data)
{
const float PI = 3.1415926f;
for(int v=0; v<8; v++)
{
for(int u=0; u<8; u++)
{
float alpha_u = (u==0) ? 1/sqrt(8.0f) : 0.5f;
float alpha_v = (v==0) ? 1/sqrt(8.0f) : 0.5f;

float temp = 0.f;
for(int x=0; x<8; x++)
{
for(int y=0; y<8; y++)
{
float data = channel_data[y*8+x];

data *= cos((2*x+1)*u*PI/16.0f);
data *= cos((2*y+1)*v*PI/16.0f);

temp += data;
}
}

temp *= alpha_u*alpha_v/m_YTable[ZigZag[v*8+u]];
fdc_data[ZigZag[v*8+u]] = (short) ((short)(temp + 16384.5) - 16384);
}
}
}

这里也是按部就班的依照公式,计算出每个u, v 对应的 F(u, v),那么下面的m_YTable是干嘛的呢

首先是由两个量化表(DQT),一个是亮度的量化表,一个是色度的量化表

const unsigned char Luminance_Quantization_Table[64] = 
{
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99
};

//-------------------------------------------------------------------------------
const unsigned char Chrominance_Quantization_Table[64] =
{
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
};

const char ZigZag[64] =
{
    0, 1, 5, 6,14,15,27,28,
    2, 4, 7,13,16,26,29,42,
    3, 8,12,17,25,30,41,43,
    9,11,18,24,31,40,44,53,
    10,19,23,32,39,45,52,54,
    20,22,33,38,46,51,55,60,
    21,34,37,47,50,56,59,61,
    35,36,48,49,57,58,62,63
}; 


这两个量化表是根据人眼的视觉心理阈(貌似是这么叫的。。。)做出来的,当然为了能够手动调节量化的程度,源码作者搞了这么个东西,作者对quality_scale的解释是   
该参数在1~199之间,数值越大,压缩比例越高,跟着代码看确实也是如此

void JpegEncoder::_initQualityTables(int quality_scale)
{
if(quality_scale<=0) quality_scale=1;
if(quality_scale>=100) quality_scale=99;

for(int i=0; i<64; i++)
{
int temp = ((int)(Luminance_Quantization_Table[i] * quality_scale + 50) / 100);
if (temp<=0) temp = 1;
if (temp>0xFF) temp = 0xFF;
m_YTable[ZigZag[i]] = (unsigned char)temp;

temp = ((int)(Chrominance_Quantization_Table[i] * quality_scale + 50) / 100);
if (temp<=0) temp = 1;
if (temp>0xFF) temp = 0xFF;
m_CbCrTable[ZigZag[i]] = (unsigned char)temp;
}
}

另外这个新生成的量化表并采用了之字形编排方式,也就是m_YTable和m_CbCrTable不是用i作为下表定位数组元素而是用了m_YTable[ZigZag[i]],这是由于u,v较大时,量化表中对应的量化台阶高度较大,另外高频分量的DCT系数较小,更容易产生0,也就是说量化后矩阵的右下角更容易出现0,那么为了0更多地能够连续出现,采用之字形编码的优势就非常明显了

接着,我们回到dct函数的最后两行代码,这里貌似源码作者有些笔误,这里temp值得计算直接都采用了m_YTable作为量化矩阵。。。最后一行四舍五入的操作也确实比较迷

3,DPCM与RLE

由于每个8*8 block里面DC的数值都比较大,为了节省空间,现在普遍采用的是DPCM的方法,就是除了第一个block存原始数值外,之后的每个block中DC全部都存与前一个DC的差值,而对于AC数值而言,之前采用的之字形编排方式,使得大量的0连续出现,从而能够采用RLE提高压缩率

在代码中这一步并没有直接的体现,我们接着往下看

4,Huffman编码

//-------------------------------------------------------------------------------
const char Standard_DC_Luminance_NRCodes[] = { 0, 0, 7, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
const unsigned char Standard_DC_Luminance_Values[] = { 4, 5, 3, 2, 6, 1, 0, 7, 8, 9, 10, 11 };

//-------------------------------------------------------------------------------
const char Standard_DC_Chrominance_NRCodes[] = { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 };
const unsigned char Standard_DC_Chrominance_Values[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

//-------------------------------------------------------------------------------
const char Standard_AC_Luminance_NRCodes[] = { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d };
const unsigned char Standard_AC_Luminance_Values[] =
{
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
};

//-------------------------------------------------------------------------------
const char Standard_AC_Chrominance_NRCodes[] = { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 };
const unsigned char Standard_AC_Chrominance_Values[] =
{
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
};

Huffman编码表按照DC/AC,亮度/色度可分为4张表,另外我们在jpeg编码中用到的也并非上课学到的传统的Huffman编码,而是一种范式Huffman编码,它具有如下特点及优势(百度百科):范式 哈夫曼编码最早由Schwartz[1964]提出,它是哈夫曼编码的一个子集。其中心思想是:使用某些强制的约定,仅通过很少的数据便能重构出 哈夫曼编码树的结构。其中一种很重要的约定是数字序列属性(numerical sequence property),它要求相同长度的码字是连续整数的二进制描述。例如,假设码字长度为4的最小值为0010,那么其它长度为4的码字必为0011, 0100, 0101, ...;另一个约定:为了尽可能的利用编码空间,长度为i第一个码字f(i)能从长度为i-1的最后一个码字得出, 即:f(i) = 2(f(i-1)+1)。假定长度为4的最后一个码字为1001,那么长度为5的第一个码字便为10100。最后一个约定:码字长度最小的第一个编码从0开始。通过上述约定,解码器能根据每个码字的长度恢复出整棵 哈夫曼编码树的结构

再定义了一堆Huffman表后,我们看看Huffman表的初始化操作

 struct BitString
{
    int length;    
    int value;
};

BitString m_Y_DC_Huffman_Table[12];
BitString m_Y_AC_Huffman_Table[256];

BitString m_CbCr_DC_Huffman_Table[12];
BitString m_CbCr_AC_Huffman_Table[256];

void JpegEncoder::_initHuffmanTables(void)
{
memset(&m_Y_DC_Huffman_Table, 0, sizeof(m_Y_DC_Huffman_Table));
_computeHuffmanTable(Standard_DC_Luminance_NRCodes, Standard_DC_Luminance_Values, m_Y_DC_Huffman_Table);

memset(&m_Y_AC_Huffman_Table, 0, sizeof(m_Y_AC_Huffman_Table));
_computeHuffmanTable(Standard_AC_Luminance_NRCodes, Standard_AC_Luminance_Values, m_Y_AC_Huffman_Table);

memset(&m_CbCr_DC_Huffman_Table, 0, sizeof(m_CbCr_DC_Huffman_Table));
_computeHuffmanTable(Standard_DC_Chrominance_NRCodes, Standard_DC_Chrominance_Values, m_CbCr_DC_Huffman_Table);

memset(&m_CbCr_AC_Huffman_Table, 0, sizeof(m_CbCr_AC_Huffman_Table));
_computeHuffmanTable(Standard_AC_Chrominance_NRCodes, Standard_AC_Chrominance_Values, m_CbCr_AC_Huffman_Table);
}

void JpegEncoder::_computeHuffmanTable(const char* nr_codes, const unsigned char* std_table, BitString* huffman_table)
{
    unsigned char pos_in_table = 0;
    unsigned short code_value = 0;

    for(int k = 1; k <= 16; k++)
    {
        for(int j = 1; j <= nr_codes[k-1]; j++)
        {
            huffman_table[std_table[pos_in_table]].value = code_value;
            huffman_table[std_table[pos_in_table]].length = k;
            pos_in_table++;
            code_value++;
        }
        code_value <<= 1;
    }  
}

以Standard_Luminance_DC为例,我们看下Huffman编码表的生成过程

computeHuffmanTable中,共有两个循环,第一层循环对应nr_nodes每个元素的遍历,第二层则将nr_nodes中对应元素取出,并对Huffman表赋值,实际上,BitString这个数据结构也就对应了单个Huffman编码,length表示编码长度,value表示编码值。于是当k = 1和k = 2时,并没有对应的编码值; k = 3时,有7个编码值与其对应(3bit 编码总共有8个,这里遵照前缀码的规则,同时也为了获得总体最小字节长度,设置了7个 3 bit 编码),按照范式Huffman编码,相同编码长度的编码值以1递增,这里也就对应了为什么code_value每次加1了;当k = 3的7个编码全部结束后,按照范式Huffman编码规则,f(i) = 2 * (f(i - 1) + 1),对code_value进行左移操作;依次类推,也就获得了整个Huffman编码表啦,当然这里使用十进制表示的编码序列的

computeHuffman以后也就得到了以下两张表,其中huffman_table以value进行寻址,对应数组元素的length和value分别为表中的Length和Bits对应的十进制数,建议结合Huffman编码 来看

Jpeg编码完整流程解析

Jpeg编码完整流程解析

从图中可以看出,编码的bit信息从上到下是按照范式Huffman编码的要求,每次递增1,bit位数增加时,加1后右移一位的要求的

JpegEncoder::BitString JpegEncoder::_getBitCode(int value)
{
    BitString ret;
    int v = (value>0) ? value : -value;
    
    int length = 0;
    for(length=0; v; v>>=1) length++;

    ret.value = value>0 ? value : ((1<<length)+value-1);
    ret.length = length;

    return ret;
};

void JpegEncoder::_doHuffmanEncoding(const short* DU, short& prevDC, const BitString* HTDC, const BitString* HTAC,
BitString* outputBitString, int& bitStringCounts)
{
BitString EOB = HTAC[0x00];
BitString SIXTEEN_ZEROS = HTAC[0xF0];

int index=0;

// encode DC
int dcDiff = (int)(DU[0] - prevDC);
prevDC = DU[0];

if (dcDiff == 0)
outputBitString[index++] = HTDC[0];
else
{
BitString bs = _getBitCode(dcDiff);

outputBitString[index++] = HTDC[bs.length];
outputBitString[index++] = bs;
}

// encode ACs
int endPos=63; //end0pos = first element in reverse order != 0
while((endPos > 0) && (DU[endPos] == 0)) endPos--;

for(int i=1; i<=endPos; )
{
int startPos = i;
while((DU[i] == 0) && (i <= endPos)) i++;

int zeroCounts = i - startPos;
if (zeroCounts >= 16)
{
for (int j=1; j<=zeroCounts/16; j++)
outputBitString[index++] = SIXTEEN_ZEROS;
zeroCounts = zeroCounts%16;
}

BitString bs = _getBitCode(DU[i]);

outputBitString[index++] = HTAC[(zeroCounts << 4) | bs.length];
outputBitString[index++] = bs;
i++;
}

if (endPos != 63)
outputBitString[index++] = EOB;

bitStringCounts = index;
}

接着就是真正Huffman编码的实现啦。DU就是之前量化后的DQT的结果,prevDC开始为0,之后一直表示每个8*8 block的直流分量值,HTDC和HTAC分别对应Huffman DC表和Huffman AC表,也就是上面放的两张图,我们继续看下具体的逻辑

_getBitCode的输入时一个有符号型整数value,输出则是该value对应的编码(编码长度与编码值)。正数的逻辑很简单了,长度就是该数值的有效数据位个数,编码值就是其本身;而对于负数,则需要以有效数据位长度(不包含符号位)为满量程,减去实际负数值为编码值。例如,-7,-3,-1的编码值都为0,而-6,-2的编码值为1。这也就刚好对应了jpeg标准码表

Jpeg编码完整流程解析

首先是DC部分的编码,从原博客上来看,主要分为以下几个步骤

1,获取DPCM值,即将本block中DC值减去上一个block的DC值,得到dcDiff

2,RLE编码,由于DC值位于数组的第一个,因此前面0的长度一直为0, 所以结果为(0,dcDiff)

3,Bit编码,通过查上表,得到dcDiff的bit值和bit长度,得到如下形式(0,BitCodingLength(dcDiff),BitCodingValue(dcDiff))

4,合并Bit编码中RLE编码和Bit编码长度两个数据,由于RLE编码对于DC而言一直为0, 那么就得到(BitCodingLength(dcDiff),BitCodingValue(dcDiff))

5,Huffman编码,得到(HuffmanCoding(BitCodingLength(dcDiff)),BitCodingValue(dcDiff))

接下来是AC编码

先找到DQT编码表中反向第一个不为零的index,即endPos

第一个循环找到AC中RLE编码0的个数,注意为了能用4个bit表示RLE值,RLE值不能超过15,所以当zerosCount >= 16时,需要按序存储一个或者多个(15,0),同样(15,0)在经过如上所示编码以后,应该是(15,0)-->(15,0,0)-->(15,0)-->(Huffman(0x0F),0),源程序貌似有些问题

接着,对找到的非零AC值,i,这里也经过上面DC编码的顺序,只不过这里RLE不是0啦,而是zerosCount,并且在组合RLE和BitCodingLength时,需要把zerosCount左移4位得到一个8bit数据的高4位。按此逻辑,一个block的Huffman编码部分也就完成啦

5,最后我们看下如何输出成一个jpeg文件

//-------------------------------------------------------------------------------
void JpegEncoder::_write_byte_(unsigned char value, FILE* fp)
{
    _write_(&value, 1, fp);
}

//-------------------------------------------------------------------------------
void JpegEncoder::_write_word_(unsigned short value, FILE* fp)
{
    // little endian in memory, lower byte in 'value' lies in lower address, so first byte of &value
    // is the lower byte
    unsigned short _value = ((value>>8)&0xFF) | ((value&0xFF)<<8);
    _write_(&_value, 2, fp);
}

//-------------------------------------------------------------------------------
void JpegEncoder::_write_(const void* p, int byteSize, FILE* fp)
{
    fwrite(p, 1, byteSize, fp);
}

//-------------------------------------------------------------------------------
void JpegEncoder::_write_jpeg_header(FILE* fp)
{
    //SOI
    _write_word_(0xFFD8, fp);        // marker = 0xFFD8

    //APPO
    _write_word_(0xFFE0,fp);        // marker = 0xFFE0
    _write_word_(16, fp);            // length = 16 for usual JPEG, no thumbnail
    _write_("JFIF", 5, fp);            // 'JFIF\0'
    _write_byte_(1, fp);            // version_hi
    _write_byte_(1, fp);            // version_low
    _write_byte_(0, fp);            // xyunits = 0 no units, normal density
    _write_word_(1, fp);            // xdensity
    _write_word_(1, fp);            // ydensity
    _write_byte_(0, fp);            // thumbWidth
    _write_byte_(0, fp);            // thumbHeight

    //DQT
    _write_word_(0xFFDB, fp);        //marker = 0xFFDB
    _write_word_(132, fp);            //size=132
    _write_byte_(0, fp);            //QTYinfo== 0:  bit 0..3: number of QT = 0 (table for Y)
                                    //                bit 4..7: precision of QT
                                    //                bit 8    : 0
    _write_(m_YTable, 64, fp);        //YTable
    _write_byte_(1, fp);            //QTCbinfo = 1 (quantization table for Cb,Cr)
    _write_(m_CbCrTable, 64, fp);    //CbCrTable

    //SOFO
    _write_word_(0xFFC0, fp);            //marker = 0xFFC0
    _write_word_(17, fp);                //length = 17 for a truecolor YCbCr JPG
    _write_byte_(8, fp);                //precision = 8: 8 bits/sample
    _write_word_(m_height&0xFFFF, fp);    //height
    _write_word_(m_width&0xFFFF, fp);    //width
    _write_byte_(3, fp);                //nrofcomponents = 3: We encode a truecolor JPG

    _write_byte_(1, fp);                //IdY = 1
    _write_byte_(0x11, fp);                //HVY sampling factors for Y (bit 0-3 vert., 4-7 hor.)(SubSamp 1x1)
    _write_byte_(0, fp);                //QTY  Quantization Table number for Y = 0

    _write_byte_(2, fp);                //IdCb = 2
    _write_byte_(0x11, fp);                //HVCb = 0x11(SubSamp 1x1)
    _write_byte_(1, fp);                //QTCb = 1

    _write_byte_(3, fp);                //IdCr = 3
    _write_byte_(0x11, fp);                //HVCr = 0x11 (SubSamp 1x1)
    _write_byte_(1, fp);                //QTCr Normally equal to QTCb = 1
    
    //DHT
    _write_word_(0xFFC4, fp);        //marker = 0xFFC4
    _write_word_(0x01A2, fp);        //length = 0x01A2
    _write_byte_(0, fp);            //HTYDCinfo bit 0..3    : number of HT (0..3), for Y =0
                                    //            bit 4        : type of HT, 0 = DC table,1 = AC table
                                    //            bit 5..7    : not used, must be 0
    _write_(Standard_DC_Luminance_NRCodes, sizeof(Standard_DC_Luminance_NRCodes), fp);    //DC_L_NRC
    _write_(Standard_DC_Luminance_Values, sizeof(Standard_DC_Luminance_Values), fp);        //DC_L_VALUE
    _write_byte_(0x10, fp);            //HTYACinfo
    _write_(Standard_AC_Luminance_NRCodes, sizeof(Standard_AC_Luminance_NRCodes), fp);
    _write_(Standard_AC_Luminance_Values, sizeof(Standard_AC_Luminance_Values), fp); //we'll use the standard Huffman tables
    _write_byte_(0x01, fp);            //HTCbDCinfo
    _write_(Standard_DC_Chrominance_NRCodes, sizeof(Standard_DC_Chrominance_NRCodes), fp);
    _write_(Standard_DC_Chrominance_Values, sizeof(Standard_DC_Chrominance_Values), fp);
    _write_byte_(0x11, fp);            //HTCbACinfo
    _write_(Standard_AC_Chrominance_NRCodes, sizeof(Standard_AC_Chrominance_NRCodes), fp);
    _write_(Standard_AC_Chrominance_Values, sizeof(Standard_AC_Chrominance_Values), fp);

    //SOS
    _write_word_(0xFFDA, fp);        //marker = 0xFFC4
    _write_word_(12, fp);            //length = 12
    _write_byte_(3, fp);            //nrofcomponents, Should be 3: truecolor JPG

    _write_byte_(1, fp);            //Idy=1
    _write_byte_(0, fp);            //HTY    bits 0..3: AC table (0..3)
                                    //        bits 4..7: DC table (0..3)
    _write_byte_(2, fp);            //IdCb
    _write_byte_(0x11, fp);            //HTCb

    _write_byte_(3, fp);            //IdCr
    _write_byte_(0x11, fp);            //HTCr

    _write_byte_(0, fp);            //Ss not interesting, they should be 0,63,0
    _write_byte_(0x3F, fp);            //Se
    _write_byte_(0, fp);            //Bf
}

//-------------------------------------------------------------------------------
void JpegEncoder::_write_bitstring_(const BitString* bs, int counts, int& newByte, int& newBytePos, FILE* fp)
{
    unsigned short mask[] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
    
    for(int i=0; i<counts; i++)
    {
        int value = bs[i].value;
        int posval = bs[i].length - 1;

        while (posval >= 0)
        {
            if ((value & mask[posval]) != 0)
            {
                newByte = newByte  | mask[newBytePos];
            }
            posval--;
            newBytePos--;
            if (newBytePos < 0)
            {
                // Write to stream
                _write_byte_((unsigned char)(newByte), fp);
                if (newByte == 0xFF)
                {
                    // Handle special case
                    _write_byte_((unsigned char)(0x00), fp);
                }

                // Reinitialize
                newBytePos = 7;
                newByte = 0;
            }
        }
    }
}


write jpeg header这里就不介绍了,大家对着jpeg header标准看一看就好,这里主要看下如果由上一部的outputBitString输出成正式jpeg文件

write_bit_string其实也就对应着原博客的一个序列化过程

mask是一个最高位为1,其余低位为0的数组,用于比较得到一个数据中每个bit的值。。。原作者的做法是这样的,虽然我觉得移位大法会好一些

如上,一个BitString中有一个length和一个value数据,那么我怎么能把一串BitString按二进制序列化出来呢

这里的做法就是用了一个newByte和一个newBytePos,newByte用于记录每8个bit的值,而newBytePos用于按序标记每个bit,当newByte8个bit都被填充以后,newBytePos小于0,调用write_byte输出该Byte数据,那么如果是0xFF,为了不跟jpeg的标识冲突,就输出一个0x00,循环往复如此,就得到完整的bit流啦