跳轉至內容

JPEG - 想法與實踐/灰度圖片繪製程式

來自華夏公益教科書,開放的書籍,開放的世界

現在來編寫一個程式,可以讀取灰度JPEG檔案並繪製圖片。各個段落不需要按照特定順序排列(除了APP0必須緊接在SOI之後),因此讀取檔案的程式需要尋找標記,當找到標記(與SOI和EOI不同)時,程式需要讀取接下來的兩個位元組,它們表示段落的長度。在讀取過程中,需要不斷地統計讀取的位元組數,將數字r從0開始加1。當所有段落都讀取完成(並且資訊被整理到我們使用的陣列中)後,跳轉到位置r = rhead,資料從這裡開始(緊接在SOS段落之後 - rhead是SOS的最後一個位元組的數字)。

編碼資料按位讀取,但是它們在檔案中以位元組形式儲存,因為每個8位塊在寫入檔案時都被轉換為一個位元組。因此,我們需要一個程式來提供下一個位,並在每使用8位後讀取下一個位元組。我們將此程式稱為nbit,該程式的程式碼將在此部分的末尾展示。

程式的結構是,每次讀取到足夠的位元組形成一個64元素陣列w[l](l = 1, ..., 64)時,繪製一個8x8的正方形(透過“setpixel”程式)。讀取過程由數字l控制,每插入一個數字到w[l]中,l就加1。當l = 64時,w透過Z字形函式轉換為一個8x8矩陣,然後將這個8x8矩陣(g(u, v))進行反量化和反餘弦變換,得到一個8x8矩陣f[i, j](i, j = 0, ..., 7),包含顏色值(帶符號位元組,透過加128轉換為無符號位元組)。如果這個8x8正方形的座標為(i0, j0)(i0 = 0, ..., wid8-1, j0 = 0, ..., hei8-1),那麼需要用值f[i, j]著色的點的座標為(i0*8 + i, j0*8 + j)。當繪製完8x8正方形後,將l重置為1,並將8x8正方形的座標(i0, j0)改為下一個正方形的座標,即如果i0 < wid8,則i0 = i0 + 1;如果i0 = wid8,則i0 = 0,同時 j0 = j0 + 1。

解碼DC和AC碼的程式分別稱為decodeddecodea。它們返回一個數字val,供num程式用來計算一個數字m。這些程式的程式碼將在主程式之後展示。

對於l = 1,使用decoded。它返回一個數字val,表示接下來需要讀取的位數,這些位構成數字m的位表示式,m加上之前的DC值(儲存在變數dc0中)就是w的DC項:dc = m + dc0,w[1] = dc。

對於l > 1,使用decodea。它返回兩個半位元組nz和val。第一個半位元組nz表示零的個數,第二個半位元組val表示如果val > 0,接下來需要讀取的位數。在這種情況下(val > 0),將l增加nz次(如果nz > 0),並將這nz個l對應的w[l]設定為0。然後將l再次增加1,接下來的val位構成數字m的位表示式,m就是w[l]的值。如果val = 0,那麼nz的值要麼是15,要麼是0。如果nz = 15,則將l增加16次,並將這16個l對應的w[l]設定為0。如果nz = 0,則表示所有接下來的AC項都為零,即將l增加1,直到l = 64,並將這所有的l對應的w[l]設定為0。

當l = 64時,陣列w[l]就完成了,我們可以繪製8x8正方形。為了更快地繪製圖片,我們將反餘弦變換中的計算(對於每個(i, j))限制在u, v = 0, ..., 5,這樣我們只需要使用64項中的前36項。由於計算的不確定性,顏色值(加128後)可能小於0或大於255,因此可能需要進行鉗位。

讀取檔案的資料部分和繪製每個8x8正方形的操作都在一個迴圈(drawloop)中進行,當到達檔案末尾時,迴圈結束。全域性變數r從r = rhead(頭部的最後一個位元組)開始,每讀取一個位元組就加1。

r = rhead
i0 = 0
j0 = 0
l = 1
s = 8
b = 0
dc = 0
dc0 = 0

drawloop

