接從零開始手敲次世代遊戲引擎(JPEG特別篇)-1。

(首先說一下,這篇會非常難看。但是如果動手去做了,會對各項能力有極大的提高)

我們現在有了核心的演算法庫,接下來就是實際讀取JPEG檔案並進行解碼了。

首先,JPEG標準(參考引用*2)定義了JPEG交換檔案的大的框架結構。

其次,JPEG檔案有JFIF和Exif這兩種主要的具體實現格式。我們先來看JFIF。JFIF的格式可以參考(參考引用*1)

總的來說,JPEG檔案是一個二進位制檔案,當中包括了一系列以0x FF xx作為開始標記的資料段(Segement)

每個資料段的格式基本如下:

首先是2個位元組的識別符號,以FF開頭。如 FF C0;

然後是2個位元組的長度,表示該段的長度(有例外,比如包含實際影象資料的SOS段);

(這裡需要注意的是,首先,JPEG當中所有的二進位制儲存都是Big Endian的,或者說是按照網路位元組順序儲存的。對於兩個位元組以上的資料型別,是按照從高位位元組(MSB)到低位位元組(LSB)的順序進行儲存的,這與X86架構的儲存方式是反的;)

(其次,這個長度不包括識別符號的2個位元組,但是包括其自身的2個位元組;)

接下來的內容則根據段的型別決定。

所以,我們首先需要定義這些段(頭)的結構。我們從Framework/Parser/BMP。hpp複製新建Framework/Parser/JFIF。hpp,然後根據(參考引用*1)(參考引用*2)當中的(segment header)定義,編寫對應的結構體(程式碼有刪節):

#pragma pack(push, 1)

struct JFIF_FILEHEADER {

uint16_t SOI;

};

struct JPEG_SEGMENT_HEADER {

uint16_t Marker;

uint16_t Length;

};

struct APP0 : public JPEG_SEGMENT_HEADER {

char Identifier[5];

};

struct JFIF_APP0 : public APP0 {

uint8_t MajorVersion;

uint8_t MinorVersion;

uint8_t DensityUnits;

uint16_t Xdensity;

uint16_t Ydensity;

uint8_t Xthumbnail;

uint8_t Ythumbnail;

};

struct JFXX_APP0 : public APP0 {

uint8_t ThumbnailFormat;

};

。。。

#pragma pack(pop)

然後,我們迴圈檢測段的開頭標誌,用上面定義的這些結構體將儲存在段頭結構當中的資料提取出來,大致是如同下面這麼一個感覺(有大量刪節):

virtual Image Parse(const Buffer& buf)

