天天看點

實驗五 JPEG

JPEG編解碼原理及代碼實作

文章目錄

  • ​​JPEG編解碼原理及代碼實作​​
  • ​​一、JPEG編解碼原理​​
  • ​​編碼原理​​
  • ​​1. Level offset 零偏置電平下移​​
  • ​​2. 8x8 DCT​​
  • ​​3. Uniform scalar quantization 均勻标稱量化​​
  • ​​4. DC系數差分編碼​​
  • ​​5. AC系數Zig-Zag掃描與遊程編碼​​
  • ​​6. Huffman編碼​​
  • ​​解碼原理​​
  • ​​二、JPEG檔案格式分析​​
  • ​​三、代碼實作​​
  • ​​1. 分層結構​​
  • ​​2. 解碼流程​​
  • ​​3. 核心子產品​​

JPEG(Joint Photographic Experts Group)是聯合圖像專家組的英文縮寫。 該組織從1986年正式開始制訂靜止數字圖像的壓縮編碼标準,該标準于1992年正式通過,稱為JPEG标準。JPEG是第一個數字圖像壓縮的國際标準,它不僅适于靜止圖像的壓縮,對 于電視圖像序列的幀内壓縮也常采用JPEG算法,是以JPEG是一個适用範圍廣泛的通用标準。

一、JPEG編解碼原理

編解碼基本框圖為:

實驗五 JPEG

編碼原理

JPEG标準本身并沒有規定具體的顔色空間,隻是對各顔色的分量分别進行編碼。為了減少各分量之間的相關性,減少資料的備援,通常會把RGB顔色空間轉換成YUV來進行各分量的編碼。

1. Level offset 零偏置電平下移

該步驟的作用是,圖像内容平均亮度較高,将0電平移到中間,平均亮度降低, 便于DCT變換量化後直流的系數大大降低,也就降低了資料量。

先對8×8的像塊進行零偏置電平下移(Level Offset),即對于灰階級為的像素,通過減去,将無符号整數變為有符号數,使其值域變為,以将絕對值大的數出現的機率大大減小,提高編碼效率。

2. 8x8 DCT

該步驟主要是用于去除圖像資料之間的相關性,便于量化過程去除圖像資料的空間備援。DCT是一種無損變換,也無法對圖像進行壓縮,這樣做的目的是在為下一步的量化做準備。

将圖像分為8×8的像塊;對于寬(高)不是8的整數倍的圖像,使用圖像邊緣像素填充,以不改變頻譜分布。然後對每一個子塊進行DCT(Discrete Cosine Transform,離散餘弦變換),以實作能量集中和去相關,便于去除空間備援,提高編碼效率。需要特别強調的是,DCT是一種無損變換,也無法對圖像進行壓縮,這樣做的目的是在為下一步的量化做準備。

其中,是的DCT變換二維核矩陣,是原始的資料。

由于DCT變換是一個正交變換,故有

3. Uniform scalar quantization 均勻标稱量化

量化器主要是利用人眼視覺特性設計而成的矩陣量化DCT系數,減少視覺備援。将DCT變換後的臨時結果,除以各自量化步長并四舍五入後取整,得到量化系數。JPEG系統分别規定了亮度分量和色度分量的量化表,色度分量相應的量化步長比亮度分量大。在量化步驟中,JPEG采用了中平型(Midtread)的均勻量化器。

實際上JPEG壓縮編碼算法中,真正可供調整的部分并不多:DCT、熵編碼這兩個主要步驟都是完全确定的了,實際上隻有量化可以調整,量化也自然是JPEG壓縮編碼算法的核心。此外,量化是編碼流程中唯一會引入誤差也是唯一會帶來壓縮的步驟。

實驗五 JPEG

JPEG标準中采用中平型均勻量化,由于人眼對低頻分量的敏感程度遠高于高頻分量,且對亮度的敏感程度遠高于色度,因而标準中據此設計了2張量化表(亮度、色差各一張),使低頻細量化,高頻粗量化,亮度細量化,色差粗量化,以減少視覺備援。

