1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 纯 MoonBit QOI 编码器
// 规范: https://qoiformat.org/qoi-specification.pdf
// 移植自 src/format/qoi.mbt 的 encode_qoi,仅替换类型引用:
// @core.Image -> @types.Image, @core.LoadError -> @types.LoadError
// qoi_hash 复用 src/pure/qoi_decode.mbt 已有定义(同包私有函数跨文件可见)
///|
/// 将 Image 编码为 QOI 字节流
/// 输入 Image 的 channels 必须为 3 (RGB) 或 4 (RGBA)
/// 输出: 14 字节 header + 像素编码 + 8 字节 padding
pub fn encode_qoi_pure(img : @types.Image) -> Bytes raise @types.LoadError {
if img.channels != 3 && img.channels != 4 {
raise @types.LoadError::EncodeFailed(
"QOI 编码需要 channels 为 3 (RGB) 或 4 (RGBA)",
)
}
let out : Array[Byte] = []
out.push(b'\x71')
out.push(b'\x6F')
out.push(b'\x69')
out.push(b'\x66')
out.push(((img.width >> 24) & 0xFF).to_byte())
out.push(((img.width >> 16) & 0xFF).to_byte())
out.push(((img.width >> 8) & 0xFF).to_byte())
out.push((img.width & 0xFF).to_byte())
out.push(((img.height >> 24) & 0xFF).to_byte())
out.push(((img.height >> 16) & 0xFF).to_byte())
out.push(((img.height >> 8) & 0xFF).to_byte())
out.push((img.height & 0xFF).to_byte())
out.push(img.channels.to_byte())
out.push(b'\x00')
let index_r : Array[Int] = Array::make(64, 0)
let index_g : Array[Int] = Array::make(64, 0)
let index_b : Array[Int] = Array::make(64, 0)
let index_a : Array[Int] = Array::make(64, 0)
let prev_r = Ref(0)
let prev_g = Ref(0)
let prev_b = Ref(0)
let prev_a = Ref(255)
let has_alpha = img.channels == 4
let pixel_count = img.width * img.height
let run = Ref(0)
for i in 0..<pixel_count {
let r = img.data[i * img.channels].to_int()
let g = img.data[i * img.channels + 1].to_int()
let b = img.data[i * img.channels + 2].to_int()
let a = if has_alpha {
img.data[i * img.channels + 3].to_int()
} else {
255
}
if r == prev_r.val && g == prev_g.val && b == prev_b.val && a == prev_a.val {
run.val += 1
if run.val == 62 {
out.push((0xC0 | (run.val - 1)).to_byte())
run.val = 0
}
} else {
if run.val > 0 {
out.push((0xC0 | (run.val - 1)).to_byte())
run.val = 0
}
let h = qoi_hash(r, g, b, a)
if index_r[h] == r &&
index_g[h] == g &&
index_b[h] == b &&
index_a[h] == a {
out.push((h & 0x3F).to_byte())
} else if a == prev_a.val {
let dr = r - prev_r.val
let dg = g - prev_g.val
let db = b - prev_b.val
let dr_dg = dr - dg
let db_dg = db - dg
if dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1 {
out.push(
(0x40 | ((dr + 2) << 4) | ((dg + 2) << 2) | (db + 2)).to_byte(),
)
} else if dg >= -32 &&
dg <= 31 &&
dr_dg >= -8 &&
dr_dg <= 7 &&
db_dg >= -8 &&
db_dg <= 7 {
out.push((0x80 | ((dg + 32) & 0x3F)).to_byte())
out.push((((dr_dg + 8) << 4) | (db_dg + 8)).to_byte())
} else {
out.push(b'\xFE')
out.push(r.to_byte())
out.push(g.to_byte())
out.push(b.to_byte())
}
} else {
out.push(b'\xFF')
out.push(r.to_byte())
out.push(g.to_byte())
out.push(b.to_byte())
out.push(a.to_byte())
}
index_r[h] = r
index_g[h] = g
index_b[h] = b
index_a[h] = a
}
prev_r.val = r
prev_g.val = g
prev_b.val = b
prev_a.val = a
}
if run.val > 0 {
out.push((0xC0 | (run.val - 1)).to_byte())
}
for i in 0..<8 {
if i == 7 {
out.push(b'\x01')
} else {
out.push(b'\x00')
}
}
Bytes::from_array(out)
}