跳转到内容

JPEG - 构思与实践/制作灰度文件的程序

来自维基教科书,开放的书籍,开放的世界

现在来看看能够生成灰度 JPEG 文件的程序。我们假设图片的宽度和高度都能被 8 整除,并设置 wid8 = width div 8 和 hei8 = height div 8。我们还假设颜色值以位图内存块 pb 的形式给出,因此屏幕坐标为 (i, j)(i = 0, ..., width-1, j = 0, ..., height-1)的点的颜色值(字节)为 pb[(height-1 - j) * width + i]。更准确地说:我们假设图片以 BMP 格式给出为彩色图片,我们只使用它在能被 8x8 方格均匀分割的最大区域内的部分,我们通过取 RGB 值的平均值来构造 pb。

我们按以下顺序编写了标记(及其段):SOI、APP、DQT、DQT、SOF、DHT、DHT、SOS(有两个 DQT 和 DHT 段,因为有两个量化表和两个霍夫曼编码)。最后一个段 - SOS - 标记编码数据的流的开始,在此之后,文件以标记 EOI 结束。我们已经针对 DC 和 AC 编号计算了 EHUFSI[val] 和 EHUFCO[val][i] 数组,它们的大小分别为分配给霍夫曼值 val 的代码的大小和代码本身。在程序中,这些数组分别称为 ehufsid[val] 和 ehufcod[val](用于 DC 编号),以及 ehufsia[val] 和 ehufcoa[val](用于 AC 编号)。

对于具有坐标集 (i0, j0)(i0 = 0, ..., wid8-1, j0 = 0, ..., hei8-1)的 8x8 方格,以及方格内具有坐标集 (i, j)(i, j = 0, ..., 7)的点,屏幕坐标集为 (i0 * 8 + i, j0 * 8 + j)。对于每个 8x8 方格,我们都有一个 8x8 矩阵 f,它包含颜色值(有符号字节 - 我们从原始颜色值(级移)中减去了 128 以获得更小的数值),通过离散余弦变换、量化和舍入,我们得到一个 8x8 矩阵 g(u, v),它包含整数。这个过程(或更确切地说,函数)称为 costrans(f):g = costrans(f)。反之字形变换(iz: (i, j) → [1, ..., 64])由两个从 1 到 64 的数组 zx[l] 和 zy[l] 组成(这样 (zx[l], zy[l]) 的之字形变换为 l),由此,g 被转换为一个 64 数组 w(从 1 到 64)。w[1] 是 DC 编号,我们从它中减去前一个 DC 编号(存储在变量 dc 中),得到差值 diff。我们通过函数 digit(n) 获取整数 n 的二进制数字表达式,并将这个数组(从 1 到 size(n))插入到变量 c 数组中(从 1 到 10)。将位(形式为 c[j],其中 c 是代码字或数字表达式)写入文件(称为 fu)的过程表示为 wbit(bit) - 此过程使用(全局)变量 b0、b 和 q。costranswbit 的程序在扫描过程的程序之后显示。

b0 = 0
b = 0
q = 256
dc = 0
for j0 = 0 to hei8 - 1 do
for i0 = 0 to wid8 - 1 do
begin
for j = 0 to 7 do
for i = 0 to 7 do
f[i, j] = pb[(height - 1 - (j0 * 8 + j)) * width + (i0 * 8 + i)] - 128
g = costrans(f)
for l = 1 to 64 do
w[l] = g[zx[l], zy[l]]
diff = w[1] - dc
dc = w[1]
val = size(diff)
e = ehufsid[val]
c = ehufcod[val]
for j = 1 to e do
wbit(c[j])
if diff <> 0 then
begin
c = digit(diff)
for j = 1 to val do
wbit(c[j])
end
r = 64
while (r > 1) and (w[r] = 0) do
r = r - 1
if r > 1 then
begin
l = 1
m = 0
while l < r do
begin
l = l + 1
n = w[l]
if n = 0 then
begin
m = m + 1
if m = 16 then
begin
e = ehufsia[240]
c = ehufcoa[240]
for j = 1 to e do
wbit(c[j])
m = 0
end
end
else
begin
k = size(n)
val = m * 16 + k
e = ehufsia[val]
c = ehufcoa[val]
for j = 1 to e do
wbit(c[j])
c = digit(n)
for j = 1 to k do
wbit(c[j])
m = 0
end
end
end
if r < 64 then
begin
e = ehufsia[0]
c = ehufcoa[0]
for j = 1 to e do
wbit(c[j])
end
end