量化矩陣并不是固定的,可以根據要求的品質的不同而進行調整。

4. DC系數差分編碼

我們注意到,8×8像塊經過DCT後得到的DC系數有兩個特點:一是系數的值較大;二是相鄰像塊的DC系數內插補點不大(即存在備援)。根據這個特點,JPEG标準采用了DPCM(差分脈沖編碼調制),以對相鄰圖像塊之間量化DC系數的內插補點DIFF進行編碼。

對DPCM後算出的DIFF內插補點使用Huffman編碼,将其分成類别,類似于指數的Golomb編碼(隻不過Golomb是一進制碼+定長碼),也就是類别ID使用規範哈夫曼編碼,類内索引使用定長碼(自然碼)。

5. AC系數Zig-Zag掃描與遊程編碼

由于DCT後,系數大多數集中在左上角,即低頻分量區,是以采用Zig-Zag(之字形)掃描,将系數按頻率的高低順序讀出,這樣可以出現很多連零的機會,便于進行RLE(Run Length Encoding,遊程編碼),尤其在最後,如果都是零,給出EOB (End of Block)即可。

實驗五 JPEG
實驗五 JPEG

例如0, -2, -1, -1, -1, 0, 0, -1, EOB表示為:(1, -2), (0, -1), (0, -1), (0, -1), (2, -1), EOB。

6. Huffman編碼

對DC系數DPCM的結果和AC系數RLE的結果進行Huffman編碼,類别ID采用一進制碼編碼,類内索引采用定長碼編碼。以DC系數為例:

實驗五 JPEG

例如內插補點DIFF = 3 ,對應的類别ID = 2,類内索引 = 3,則碼字為100 11。

共有亮度DC、亮度AC、色差DC、色差AC四張Huffman編碼表。

解碼原理

解碼完全是編碼的逆過程,解碼系統框圖:

實驗五 JPEG

二、JPEG檔案格式分析

JPEG檔案以segment的形式組織,其中每個segment以一個marker開始,而每個marker均以0xFF和一個marker的辨別符開始,随後為2位元組的marker長度(不包含marker的起始兩位元組)和對應的payload(SOI和EOI marker隻有2位元組的辨別符)。

注意,連續的0xFF位元組并不是marker的起始标志,而是用來填充的特殊字元。

此外,部分中,0xFF後若為0x00,則跳過此位元組不予處理。

常見的marker如下:

Short name Bytes Payload Name Comments
SOI 0xFF, 0xD8 none Start Of Image
SOF0 0xFF, 0xC0 variable size Start Of Frame (baseline DCT) Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).
SOF2 0xFF, 0xC2 variable size Start Of Frame (progressive DCT) Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, and component subsampling (e.g., 4:2:0).
DHT 0xFF, 0xC4 variable size Define Huffman Table(s) Specifies one or more Huffman tables.
DQT 0xFF, 0xDB variable size Define Quantization Table(s) Specifies one or more quantization tables.
DRI 0xFF, 0xDD 4 bytes Define Restart Interval Specifies the interval between RSTn markers, in Minimum Coded Units (MCUs). This marker is followed by two bytes indicating the fixed size so it can be treated like any other variable size segment.
SOS 0xFF, 0xDA variable size Start Of Scan Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it will contain, and is immediately followed by entropy-coded data.
RSTn 0xFF, 0xDn none Restart Inserted every r rr macroblocks, where r rr is the restart interval set by a DRI marker. Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7.
APPn 0xFF, 0xEn variable size Application-specific For example, an Exif JPEG file uses an APP1 marker to store metadata, laid out in a structure based closely on TIFF.
COM 0xFF, 0xFE variable size Comment Contains a text comment.
EOI 0xFF, 0xD9 none End Of Image

下面使用Synalyze it! Pro App進行二進制分析,并對一些marker字段作一些簡要說明。

實驗五 JPEG

實驗圖檔test.jpg(1024x1024)

  • SOI與EOI
  • 實驗五 JPEG
  • APP0
  • 實驗五 JPEG
  • DQT
  • 實驗五 JPEG
  • SOF0
  • 實驗五 JPEG
  • DHT
  • 實驗五 JPEG
  • SOS
  • 實驗五 JPEG
  • SOS
  • 實驗五 JPEG