{

Image img;

const uint8_t* pData = buf。GetData();

const uint8_t* pDataEnd = buf。GetData() + buf。GetDataSize();

const JFIF_FILEHEADER* pFileHeader = reinterpret_cast(pData);

pData += sizeof(JFIF_FILEHEADER);

if (pFileHeader->SOI == endian_net_unsigned_int((uint16_t)0xFFD8) /* FF D8 */) {

std::cout << “Asset is JPEG file” << std::endl;

while(pData < pDataEnd)

{

const JPEG_SEGMENT_HEADER* pSegmentHeader = reinterpret_cast(pData);

std::cout << “============================” << std::endl;

std::printf(“Segment Length: %d bytes\n” ,endian_net_unsigned_int(pSegmentHeader->Length));

switch (endian_net_unsigned_int(pSegmentHeader->Marker)) {

case 0xFFC0:

case 0xFFC2:

{

if (endian_net_unsigned_int(pSegmentHeader->Marker) == 0xFFC0)

std::cout << “Start Of Frame0 (baseline DCT)” << std::endl;

else

std::cout << “Start Of Frame2 (progressive DCT)” << std::endl;

std::cout << “——————————————” << std::endl;

。。。

}

break;

case 0xFFC4:

{

std::cout << “Define Huffman Table” << std::endl;

std::cout << “——————————————” << std::endl;

auto segmentLength = endian_net_unsigned_int(pSegmentHeader->Length) - 2;

}

。。。

其中,0x FF DA(SOS, Start Of Scan)是實際影象資料儲存塊開始的標誌。當我們檢測到這個標誌的時候,我們就可以開始Dump影象的資料,直到遇到0x FF D9(EOI, End Of Image)為止。

不過這裡面需要注意的問題是,實際上影象資料裡面,也會出現0x FF這樣的值。為了不讓其打亂我們的定位標誌,JPEG標準規定,對於影象資料當中出現0x FF這樣的情況,在其後立即插入0x 00,也就是以0x FF 00來代表0x FF。(同時,JPEG標準規定,不存在0x FF 00這樣的段標誌)

上一篇文章當中我們也提到了,JPEG是將影象分割成為8x8的影象塊進行編碼的。(參考引用*3)當中比較詳細地介紹了手工解碼一個十分簡單的JPEG影象檔案的過程。

在我們程式設計的時候,測試用例的選擇同樣是十分重要的。一個好的測試用例,可以大大加快我們開發的速度和質量。由於JPEG解碼過程涉及到非常多的運算和型別轉換,使用一個簡單明確的資料樣本可以幫助我們快速地找到問題。這裡我選用了與參考引用*3相同的圖片。這樣我們可以將程式解碼的每一步結果與文章當中提供的結果進行對比,快速找到問題。這個圖片是這樣的:

從零開始手敲次世代遊戲引擎(JPEG特別篇)-2

圖片來自參考引用*3

太小?放大的話,如下(外框和虛線表示畫素邊界,不是原圖片的一部分):

從零開始手敲次世代遊戲引擎(JPEG特別篇)-2

圖片來自參考引用*3

是一個16畫素寬,8畫素高的圖片。左邊8x8是全黑,右邊8x8是全白。

這麼一個圖片,在壓縮之前是 16(畫素(寬)) * 8(畫素(高)) * 3 (位元組/畫素)= 384個位元組。經過JPEG壓縮之後,是304個位元組到653個位元組之間。

什麼?653個位元組?那不是更大了?是的,這是因為JPEG檔案的構造當中,除了影象資料之外,還有很多記錄影象屬性以及壓縮演算法引數的元資料。因為我們的圖片太小了,所以這些開銷反而成為了大頭。事實上,如果拋去這部分開銷,壓縮後的影象資料只有7到9個位元組。

一般來說,JPEG的壓縮率大約在1/5左右。當然這和很多因素有關。

我們接下來就來看這區區9個位元組是怎麼一步一步展開成為384個位元組的。

經過上面的程式的處理,我們可以從檔案當中的SOS和EOS這兩個標誌之間,提取出下面這9個位元組的影象資料:

FC FF 00 E2 AF EF F3 15 7F

首先,如上面所說的,0x FF 00實際上是代表著FF。所以其實有效的資料一共有8個:

FC FF E2 AF EF F3 15 7F

然後,這8個數據其實是一種變種的霍夫曼(Huffman)編碼(參考引用*5)。霍夫曼是上個世紀50年代MIT的高材生。這種編碼的核心思想就是,用盡量短的2進位制bit代表最多出現的資料。

比如說我們有100個人,每個人有一個學號。如果其中有些人經常來上課,我們就給他一個短一點兒的號碼。不太來上課的,我們就給他一個長一點的學號。這樣的話,每次點名的時候,我們就可以少寫一點兒字。

在計算機當中,所有數都是用二進位制表示的。比如十進位制8,用二進位制表示是1000,這個長度就是4個bit;而十進位制1,用二進位制表示還是1, 這個長度就只有1個bit。如果在一個序列當中,8經常出現,而1出現的比較少,那麼我們透過(強行)定義二進位制1代表8,而二進位制1000代表1,那麼就可以縮短整個編碼的長度,而不會丟失任何資料。

因此很容易可以想到,如果想要對一個序列進行霍夫曼編碼,首先需要統計在這個序列當中基本符號的種類和出現頻率。然後將基本符號按照出現頻率從高到低進行排序,對於高的給予儘可能短的編碼,對於低的給予稍長一些的編碼,目的是使最終的整體碼長最短。

這是一個演算法問題。有興趣的可以參考(參考引用*5)。我這裡就不作更多的展開了。

我們這裡需要實現的是霍夫曼編碼的解碼。絕大部分的解碼,最終都是類似一個查字典的過程。首要的是構建這個字典(如果放在安全領域就叫密碼本),然後根據索引一個個查出來就好了。

JPEG檔案的目的並不是加密,所以這個密碼本是放在檔案當中的。這也是為什麼在我們這個例子當中,壓縮之後的檔案反而變大的原因。

JPEG的密碼本的起始標誌是0x FF C4。我們在檔案當中定位到它,然後將其提取出來。提取出來的結果大致如下(出於篇幅考慮只列出了部分):

============================

Segment Length: 418 bytes

Define Huffman Table

——————————————

Table Class: 0

Destination Identifier: 0

000 | 4

001 | 5

010 | 3

011 | 2

100 | 6

101 | 1

110 | 0

1110 | 7

11110 | 8

111110 | 9

1111110 | a

11111110 | b

豎線左側的是編碼(索引,密文),右側的是值(詞條,符號,明文)

這裡需要注意的有以下幾點:

雖然JPEG當中儲存了這個字典(密碼本),但是為了縮小檔案尺寸(畢竟JPEG的目的是壓縮),沒有直接儲存編碼(索引,密文),而只是儲存了值(詞條,符號,明文)以及其在Huffman Tree當中的層級。我們需要自己根據這些資訊重建Huffman Tree來生成索引;

JPEG當中這樣的字典(密碼本)有4個。其內容是根據被儲存的圖片內容以及壓縮品質設定變化的。在解碼的過程當中會根據當前提取的資料狀態使用不同的字典(密碼本)(後面詳述)

好了。接下來我們需要了解JPEG檔案當中影象資料的組織形式。上一篇文章當中我們所提到的8x8分塊,這個其實是最底層的組織形式。事實上,彩色影象會包括R/G/B這3個通道(注意普通的JPEG不支援Alpha通道,也就是透明度)。上一篇文章當中也提到,在JPEG壓縮的過程當中,首先會將畫素的色彩空間從RGB轉成YCbCr。這樣做的原因(或者說目的)是為了進一步減少檔案尺寸。

不過單純的RGB轉YCbCr,這個是一個3個分量到3個分量的座標軸轉換,並不會直接帶來資料的減少。秘密在於YCbCr的DownSampling。RGB三個通道都是彩色通道,其對於人眼的重要度其實是基本均等的(如果硬要說的話,G相對來說更重要一些)。但是到了YCbCr領域,Y(輝度)是最重要的(人眼的辨識度/敏感度最高),而兩個色差訊號就比較不重要一些。

所以,在變換到YCbCr之後,我們就可以將CbCr這兩個通道的圖縮小。意思是,比如8x8的影象塊,Y通道仍然維持8x8的解析度,但是CbCr這兩個通道可以只保留4x4的解析度,甚至只保留2x2的解析度,就可以了。事實上,大部分的彩色電視訊號,都是採用了這種方式來減少訊號的位元速率。需要進一步瞭解的,可以參考(參考引用*6)

從零開始手敲次世代遊戲引擎(JPEG特別篇)-2

圖片來源:Wikipedia

說了這麼多,好像和前面的霍夫曼編碼沒有什麼關係呢?

如果是原版的霍夫曼編碼,那麼只需要按照字典將“密文”一一翻譯成為明文就可以了。然而JPEG當中的複雜性在於,它有4個密碼本。什麼時候使用哪個密碼本,是和上面所說的YUV格式息息相關的。這是因為各個訊號的統計分佈很可能是非常不一樣的。結合其特點使用不一樣的密碼本可以進一步減少壓縮後的檔案大小。

常規的做法是,對於Y訊號(通道)使用一套密碼本,而對於CbCr訊號(通道)使用另外一套密碼本。注意我這裡使用的是“一套”,而不是“一個”。為什麼呢?

因為單單是這樣解釋不了JPEG使用4個密碼本的原因(看起來似乎2個就夠了?)。這時候請回想起(或者如果還沒有看過,請看)上一篇文章當中關於DCT/IDCT的介紹。經過DCT變換之後,矩陣的左上角(0,0)位置的值絕對值一般遠大於矩陣當中的其他值。這個左上角的值被稱為直流分量(DC。沿用電學稱呼,在這裡其實與電流沒有任何關係)代表的是8x8畫素塊的平均值,它的統計特性與其他值(交流分量,AC。)也是非常不同的。

可能聰明的你已經猜到,其實JPEG不僅為不同的通道指定了(可能)不同的密碼本,也為DCT當中的直流分量和交流分量指定了不同的密碼本。所以實際上是這麼一個分配情況:

用於Y通道DC分量的密碼本

用於Y通道AC分量的密碼本

用於CbCr通道DC分量的密碼本

用於CbCr通道AC分量的密碼本

下面是我們這個例子當中用到的Y通道的AC分量密碼本,對比上面DC分量的密碼本,我們可以看到它們是非常不同的(符號的數量遠多於DC分量):

Table Class: 1

Destination Identifier: 0

00 | 1

01 | 2

100 | 3

1010 | 11

1011 | 4

1100 | 0

11010 | 5

11011 | 21

11100 | 12

111010 | 31

111011 | 41

1111000 | 51

1111001 | 6

1111010 | 13

1111011 | 61

11111000 | 22

11111001 | 71

111110100 | 81

111110101 | 14

111110110 | 32

111110111 | 91

111111000 | a1

111111001 | 7

1111110100 | 15

1111110101 | b1

1111110110 | 42

1111110111 | 23

1111111000 | c1

1111111001 | 52

1111111010 | d1

11111110110 | e1

11111110111 | 33

11111111000 | 16

111111110010 | 62

111111110011 | f0

111111110100 | 24

111111110101 | 72

1111111101100 | 82

1111111101101 | f1

11111111011100 | 25

11111111011101 | 43

11111111011110 | 34

11111111011111 | 53

11111111100000 | 92

11111111100001 | a2

111111111000100 | b2

111111111000101 | 63

1111111110001100 | 73

1111111110001101 | c2

1111111110001110 | 35

1111111110001111 | 44

1111111110010000 | 27

1111111110010001 | 93

1111111110010010 | a3

1111111110010011 | b3

1111111110010100 | 36

1111111110010101 | 17

1111111110010110 | 54

1111111110010111 | 64

1111111110011000 | 74

1111111110011001 | c3

1111111110011010 | d2

1111111110011011 | e2

1111111110011100 | 8

1111111110011101 | 26

1111111110011110 | 83

1111111110011111 | 9

1111111110100000 | a

1111111110100001 | 18

1111111110100010 | 19

1111111110100011 | 84

1111111110100100 | 94

1111111110100101 | 45

1111111110100110 | 46

1111111110100111 | a4

1111111110101000 | b4

1111111110101001 | 56

1111111110101010 | d3

1111111110101011 | 55

1111111110101100 | 28

1111111110101101 | 1a

1111111110101110 | f2

1111111110101111 | e3

1111111110110000 | f3

1111111110110001 | c4

1111111110110010 | d4

1111111110110011 | e4

1111111110110100 | f4

1111111110110101 | 65

1111111110110110 | 75

1111111110110111 | 85

1111111110111000 | 95

1111111110111001 | a5

1111111110111010 | b5

1111111110111011 | c5

1111111110111100 | d5

1111111110111101 | e5

1111111110111110 | f5

1111111110111111 | 66

1111111111000000 | 76

1111111111000001 | 86

1111111111000010 | 96

1111111111000011 | a6

1111111111000100 | b6

1111111111000101 | c6

1111111111000110 | d6

1111111111000111 | e6

1111111111001000 | f6

1111111111001001 | 37

1111111111001010 | 47

1111111111001011 | 57

1111111111001100 | 67

1111111111001101 | 77

1111111111001110 | 87

1111111111001111 | 97

1111111111010000 | a7

1111111111010001 | b7

1111111111010010 | c7

1111111111010011 | d7

1111111111010100 | e7

1111111111010101 | f7

1111111111010110 | 38

1111111111010111 | 48

1111111111011000 | 58

1111111111011001 | 68

1111111111011010 | 78

1111111111011011 | 88

1111111111011100 | 98

1111111111011101 | a8

1111111111011110 | b8

1111111111011111 | c8

1111111111100000 | d8

1111111111100001 | e8

1111111111100010 | f8

1111111111100011 | 29

1111111111100100 | 39

1111111111100101 | 49

1111111111100110 | 59

1111111111100111 | 69

1111111111101000 | 79

1111111111101001 | 89

1111111111101010 | 99

1111111111101011 | a9

1111111111101100 | b9

1111111111101101 | c9

1111111111101110 | d9

1111111111101111 | e9

1111111111110000 | f9

1111111111110001 | 2a

1111111111110010 | 3a

1111111111110011 | 4a

1111111111110100 | 5a

1111111111110101 | 6a

1111111111110110 | 7a

1111111111110111 | 8a

1111111111111000 | 9a

1111111111111001 | aa

1111111111111010 | ba

1111111111111011 | ca

1111111111111100 | da

1111111111111101 | ea

1111111111111110 | fa

好了,有了這些知識儲備之後,我們就可以解碼那神秘的8個位元組了:

FC FF E2 AF EF F3 15 7F

在JPEG當中,是按照下面這麼一個順序儲存資料的(這裡以YUV444為例):

Y(DC)Y(AC)Cb(DC)Cb(AC)Cr(DC)Cr(AC)

我們根據這個順序反覆替換對應的密碼本,就可以得到Y,Cb,Cr三個分量的DCT矩陣。不過這裡實際上還有一個秘密,就是被霍夫曼編碼的其實並不是矩陣的值,而是關於矩陣的值的一個描述。具體來說:

將FC展開成二進位制:

1111 1100

查閱Y(DC)密碼本,可以得到前7位(1111 110)= 0x 0a。(為什麼我們知道是前7位?仔細看看上面霍夫曼的編碼(索引),你會發現在同一個密碼本里面沒有任何一個短的碼會成為長的碼的開頭一部分。所以我們只需要一位一位的去匹配,直到找到對應的值為止。這就是霍夫曼編碼的NB之處)

這個0x 0a其實並不代表Y(DC)的值是10。它的意思其實是說,接下來的10個bit是DC的值。所以我們從第8位開始往後取出10個bit,這才是DC的值。

這就是JPEG當中的霍夫曼編碼與常規的霍夫曼編碼不同的地方,被稱為huffman / entropy coding。其目的當然是:為了進一步毫無人性地減少檔案的尺寸。(提供以bit為單位,而不是byte為單位的儲存方式)

這就是全部的秘密了嗎?

並不是。JPEG是一個相當複雜的編碼體系。接下來的AC部分,規則又有些不一樣了。

原因其實也並不複雜。每個8x8矩陣裡面,DC只有一個,位於(0,0)位置,但是AC有63個,並且大多數為0⃣️。學過演算法的應該知道,這是一種典型的稀疏矩陣。下面是我們這個例子的左邊8x8的Y分量DCT矩陣:

-512,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

所以,如果按照DC的儲存方式,即使是0,我們也需要為每個0分配儲存空間(哪怕是1bit)。這個顯然還不夠省。

所以AC分量採用的編碼方式是,在用於說明資料格式的那個位元組(也就是被霍夫曼編碼的那個位元組當中)除了說明後面有幾個bit用來表示AC分量之外,還說明有幾個連續的零。但是因為其實這個說明符只有一個位元組,所以其中的高4位用於表示連續的0⃣️的個數,低4位用於表示AC分量的bit數。這樣的話,最多能夠表示的連續的0⃣️的個數是15個。

這對於上面這種情況,還是不夠省。所以JPEG又規定,如果資料說明位元組的值為0,則表示從該位置開始之後到第64個矩陣元素的值都是0。

這就是全部的秘密了嗎?

很可惜,雖然已經很複雜了,還有。

因為DCT矩陣的特點就是左上角有值,右下角基本都是0⃣️,所以傳統的按行列進行儲存的話,會把0⃣️分散在非零的值之間,不能達到最大的壓縮率。所以,JPEG當中對於矩陣的儲存採用的是ZigZag方式,如下:

從零開始手敲次世代遊戲引擎(JPEG特別篇)-2

圖片來源:ISO/IEC 10918-1 : 1993(E)(參考引用*2)

這樣可以將矩陣當中的0⃣️串一長串,然後用一個0x 00早早的將這個矩陣結束掉。

也就是說:

如果矩陣全為零,那麼一個0x 00就可以代表它。(壓縮率1/64)

如果矩陣裡面只有稀稀拉拉幾個值,那麼幾個位元組就可以代表它。

好了,經過這樣眼花繚亂的技巧之後,我們上面的8個位元組,就可以展開成為下面這6個矩陣:

第一個8x8塊:

Y分量(頻域)

-512,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

Cb分量(頻域)

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

Cr分量(頻域)

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

第二個8x8塊

Y分量(頻域)

1020,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

Cb分量(頻域)

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

Cr分量(頻域)

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

這裡最後的一個步驟:Y分量的DC部分在塊間是儲存的差值。也就是說,第二個8x8塊的Y(DC)其實應該是第一個8x8塊的Y(DC)+第二個8x8塊的Y(DC)。也就是說上面第二個塊的Y分量矩陣需要修正為 -512 + 1020 = 508

508,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

然後將這些8x8矩陣分別用上一篇我們所寫的IDCT變換變換回來,得到:(CbCr為全零矩陣,IDCT之後仍然是全零矩陣)

第一個8x8塊:

Y分量

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

第二個8x8塊

Y分量

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

然後將其上浮128(使得值域從[-128 ,127]變為[0, 255]),然後使用我們上一篇所寫的YCbCr ——> RGB色彩空間變換,得到最終結果如下:

從零開始手敲次世代遊戲引擎(JPEG特別篇)-2

每組資料分別包括R、G、B三個分量

這正是我們用於測試的圖片的樣子(左邊8x8黑色,右邊8x8白色)

最後附上完整的程式執行輸出,供有興趣的讀者參考:(程式碼在jpeg分支當中)

*上面的說明當中還省略了被稱為Quantization的過程。這個過程是將DCT矩陣的不同元素進行縮放,使得AC的部分儘可能變成絕對值小於1的小數,然後在取整的時候變成0。JPEG之所以是有失真壓縮,除了色空間變換之後的YUV chrome sub sampling,資料的丟失主要就是發生在這一步。而上面介紹的其他步驟,在不考慮計算誤差的情況下,都是可逆的)

chenwenlideMBP:GameEngineFromScratch chenwenli$ 。/build/Test/JpegParserTest

Asset is JPEG file

============================

Segment Length: 16 bytes

JFIF-APP0

——————————————

JFIF Version: 1。2

Density Units: No units

Density: 100*100

No thumbnail defined in JFIF-APP0 segment!

============================

Segment Length: 17 bytes

Ignor Unrecognized Segment。 Marker=ffec

============================

Segment Length: 14 bytes

Ignor Unrecognized Segment。 Marker=ffee

============================

Segment Length: 132 bytes

Define Quantization Table

——————————————

Element Precision: 0

Destination Identifier: 0

2,2,2,2,3,4,5,6

2,2,2,2,3,4,5,6

2,2,2,2,4,5,7,9

2,2,2,4,5,7,9,12

3,3,4,5,8,10,12,12

4,4,5,7,10,12,12,12

5,5,7,9,12,12,12,12

6,6,9,12,12,12,12,12

Element Precision: 0

Destination Identifier: 1

3,3,5,9,13,15,15,15

3,4,6,11,14,12,12,12

5,6,9,14,12,12,12,12

9,11,14,12,12,12,12,12

13,14,12,12,12,12,12,12

15,12,12,12,12,12,12,12

15,12,12,12,12,12,12,12

15,12,12,12,12,12,12,12

============================

Segment Length: 17 bytes

Start Of Frame0 (baseline DCT)

——————————————

Sample Precision: 8

Num of Lines: 8

Num of Samples per Line: 16

Num of Components In Frame: 3

Component Identifier: 1

Horizontal Sampling Factor: 1

Vertical Sampling Factor: 1

Quantization Table Destination Selector: 0

Component Identifier: 2

Horizontal Sampling Factor: 1

Vertical Sampling Factor: 1

Quantization Table Destination Selector: 1

Component Identifier: 3

Horizontal Sampling Factor: 1

Vertical Sampling Factor: 1

Quantization Table Destination Selector: 1

============================

Segment Length: 418 bytes

Define Huffman Table

——————————————

Table Class: 0

Destination Identifier: 0

000 | 4

001 | 5

010 | 3

011 | 2

100 | 6

101 | 1

110 | 0

1110 | 7

11110 | 8

111110 | 9

1111110 | a

11111110 | b

Table Class: 0

Destination Identifier: 1

00 | 1

01 | 0

100 | 2

101 | 3

1100 | 4

1101 | 5

1110 | 6

11110 | 7

111110 | 8

1111110 | 9

11111110 | a

111111110 | b

Table Class: 1

Destination Identifier: 0

00 | 1

01 | 2

100 | 3

1010 | 11

1011 | 4

1100 | 0

11010 | 5

11011 | 21

11100 | 12

111010 | 31

111011 | 41

1111000 | 51

1111001 | 6

1111010 | 13

1111011 | 61

11111000 | 22

11111001 | 71

111110100 | 81

111110101 | 14

111110110 | 32

111110111 | 91

111111000 | a1

111111001 | 7

1111110100 | 15

1111110101 | b1

1111110110 | 42

1111110111 | 23

1111111000 | c1

1111111001 | 52

1111111010 | d1

11111110110 | e1

11111110111 | 33

11111111000 | 16

111111110010 | 62

111111110011 | f0

111111110100 | 24

111111110101 | 72

1111111101100 | 82

1111111101101 | f1

11111111011100 | 25

11111111011101 | 43

11111111011110 | 34

11111111011111 | 53

11111111100000 | 92

11111111100001 | a2

111111111000100 | b2

111111111000101 | 63

1111111110001100 | 73

1111111110001101 | c2

1111111110001110 | 35

1111111110001111 | 44

1111111110010000 | 27

1111111110010001 | 93

1111111110010010 | a3

1111111110010011 | b3

1111111110010100 | 36

1111111110010101 | 17

1111111110010110 | 54

1111111110010111 | 64

1111111110011000 | 74

1111111110011001 | c3

1111111110011010 | d2

1111111110011011 | e2

1111111110011100 | 8

1111111110011101 | 26

1111111110011110 | 83

1111111110011111 | 9

1111111110100000 | a

1111111110100001 | 18

1111111110100010 | 19

1111111110100011 | 84

1111111110100100 | 94

1111111110100101 | 45

1111111110100110 | 46

1111111110100111 | a4

1111111110101000 | b4

1111111110101001 | 56

1111111110101010 | d3

1111111110101011 | 55

1111111110101100 | 28

1111111110101101 | 1a

1111111110101110 | f2

1111111110101111 | e3

1111111110110000 | f3

1111111110110001 | c4

1111111110110010 | d4

1111111110110011 | e4

1111111110110100 | f4

1111111110110101 | 65

1111111110110110 | 75

1111111110110111 | 85

1111111110111000 | 95

1111111110111001 | a5

1111111110111010 | b5

1111111110111011 | c5

1111111110111100 | d5

1111111110111101 | e5

1111111110111110 | f5

1111111110111111 | 66

1111111111000000 | 76

1111111111000001 | 86

1111111111000010 | 96

1111111111000011 | a6

1111111111000100 | b6

1111111111000101 | c6

1111111111000110 | d6

1111111111000111 | e6

1111111111001000 | f6

1111111111001001 | 37

1111111111001010 | 47

1111111111001011 | 57

1111111111001100 | 67

1111111111001101 | 77

1111111111001110 | 87

1111111111001111 | 97

1111111111010000 | a7

1111111111010001 | b7

1111111111010010 | c7

1111111111010011 | d7

1111111111010100 | e7

1111111111010101 | f7

1111111111010110 | 38

1111111111010111 | 48

1111111111011000 | 58

1111111111011001 | 68

1111111111011010 | 78

1111111111011011 | 88

1111111111011100 | 98

1111111111011101 | a8

1111111111011110 | b8

1111111111011111 | c8

1111111111100000 | d8

1111111111100001 | e8

1111111111100010 | f8

1111111111100011 | 29

1111111111100100 | 39

1111111111100101 | 49

1111111111100110 | 59

1111111111100111 | 69

1111111111101000 | 79

1111111111101001 | 89

1111111111101010 | 99

1111111111101011 | a9

1111111111101100 | b9

1111111111101101 | c9

1111111111101110 | d9

1111111111101111 | e9

1111111111110000 | f9

1111111111110001 | 2a

1111111111110010 | 3a

1111111111110011 | 4a

1111111111110100 | 5a

1111111111110101 | 6a

1111111111110110 | 7a

1111111111110111 | 8a

1111111111111000 | 9a

1111111111111001 | aa

1111111111111010 | ba

1111111111111011 | ca

1111111111111100 | da

1111111111111101 | ea

1111111111111110 | fa

Table Class: 1

Destination Identifier: 1

00 | 1

01 | 0

100 | 2

101 | 11

1100 | 3

11010 | 4

11011 | 21

111000 | 12

111001 | 31

111010 | 41

1110110 | 5

1110111 | 51

1111000 | 13

1111001 | 61

1111010 | 22

11110110 | 6

11110111 | 71

11111000 | 81

11111001 | 91

11111010 | 32

111110110 | a1

111110111 | b1

111111000 | f0

111111001 | 14

1111110100 | c1

1111110101 | d1

1111110110 | e1

1111110111 | 23

1111111000 | 42

11111110010 | 15

11111110011 | 52

11111110100 | 62

11111110101 | 72

11111110110 | f1

11111110111 | 33

111111110000 | 24

111111110001 | 34

111111110010 | 43

111111110011 | 82

1111111101000 | 16

1111111101001 | 92

1111111101010 | 53

1111111101011 | 25

1111111101100 | a2

1111111101101 | 63

1111111101110 | b2

1111111101111 | c2

11111111100000 | 7

11111111100001 | 73

11111111100010 | d2

111111111000110 | 35

111111111000111 | e2

111111111001000 | 44

1111111110010010 | 83

1111111110010011 | 17

1111111110010100 | 54

1111111110010101 | 93

1111111110010110 | 8

1111111110010111 | 9

1111111110011000 | a

1111111110011001 | 18

1111111110011010 | 19

1111111110011011 | 26

1111111110011100 | 36

1111111110011101 | 45

1111111110011110 | 1a

1111111110011111 | 27

1111111110100000 | 64

1111111110100001 | 74

1111111110100010 | 55

1111111110100011 | 37

1111111110100100 | f2

1111111110100101 | a3

1111111110100110 | b3

1111111110100111 | c3

1111111110101000 | 28

1111111110101001 | 29

1111111110101010 | d3

1111111110101011 | e3

1111111110101100 | f3

1111111110101101 | 84

1111111110101110 | 94

1111111110101111 | a4

1111111110110000 | b4

1111111110110001 | c4

1111111110110010 | d4

1111111110110011 | e4

1111111110110100 | f4

1111111110110101 | 65

1111111110110110 | 75

1111111110110111 | 85

1111111110111000 | 95

1111111110111001 | a5

1111111110111010 | b5

1111111110111011 | c5

1111111110111100 | d5

1111111110111101 | e5

1111111110111110 | f5

1111111110111111 | 46

1111111111000000 | 56

1111111111000001 | 66

1111111111000010 | 76

1111111111000011 | 86

1111111111000100 | 96

1111111111000101 | a6

1111111111000110 | b6

1111111111000111 | c6

1111111111001000 | d6

1111111111001001 | e6

1111111111001010 | f6

1111111111001011 | 47

1111111111001100 | 57

1111111111001101 | 67

1111111111001110 | 77

1111111111001111 | 87

1111111111010000 | 97

1111111111010001 | a7

1111111111010010 | b7

1111111111010011 | c7

1111111111010100 | d7

1111111111010101 | e7

1111111111010110 | f7

1111111111010111 | 38

1111111111011000 | 48

1111111111011001 | 58

1111111111011010 | 68

1111111111011011 | 78

1111111111011100 | 88

1111111111011101 | 98

1111111111011110 | a8

1111111111011111 | b8

1111111111100000 | c8

1111111111100001 | d8

1111111111100010 | e8

1111111111100011 | f8

1111111111100100 | 39

1111111111100101 | 49

1111111111100110 | 59

1111111111100111 | 69

1111111111101000 | 79

1111111111101001 | 89

1111111111101010 | 99

1111111111101011 | a9

1111111111101100 | b9

1111111111101101 | c9

1111111111101110 | d9

1111111111101111 | e9

1111111111110000 | f9

1111111111110001 | 2a

1111111111110010 | 3a

1111111111110011 | 4a

1111111111110100 | 5a

1111111111110101 | 6a

1111111111110110 | 7a

1111111111110111 | 8a

1111111111111000 | 9a

1111111111111001 | aa

1111111111111010 | ba

1111111111111011 | ca

1111111111111100 | da

1111111111111101 | ea

1111111111111110 | fa

============================

Segment Length: 12 bytes

Start Of Scan

——————————————

Image Conponents in Scan: 3

Size Of Scan: 9 bytes

Size Of Scan (after remove bitstuff): 8 bytes

Total MCU count: 2

MCU: 0

Component Selector: 1

Quantization Table Destination Selector: 0

DC Entropy Coding Table Destination Selector: 0

AC Entropy Coding Table Destination Selector: 0

DC Code: a

DC Bit Length: 10

DC Value: -512

Found EOB when decode AC!

Extracted Component[0] 8x8 block:

-512,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After Quantization:

-1024,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After IDCT:

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

-128,-128,-128,-128,-128,-128,-128,-128

Component Selector: 2

Quantization Table Destination Selector: 1

DC Entropy Coding Table Destination Selector: 1

AC Entropy Coding Table Destination Selector: 1

Found EOB when decode DC!

DC Code: 0

DC Bit Length: 0

DC Value: 0

Found EOB when decode AC!

Extracted Component[1] 8x8 block:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After Quantization:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After IDCT:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

Component Selector: 3

Quantization Table Destination Selector: 1

DC Entropy Coding Table Destination Selector: 1

AC Entropy Coding Table Destination Selector: 1

Found EOB when decode DC!

DC Code: 0

DC Bit Length: 0

DC Value: 0

Found EOB when decode AC!

Extracted Component[2] 8x8 block:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After Quantization:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After IDCT:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

MCU: 1

Component Selector: 1

Quantization Table Destination Selector: 0

DC Entropy Coding Table Destination Selector: 0

AC Entropy Coding Table Destination Selector: 0

DC Code: a

DC Bit Length: 10

DC Value: 508

Found EOB when decode AC!

Extracted Component[0] 8x8 block:

508,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After Quantization:

1016,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After IDCT:

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

127,127,127,127,127,127,127,127

Component Selector: 2

Quantization Table Destination Selector: 1

DC Entropy Coding Table Destination Selector: 1

AC Entropy Coding Table Destination Selector: 1

Found EOB when decode DC!

DC Code: 0

DC Bit Length: 0

DC Value: 0

Found EOB when decode AC!

Extracted Component[1] 8x8 block:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After Quantization:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After IDCT:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

Component Selector: 3

Quantization Table Destination Selector: 1

DC Entropy Coding Table Destination Selector: 1

AC Entropy Coding Table Destination Selector: 1

Found EOB when decode DC!

DC Code: 0

DC Bit Length: 0

DC Value: 0

Found EOB when decode AC!

Extracted Component[2] 8x8 block:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After Quantization:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

After IDCT:

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

0,0,0,0,0,0,0,0

============================

Segment Length: 0 bytes

End Of Scan

——————————————

Image

——-

Width: 16

Height: 8

Bit Count: 24

Pitch: 48

Data Size: 384

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff

參考引用

JPEG File Interchange Format

https://www。

w3。org/Graphics/JPEG/it

u-t81。pdf

JPEG Huffman Coding Tutorial

JPEG - Wikipedia

Huffman coding - Wikipedia

YUV - Wikipedia