函数 costrans(f) 的程序,该程序对 8x8 矩阵 f[i, j](有符号字节)进行余弦变换和量化,得到 8x8 矩阵 g[u, v](整数),分为四种情况:u = 0 和 v = 0、u = 0 和 v > 0、u > 0 和 v = 0 以及 u > 0 和 v > 0。如果量化表的 64 数组称为 quant[k],之字形函数称为 iz(i, j),我们事先计算了矩阵 cq[i, j] = 4 * quant[iz(i, j)](i, j = 0, 1, ..., 7)(整数)和矩阵 cs[i, j] = cos((2 * i + 1) * j * pi / 16)(i, j = 0, 1, ..., 7)(实数)。g[u, v] 的四种情况的程序可以如下所示

s = 0
for i = 0 to 7 do
for j = 0 to 7 do
s = s + f[i, j]
g[0, 0] = round(s / (2 * cq[0, 0]))
for v = 1 to 7 do
begin
s = 0
for j = 0 to 7 do
begin
t = 0
for i = 0 to 7 do
t = t + f[i, j]
s = s + cs[j, v] * t
end
g[0, v] = round(s / (sqrt(2) * cq[0, v]))
end
for u = 1 to 7 do
begin
s = 0
for i = 0 to 7 do
begin
t = 0
for j = 0 to 7 do
t = t + f[i, j]
s = s + cs[i, u] * t
end
g[u, 0] = round(s / (sqrt(2) * cq[u, 0]))
end
for u = 1 to 7 do
for v = 1 to 7 do
begin
s = 0
for i = 0 to 7 do
begin
t = 0
for j = 0 to 7 do
t = t + cs[j, v] * f[i, j]
s = s + cs[i, u] * t
end
g[u, v] = round(s / cq[u, v])
end

最后是过程 wbit(bit),它将位“bit”(定义为字节,因为程序不处理位)写入文件 fu。我们从代码字或数字的数字中获取位,在插入文件之前,这些位被收集到 8 个块中,并转换为字节。我们称当前字节为 b(最初设置为 0),如果我们有一个从 256 开始的整数 q,并且在将每个位插入 b 之前,它被除以 2,那么添加(新)位意味着 b 必须增加 bit * q:b = b + bit * q。当 q = 1 时,b 被写入文件,q 再次设置为 256。如果 b = 255(8 个数字 1),则写入必须紧随其后的是写入零字节 b0(8 个数字 0)(字节填充),这样 255(在解码期间)就不会被误认为是标记的开始。写入过程 wbit 可能如下所示

procedure wbit(bit: byte)
begin
q = q div 2
b = b + bit * q
if q = 1 then
begin
write(fu, b)
if b = 255 then
write(fu, b0)
b = 0
q = 256
end
end

程序以这个过程结束,这个过程在 q 未设置为 256(表示 b 尚未写入)时写入最后一个字节 b,并将 b 的其余位设置为 1(位填充

e = size(q) - 1
p = 1
for i = 1 to e do
begin
b = b + p
p = 2 * p
end
write(fu, b)

如果最后一个字节 b 为 255,则它必须紧随其后的是零字节 b0。最后,我们写入标记 EOI = (255, 217)(图像结束)并关闭文件。

华夏公益教科书