三、代碼實作

1. 分層結構

JPEG壓縮編碼算法的一大特點就是采用了分層結構設計的思想,下面說明三個主要結構體的設計意圖:

struct huffman_table:存儲Huffman碼表。

/* tinyjpeg-internal.h */

struct huffman_table
{
  /* Fast look up table, using HUFFMAN_HASH_NBITS bits we can have directly the symbol,
   * if the symbol is <0, then we need to look into the tree table */
  short int lookup[HUFFMAN_HASH_SIZE];

  /* code size: give the number of bits of a symbol is encoded */
  unsigned char code_size[HUFFMAN_HASH_SIZE];
  /* some place to store value that is not encoded in the lookup table 
   * FIXME: Calculate if 256 value is enough to store all values
   */
  uint16_t slowtable[16-HUFFMAN_HASH_NBITS][256];
};      

struct component:儲存目前8×8像塊中有關解碼的資訊。

/* tinyjpeg-internal.h */

struct component 
{
  unsigned int Hfactor; // 水準采樣因子
  unsigned int Vfactor; // 垂直采樣因子
  float* Q_table;   // 指向該8×8塊使用的量化表
  struct huffman_table *AC_table;   // 指向該塊使用的AC Huffman表
  struct huffman_table *DC_table;   // 指向該塊使用的DC Huffman表
  short int previous_DC;    // 前一個塊的直流DCT系數
  short int DCT[64];    // DCT系數數組
    
#if SANITY_CHECK
  unsigned int cid;
#endif
};      

struct jdec_private:JPEG資料流結構體,用于存儲JPEG圖像寬高、資料流指針、Huffman碼表等内容,并包含struct huffman_table和struct component。

/* tinyjpeg-internal.h */

struct jdec_private
{
  /* Public variables */
  uint8_t *components[COMPONENTS];  /* 分别指向YUV三個分量的三個指針 */
  unsigned int width, height;  /* 圖像寬高 */
  unsigned int flags;

  /* Private variables */
  const unsigned char *stream_begin, *stream_end;
  unsigned int stream_length;

  const unsigned char *stream;  /* 指向目前資料流的指針 */
  unsigned int reservoir, nbits_in_reservoir;

  struct component component_infos[COMPONENTS];
  float Q_tables[COMPONENTS][64];    /* quantization tables */
  struct huffman_table HTDC[HUFFMAN_TABLES];  /* DC huffman tables */
  struct huffman_table HTAC[HUFFMAN_TABLES];  /* AC huffman tables */
  int default_huffman_table_initialized;
  int restart_interval;
  int restarts_to_go;        /* MCUs left in this restart interval */
  int last_rst_marker_seen;      /* Rst marker is incremented each time */

  /* Temp space used after the IDCT to store each components */
  uint8_t Y[64*4], Cr[64], Cb[64];

  jmp_buf jump_state;
  /* Internal Pointer use for colorspace conversion, do not modify it !!! */
  uint8_t *plane[COMPONENTS];
};      

2. 解碼流程

