反量化
反量化其實很簡單,將霍夫曼解碼出來的資料乘上對應的量化表就好了
透過當前色度選擇出SOF中的Component,其中的Tqi指出了這一色度所需的量化表id
Component的結構如下:
名稱 | 長度(bit) | 備註 |
---|---|---|
Ci | 8 | Compoenent的id |
Hi | 4 | 水平縮放因子 |
Vi | 4 | 垂直縮放因子 |
Tqi | 8 | 對應的量化表id |
然後就可以根據量化表id找到量化表,將其中每一個元素與霍夫曼解碼的結果相乘就OK了
// 就是簡單粗暴的直接相乘
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; ++j) {
result[i][j] = input[i][j] * dqt[i][j];
}
}
逆ZigZag變換
逆ZigZag變換實際上和ZigZag變換做的是同樣的操作,直接按Z字形重新排序就好
參考程式碼
// 直接查表大法
const int zigzag_table[8*8][2] = {
{0, 0}, {0, 1}, {1, 0}, {2, 0}, {1, 1}, {0, 2}, {0, 3}, {1, 2},
{2, 1}, {3, 0}, {4, 0}, {3, 1}, {2, 2}, {1, 3}, {0, 4}, {0, 5},
{1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}, {6, 0}, {5, 1}, {4, 2},
{3, 3}, {2, 4}, {1, 5}, {0, 6}, {0, 7}, {1, 6}, {2, 5}, {3, 4},
{4, 3}, {5, 2}, {6, 1}, {7, 0}, {7, 1}, {6, 2}, {5, 3}, {4, 4},
{3, 5}, {2, 6}, {1, 7}, {2, 7}, {3, 6}, {4, 5}, {5, 4}, {6, 3},
{7, 2}, {7, 3}, {6, 4}, {5, 5}, {4, 6}, {3, 7}, {4, 7}, {5, 6},
{6, 5}, {7, 4}, {7, 5}, {6, 6}, {5, 7}, {6, 7}, {7, 6}, {7, 7}
};
void zigzag_transform(int input[8][8], int output[8][8]) {
for (int i = 0; i < 8*8; ++i) {
int row = zigzag_table[i][0];
int col = zigzag_table[i][1];
output[row][col] = input[i/8][i%8];
}
}
IDCT變換
最後再進行IDCT2D,IDCT變換就是DCT變換的逆變換,IDCT2D就是橫著變換一次再豎著變換一次。這個更多是數學上的東西我就不多說了,怕誤人子弟
話不多說,上程式碼:
pub struct DCT {
pub idct2d_data: [[[[f32; 8]; 8]; 8]; 8],
}
fn cc(i: usize, j: usize) -> f32 {
if i == 0 && j == 0 {
return 1.0 / 2.0;
} else if i == 0 || j == 0 {
return 1.0 / (2.0 as f32).sqrt();
} else {
return 1.0;
}
}
impl DCT {
// 先提前算好一部分
pub fn new() -> DCT {
let mut tmp: [[[[f32; 8]; 8]; 8]; 8] = Default::default();
for i in 0..8 {
for j in 0..8 {
for x in 0..8 {
let i_cos = ((2 * i + 1) as f32 * PI / 16.0 * x as f32).cos();
for y in 0..8 {
let j_cos = ((2 * j + 1) as f32 * PI / 16.0 * y as f32).cos();
tmp[i][j][x][y] = cc(x, y) * i_cos * j_cos / 4.0;
}
}
}
}
DCT { idct2d_data: tmp }
}
pub fn idct2d(&self, data: [[f32; 8]; 8]) -> [[f32; 8]; 8] {
let mut tmp: [[f32; 8]; 8] = Default::default();
for i in 0..8 {
for j in 0..8 {
tmp[i][j] = {
let mut tmp = 0.0;
for x in 0..8 {
for y in 0..8 {
tmp += self.idct2d_data[i][j][x][y] * data[x][y];
}
}
tmp
};
}
}
tmp
}
// 後面還有SSE和AVX加速的程式碼就不放了,直接看原始碼就好了
}
尾聲
到這裡JPEG解碼的部分就全部結束了。需要注意的是,這裡解碼出來的資料顏色格式並不是可以直接輸出到螢幕的RGB888(其實允許這種格式,但很少見,比較常見的是YCbCr格式),還要根據需要處理。
提醒一下,解碼出來的是分割出來的8x8的塊,還要再拼起來
參考資料
部落格園部落格:JPEG解碼——(4)霍夫曼解碼 - OnlyTime_唯有時光 - 部落格園 (cnblogs.com)
JPEG標準:Microsoft Word - T081E.DOC (w3.org)
一個Rust寫的JPEG解碼器:MROS/jpeg_tutorial: 跟我寫 JPEG 解碼器 (Write a JPEG decoder with me) (github.com)
友情連結
我學習過程中寫的JPEG圖片檢視器:Ryan1202/my-tiny-jpeg-viewer: A Tiny Jpeg Viewer (github.com)