if l = 1 then
begin
dc0 = dc
decoded
num
dc = m + dc0
w[1] =dc
end
decodea
if val > 0 then
begin
if nz > 0 then
for i = 1 to nz do
begin
l = l + 1
w[l] = 0
end
num
l = l + 1
w[l] = m
end
if (nz = 15) and (val = 0) then
for i = 1 to 16 do
begin
l = l + 1
w[l] = 0
end
if (nz = 0) and (val = 0) then
while l < 64 do
begin
l = l + 1
w[l] = 0
end
if l = 64 then
begin
l = 1
for j = 0 to 7 do
for i = 0 to 7 do
begin
t = w[1] * cq[0, 0] / sqrt(2)
for v = 1 to 5 do
t = t + cs[j, v] * cq[0, v] * w[iz(0, v)]
s = t / sqrt(2)
for u = 1 to 5 do
begin
cq[u, 0] * w[iz(u, 0)] / sqrt(2)
for v = 1 to 5 do
t = t + cs[j, v] * cq[u, v] * w[iz(u, v)]
s = s + cs[i, u] * t
end
k = round(s + 128)
if k < 0 then
k = 0
if k > 255 then
k = 255
setpixel(i0 * 8 + i, j0 * 8 + j, k, k, k)
end
i0 = i0 + 1
if i0 = wid8 then
begin
i0 = 0
j0 = j0 + 1
end
end
goto drawloop

decoded程式解碼DC值(l = 1)的霍夫曼碼,decodea程式解碼AC值(l > 1)的霍夫曼碼。它們使用從霍夫曼表中構造的陣列mincode[k], maxcode[k], valptr[k]和huffval[k]。用於解碼DC值的霍夫曼表的陣列稱為mincoded[k], maxcoded[k], valptrd[k]和huffvald[k],用於解碼AC值的霍夫曼表的陣列稱為mincodea[k], maxcodea[k], valptra[k]和huffvala[k]。decodeddecodea程式都包含讀取下一個位的程式nbitdecoded程式的程式碼可以如下所示:

c = 0
j = 0
while c > maxcoded[j] do
begin
nbit
c = 2 * c + bit
j = j + 1
end
val = huffvald[valptrd[j] + c - mincoded[j]]

decodea程式的程式碼類似,只是數字val(位元組)現在被分成兩個半位元組:nz = val div 16和val = val - nz * 16 - 第一個半位元組nz表示零的個數。

decodeddecodea程式返回的數字val表示接下來需要讀取的位數,這些位構成數字m的位表示式。m由num程式計算,該程式也使用讀取下一個位的程式nbit。但是,如果讀取的第一個位是0,則表示數字m是負數,它的數值就是計算得到的m的二進位制補碼,即m = -(q0-1 - m),其中q0 = 2val(第一個位的讀取bit1由數字z控制)。

procedure num
begin
q0 = round(exp(val * ln(2)))
q = q0
z = 0
m = 0
while q > 1 do
begin
q = q div 2
nbit
if z = 0 then
begin
bit1 = bit
z = 1
end
m = m + bit * q
end
if bit1 = 0 then
m = -(q0 - 1 - m)
end

現在來編寫nbit程式,它返回位流中的下一個位,稱為bit,並用於decodeddecodeanum程式。下一個位來自一個數組c[i](從1到8),該陣列在每使用8位後更新:讀取一個新的位元組b,並用b的位表示式更新c:c = digit(b) - digit程式的程式碼如下所示。位讀取過程由一個全域性變數s控制,它從0開始,每次呼叫nbit程式就加1,當s = 8時重置為0(我們需要從s = 8開始,以便可以讀取第一個位元組)。但是,由於在寫入檔案時,我們在每個值為255的位元組後都寫了一個零位元組,因此在讀取時,如果遇到一個位元組為255,則需要跳過下一個位元組。唯一的例外是當255後面的位元組為217時,因為此時我們遇到了(255, 217)對,這是EOI標記(影像末尾),這時需要關閉檔案並將繪製程式設定為停止(將變數z從0改為1,並跳轉到mainloop,即視窗的“getmessage”迴圈)。nbit程式的程式碼可以如下所示:

procedure nbit;
begin
if s = 8 then
begin
r = r + 1
read(fu, b)
if b = 255 then
begin
r = r + 1
read(fu, b1)
if b1 = 217 then
begin
close(fu)
z = 1
goto mainloop
end
end
c = digit(b)
s = 0
end
s = s + 1
bit = c[s]
end

最後,編寫digit(b)函式的程式碼,該函式返回位元組b的位表示式。此函式與寫入程式中同名的函式相同,只是它現在只適用於位元組,並且它的位陣列從1到8,以便可以從零開始。

q = 128
i = 0
while i < 8 do
begin

i = i + 1
j = b div q
b = b - j * q
q = q div 2
digit[i] = j
end
華夏公益教科書