/* 讀取JPEG檔案,進行解碼,并存儲結果 */
int convert_one_image(const char *infilename, const char *outfilename, int output_format)
{
  FILE *fp;
  unsigned int length_of_file;  // 檔案大小
  unsigned int width, height;   // 圖像寬、高
  unsigned char *buf;   // 緩沖區
  struct jdec_private *jdec;
  unsigned char *components[3];

  /* 将JPEG讀入緩沖區 */
  fp = fopen(infilename, "rb");
  if (fp == NULL)
    exitmessage("Cannot open filename\n");
  length_of_file = filesize(fp);
  buf = (unsigned char *)malloc(length_of_file + 4);
  if (buf == NULL)
    exitmessage("Not enough memory for loading file\n");
  fread(buf, length_of_file, 1, fp);
  fclose(fp);

  /* Decompress it */
  jdec = tinyjpeg_init();   // 初始化
  if (jdec == NULL)
    exitmessage("Not enough memory to alloc the structure need for decompressing\n");

  /* 解析JPEG檔案頭 */
  if (tinyjpeg_parse_header(jdec, buf, length_of_file)<0)
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* 計算圖像寬高 */
  tinyjpeg_get_size(jdec, &width, &height);

  snprintf(error_string, sizeof(error_string),"Decoding JPEG image...\n");
  if (tinyjpeg_decode(jdec, output_format) < 0) // 解碼實際資料
    exitmessage(tinyjpeg_get_errorstring(jdec));

  /* 
   * Get address for each plane (not only max 3 planes is supported), and
   * depending of the output mode, only some components will be filled 
   * RGB: 1 plane, YUV420P: 3 planes, GREY: 1 plane
   */
  tinyjpeg_get_components(jdec, components);

  /* 按照指定的輸出格式儲存輸出檔案 */
  switch (output_format)
   {
    case TINYJPEG_FMT_RGB24:
    case TINYJPEG_FMT_BGR24:
      write_tga(outfilename, output_format, width, height, components);
      break;
    case TINYJPEG_FMT_YUV420P:
      write_yuv(outfilename, width, height, components);
      break;
    case TINYJPEG_FMT_GREY:
      write_pgm(outfilename, width, height, components);
      break;
   }

  /* Only called this if the buffers were allocated by tinyjpeg_decode() */
  tinyjpeg_free(jdec);
  /* else called just free(jdec); */

  free(buf);
  return 0;
}      

3. 核心子產品

解析JPEG檔案頭

int tinyjpeg_parse_header(struct jdec_private *priv, const unsigned char *buf, unsigned int size)
{
  int ret;

  /* Identify the file */
  if ((buf[0] != 0xFF) || (buf[1] != SOI))  // JPEG檔案必須以SOI marker為起始,否則不是合法的JPEG檔案
    snprintf(error_string, sizeof(error_string),"Not a JPG file ?\n");

  priv->stream_begin = buf+2;   // 跳過辨別符
  priv->stream_length = size-2;
  priv->stream_end = priv->stream_begin + priv->stream_length;

  ret = parse_JFIF(priv, priv->stream_begin);   // 開始解析JPEG

  return ret;
}      

解析marker辨別符

/* 略去了trace部分 */

static int parse_JFIF(struct jdec_private *priv, const unsigned char *stream)
{
  int chuck_len;
  int marker;
  int sos_marker_found = 0;
  int dht_marker_found = 0;
  const unsigned char *next_chunck;

  /* Parse marker */
  while (sos_marker_found == 0)
   {
     if (*stream++ != 0xff)
       goto bogus_jpeg_format;
     /* Skip any padding ff byte (this is normal) */
     while (*stream == 0xff)
       stream++;

     marker = *stream++;    // 擷取0xFF後的一個位元組(即為marker辨別符)
     chuck_len = be16_to_cpu(stream);   // length字段
     next_chunck = stream + chuck_len;
     switch (marker)    // 判斷marker類型
      {
       case SOF:
   if (parse_SOF(priv, stream) < 0)
     return -1;
   break;
       case DQT:
   if (parse_DQT(priv, stream) < 0)
     return -1;
   break;
       case SOS:
   if (parse_SOS(priv, stream) < 0)
     return -1;
   sos_marker_found = 1;
   break;
       case DHT:
   if (parse_DHT(priv, stream) < 0)
     return -1;
   dht_marker_found = 1;
   break;
       case DRI:
   if (parse_DRI(priv, stream) < 0)
     return -1;
   break;
       default:
   break;
      }

     stream = next_chunck;  // 解析下一個marker
   }

  if (!dht_marker_found) {
    build_default_huffman_tables(priv);
  }
  return 0;
    
bogus_jpeg_format:
  return -1;
}      

解析DQT

static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
  int qi;   // 量化表ID
  float *table; // 指向量化表
  const unsigned char *dqt_block_end;   // 指向量化表結束位置
  dqt_block_end = stream + be16_to_cpu(stream);
  stream += 2;  // 跳過長度字段

  while (stream < dqt_block_end)  // 檢查是否還有量化表
   {
     qi = *stream++;    // 将量化表中系數逐個賦給qi
     table = priv->Q_tables[qi];
     build_quantization_table(table, stream);
     stream += 64;
   }
  return 0;
}      

建立量化表

static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
  int i, j;
  static const double aanscalefactor[8] = {
     1.0, 1.387039845, 1.306562965, 1.175875602,
     1.0, 0.785694958, 0.541196100, 0.275899379
  };    // 比例因子
  const unsigned char *zz = zigzag;

  for (i=0; i<8; i++) {
     for (j=0; j<8; j++) {
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
     }
   }
}      

解析DHT

static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
  unsigned int count, i;
  unsigned char huff_bits[17];  // 碼長1~16
  int length, index;

  length = be16_to_cpu(stream) - 2;
  stream += 2;  // 跳過長度字段

  while (length>0) {    // 檢查是否還有表
     index = *stream++;

     /* We need to calculate the number of bytes 'vals' will takes */
     huff_bits[0] = 0;
     count = 0;
     for (i=1; i<17; i++) {
      huff_bits[i] = *stream++;
      count += huff_bits[i];
     }

     if (index & 0xf0 )
       build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);  // 建立交流表
     else
       build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);  // 建立直流表

     length -= 1;
     length -= 16;
     length -= count;
     stream += count;
  }
  return 0;
}      

建立Huffman碼表

static void build_huffman_table(const unsigned char *bits, const unsigned char *vals, struct huffman_table *table)  // bits為各個位數位字的數量,val為Huffval,table為要建立的Huffman表
{
  unsigned int i, j, code, code_size, val, nbits;
  unsigned char huffsize[HUFFMAN_BITS_SIZE + 1];    // 每個碼字的長度
  unsigned char* hz;
  unsigned int huffcode[HUFFMAN_BITS_SIZE + 1]; // 每個碼字
  unsigned char* hc;
  int next_free_entry;

  /* 初始化 */
  hz = huffsize;
  for (i=1; i<=16; i++)
   {
     for (j=1; j<=bits[i]; j++)
       *hz++ = i;
   }
  *hz = 0;

  memset(table->lookup, 0xff, sizeof(table->lookup));
  for (i=0; i<(16-HUFFMAN_HASH_NBITS); i++)
    table->slowtable[i][0] = 0;

  code = 0;
  hc = huffcode;
  hz = huffsize;
  nbits = *hz;
  while (*hz)
   {
     while (*hz == nbits)
      {
  *hc++ = code++;
  hz++;
      }
     code <<= 1;
     nbits++;
   }

  /*
   * Build the lookup table, and the slowtable if needed.
   */
  next_free_entry = -1;
  for (i=0; huffsize[i] != 0; i++)
   {
     /* 得到Huffval、每個碼字、每個碼字的長度*/
     val = vals[i];
     code = huffcode[i];
     code_size = huffsize[i];
     table->code_size[val] = code_size; // Huffval(權值)
     if (code_size <= HUFFMAN_HASH_NBITS)
      {
  /*
   * Good: val can be put in the lookup table, so fill all value of this
   * column with value val 
   */
  int repeat = 1UL<<(HUFFMAN_HASH_NBITS - code_size);
  code <<= HUFFMAN_HASH_NBITS - code_size;
  while ( repeat-- )
    table->lookup[code++] = val;  // 得到Huffval長度的查找表
      }
     else
      {
  /* Perhaps sorting the array will be an optimization */
  uint16_t *slowtable = table->slowtable[code_size-HUFFMAN_HASH_NBITS-1];
  while(slowtable[0])
    slowtable+=2;
  slowtable[0] = code;
  slowtable[1] = val;
  slowtable[2] = 0;
  /* TODO: NEED TO CHECK FOR AN OVERFLOW OF THE TABLE */
      }
   }
}      

解析SOS

static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
  unsigned int i, cid, table;
  unsigned int nr_components = stream[2];   // 顔色分量數

  stream += 3;
  for (i=0;i<nr_components;i++) {
     /* 得到使用的Huffmann表号 */
     cid = *stream++;
     table = *stream++;
      
     priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
     priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
  }
  priv->stream = stream+3;
  return 0;
}      

解析SOF

static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)
{
  int i, width, height, nr_components, cid, sampling_factor;
  int Q_table;
  struct component *c;

  print_SOF(stream);

  height = be16_to_cpu(stream+3);   // 圖像高度
  width  = be16_to_cpu(stream+5);   // 圖像寬度
  nr_components = stream[7];    // 顔色分量數

  stream += 8;
  for (i=0; i<nr_components; i++) {
     /* 分别解析各分量 */
     cid = *stream++;   // 分量ID
     sampling_factor = *stream++;   // 采樣因子
     Q_table = *stream++;
     c = &priv->component_infos[i];
     c->Vfactor = sampling_factor&0xf;  // 垂直采樣因子
     c->Hfactor = sampling_factor>>4;   // 水準采樣因子
     c->Q_table = priv->Q_tables[Q_table];  // 使用的量化表
  }
  priv->width = width;
  priv->height = height;

  return 0;
}      

解析JPEG實際資料

int tinyjpeg_decode(struct jdec_private *priv, int pixfmt)  // pixfmt為輸出格式
{
  unsigned int x, y, xstride_by_mcu, ystride_by_mcu;
  unsigned int bytes_per_blocklines[3], bytes_per_mcu[3];
  decode_MCU_fct decode_MCU;
  const decode_MCU_fct *decode_mcu_table;
  const convert_colorspace_fct *colorspace_array_conv;
  convert_colorspace_fct convert_to_pixfmt;

  if (setjmp(priv->jump_state))
    return -1;

  /* To keep gcc happy initialize some array */
  bytes_per_mcu[1] = 0;
  bytes_per_mcu[2] = 0;
  bytes_per_blocklines[1] = 0;
  bytes_per_blocklines[2] = 0;

  decode_mcu_table = decode_mcu_3comp_table;
  switch (pixfmt) {
     /* 根據不同的輸出格式确定MCU */
     case TINYJPEG_FMT_YUV420P:
       colorspace_array_conv = convert_colorspace_yuv420p;
       if (priv->components[0] == NULL)
   priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
       if (priv->components[1] == NULL)
   priv->components[1] = (uint8_t *)malloc(priv->width * priv->height/4);
       if (priv->components[2] == NULL)
   priv->components[2] = (uint8_t *)malloc(priv->width * priv->height/4);
       bytes_per_blocklines[0] = priv->width;
       bytes_per_blocklines[1] = priv->width/4;
       bytes_per_blocklines[2] = priv->width/4;
       bytes_per_mcu[0] = 8;
       bytes_per_mcu[1] = 4;
       bytes_per_mcu[2] = 4;
       break;

     case TINYJPEG_FMT_RGB24:
       colorspace_array_conv = convert_colorspace_rgb24;
       if (priv->components[0] == NULL)
   priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_BGR24:
       colorspace_array_conv = convert_colorspace_bgr24;
       if (priv->components[0] == NULL)
   priv->components[0] = (uint8_t *)malloc(priv->width * priv->height * 3);
       bytes_per_blocklines[0] = priv->width * 3;
       bytes_per_mcu[0] = 3*8;
       break;

     case TINYJPEG_FMT_GREY:
       decode_mcu_table = decode_mcu_1comp_table;
       colorspace_array_conv = convert_colorspace_grey;
       if (priv->components[0] == NULL)
   priv->components[0] = (uint8_t *)malloc(priv->width * priv->height);
       bytes_per_blocklines[0] = priv->width;
       bytes_per_mcu[0] = 8;
       break;

     default:
       return -1;
  }

  xstride_by_mcu = ystride_by_mcu = 8;  // 初始化:MCU的寬高均為8px(4:4:4)
  if ((priv->component_infos[cY].Hfactor | priv->component_infos[cY].Vfactor) == 1) {
     /* 水準、垂直采樣因子均為1 */
     decode_MCU = decode_mcu_table[0];  // MCU包含1個Y
     convert_to_pixfmt = colorspace_array_conv[0];
  } else if (priv->component_infos[cY].Hfactor == 1) {
     /* 水準采樣因子為1,垂直采樣因子為2 */
     decode_MCU = decode_mcu_table[1];  // MCU包含2個Y
     convert_to_pixfmt = colorspace_array_conv[1];
     ystride_by_mcu = 16;   // MCU高16px,寬8px
  } else if (priv->component_infos[cY].Vfactor == 2) {
     /* 水準、垂直采樣因子均為2 */
     decode_MCU = decode_mcu_table[3];  // MCU包含4個Y
     convert_to_pixfmt = colorspace_array_conv[3];
     xstride_by_mcu = 16;   // MCU寬16px
     ystride_by_mcu = 16;   // MCU高16px
  } else {
     /* 水準采樣因子為2,垂直采樣因子為1 */
     decode_MCU = decode_mcu_table[2];  // MCU包含2個Y
     convert_to_pixfmt = colorspace_array_conv[2];
     xstride_by_mcu = 16;   // MCU寬16px,高8px
  }

  resync(priv);

  /* Don't forget to that block can be either 8 or 16 lines */
  bytes_per_blocklines[0] *= ystride_by_mcu;
  bytes_per_blocklines[1] *= ystride_by_mcu;
  bytes_per_blocklines[2] *= ystride_by_mcu;

  bytes_per_mcu[0] *= xstride_by_mcu/8;
  bytes_per_mcu[1] *= xstride_by_mcu/8;
  bytes_per_mcu[2] *= xstride_by_mcu/8;

  /* 對每個像塊進行解碼(8x8 / 8x16 / 16x16) */
  for (y=0; y < priv->height/ystride_by_mcu; y++)
   {
     //trace("Decoding row %d\n", y);
     priv->plane[0] = priv->components[0] + (y * bytes_per_blocklines[0]);
     priv->plane[1] = priv->components[1] + (y * bytes_per_blocklines[1]);
     priv->plane[2] = priv->components[2] + (y * bytes_per_blocklines[2]);
     for (x=0; x < priv->width; x+=xstride_by_mcu)
      {
  decode_MCU(priv);
  convert_to_pixfmt(priv);
  priv->plane[0] += bytes_per_mcu[0];
  priv->plane[1] += bytes_per_mcu[1];
  priv->plane[2] += bytes_per_mcu[2];
  if (priv->restarts_to_go>0)
   {
     priv->restarts_to_go--;
     if (priv->restarts_to_go == 0)
      {
        priv->stream -= (priv->nbits_in_reservoir/8);
        resync(priv);
        if (find_next_rst_marker(priv) < 0)
    return -1;
      }
   }
      }
   }

  return 0;
}      

解析MCU

/*
 * Decode a 2x2
 *  .-------.
 *  | 1 | 2 |
 *  |---+---|
 *  | 3 | 4 |
 *  `-------'
 */
static void decode_MCU_2x2_3planes(struct jdec_private *priv)
{
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}      

将檔案輸出為.yuv格式

static void write_yuv(const char* filename, int width, int height, unsigned char** components) {
    FILE* F;
    char temp[1024];

    snprintf(temp, 1024, "%s.Y", filename);
    F = fopen(temp, "wb");
    fwrite(components[0], width, height, F);
    fclose(F);
    snprintf(temp, 1024, "%s.U", filename);
    F = fopen(temp, "wb");
    fwrite(components[1], width * height / 4, 1, F);
    fclose(F);
    snprintf(temp, 1024, "%s.V", filename);
    F = fopen(temp, "wb");
    fwrite(components[2], width * height / 4, 1, F);
    fclose(F);

    snprintf(temp, 1024, "%s.YUV", filename);
    F = fopen(temp, "wb");
    fwrite(components[0], width, height, F);
    fwrite(components[1], width * height / 4, 1, F);
    fwrite(components[2], width * height / 4, 1, F);
    fclose(F);
}      

輸出yuv檔案如下:

實驗五 JPEG

輸出量化矩陣和Huffman碼表

/* tinyjpeg.h中添加 */
/* 聲明全局變量 by S.Z.Zheng */
    FILE* qtabFilePtr;    // 量化表檔案指針
/* 聲明結束 */


/* tinyjpeg.c中添加*/
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
  ...
  for (i=0; i<8; i++) {
      for (j=0; j<8; j++) {
          /* Added by S.Z.Zheng */
          fprintf(qtabFilePtr, "%-6d", ref_table[*zz]);
          if (j == 7) {
              fprintf(qtabFilePtr, "\n");
          }
          /* Addition ended */
     ...
      }
  }
  fprintf(qtabFilePtr, "\n\n"); // Added by S.Z.Zheng
}

static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
    ...
    while (stream < dqt_block_end)    // 檢查是否還有量化表
    {
        ...
        fprintf(qtabFilePtr, "Quantisation table [%d]:\n", qi);   // 量化表ID(added by S.Z.Zheng)
        build_quantization_table(table, stream);
        ...
    }
  ...
}


/* loadjpeg.c中添加 */
int main(int argc, char *argv[])
{
  ...
  /* Added by S.Z.Zheng */
  const char* qtabFileName = "q_table.txt"; // 量化表檔案名
  fopen_s(&qtabFilePtr, qtabFileName, "wb");    // 打開檔案
  /* Addition ended */
  ...
  fclose(qtabFilePtr);  // Added by S.Z.Zheng
  return 0;
}      
實驗五 JPEG
/* tinyjpeg.h中添加 */
/* 聲明全局變量 by S.Z.Zheng */
    ...
    FILE* dcImgFilePtr; // DC圖像檔案指針
    FILE* acImgFilePtr; // AC圖像檔案指針
/* 聲明結束 */

/* tinyjpeg.c中添加 */
int tinyjpeg_decode(struct jdec_private* priv, int pixfmt)
{
    ...
    /* Added by S.Z.Zheng */
    unsigned char* dcImgBuff;
    unsigned char* acImgBuff;
    unsigned char* uvBuff = 128;
    int count = 0;
    /* Addition ended*/

    /* 對每個像塊進行解碼(8x8 / 8x16 / 16x16) */
    for (y = 0; y < priv->height / ystride_by_mcu; y++) {
        ...
        for (x = 0; x < priv->width; x += xstride_by_mcu) {
            decode_MCU(priv);

            dcImgBuff = (unsigned char)((priv->component_infos->DCT[0] + 512.0) / 4 + 0.5);  // DCT[0]為DC系數;DC系數範圍-512~512;變換到0~255
            acImgBuff = (unsigned char)(priv->component_infos->DCT[1] + 128);   // 選取DCT[1]作為AC的observation;+128便于觀察
            fwrite(&dcImgBuff, 1, 1, dcImgFilePtr);
            fwrite(&acImgBuff, 1, 1, acImgFilePtr);
            count++;
            ...
                }
            }
        }
    }
    ...
    /* Added by S.Z.Zheng */
    for (int i = 0; i < count / 4 * 2; i++) {
        fwrite(&uvBuff, sizeof(unsigned char), 1, dcImgFilePtr);
        fwrite(&uvBuff, sizeof(unsigned char), 1, acImgFilePtr);
    }
    /* Addition ended */
    return 0;
}

/* loadjpeg.c中添加 */
int main(int argc, char *argv[]) {
  ...
  /* Added by S.Z.Zheng */
  ...
  const char* dcImgFileName = "test_decoded_dc.yuv";    // DC圖像檔案名
  const char* acImgFileName = "test_decoded_ac.yuv";    // AC圖像檔案名
  ...
  fopen_s(&dcImgFilePtr, dcImgFileName, "wb");    // 打開DC圖像檔案
  fopen_s(&acImgFilePtr, acImgFileName, "wb");    // 打開AC圖像檔案
  /* Addition ended */
  ...
  /* Added by S.Z.Zheng */
  ...
  fclose(dcImgFilePtr);
  fclose(acImgFilePtr);
  /* Addition Ended */

  return 0;
}