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
// 纯 MoonBit QOI 解码器
// 规范: https://qoiformat.org/qoi-specification.pdf
// 移植自 src/format/qoi.mbt 的 decode_qoi,仅替换类型引用:
// @core.Image -> @types.Image, @core.LoadError -> @types.LoadError
// QOI 哈希函数: (r*3 + g*5 + b*7 + a*11) % 64
///|
fn qoi_hash(r : Int, g : Int, b : Int, a : Int) -> Int {
(r * 3 + g * 5 + b * 7 + a * 11) % 64
}
///|
/// 解码 QOI 字节流为 Image(channels=3 或 4)
/// 支持 QOI_OP_INDEX / QOI_OP_DIFF / QOI_OP_LUMA / QOI_OP_RUN / QOI_OP_RGB / QOI_OP_RGBA 全部标签
pub fn decode_qoi_pure(data : Bytes) -> @types.Image raise @types.LoadError {
let len = data.length()
if len < 14 {
raise @types.LoadError::DecodeFailed("QOI 数据过短")
}
if data[0].to_int() != 0x71 ||
data[1].to_int() != 0x6F ||
data[2].to_int() != 0x69 ||
data[3].to_int() != 0x66 {
raise @types.LoadError::DecodeFailed("QOI magic 不匹配")
}
let width = (data[4].to_int() << 24) |
(data[5].to_int() << 16) |
(data[6].to_int() << 8) |
data[7].to_int()
let height = (data[8].to_int() << 24) |
(data[9].to_int() << 16) |
(data[10].to_int() << 8) |
data[11].to_int()
let channels = data[12].to_int()
if width <= 0 || height <= 0 {
raise @types.LoadError::DecodeFailed("QOI 尺寸无效")
}
let out_channels = if channels == 3 { 3 } else { 4 }
@types.check_dims(width, height, out_channels)
let pixel_count = width * height
let pixels : Array[Byte] = Array::make(pixel_count * out_channels, 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 px_r = Ref(0)
let px_g = Ref(0)
let px_b = Ref(0)
let px_a = Ref(255)
let pos = Ref(14)
let pixel_idx = Ref(0)
while pixel_idx.val < pixel_count && pos.val < len {
let tag = data[pos.val].to_int()
pos.val += 1
if tag == 0xFE {
px_r.val = data[pos.val].to_int()
px_g.val = data[pos.val + 1].to_int()
px_b.val = data[pos.val + 2].to_int()
pos.val += 3
} else if tag == 0xFF {
px_r.val = data[pos.val].to_int()
px_g.val = data[pos.val + 1].to_int()
px_b.val = data[pos.val + 2].to_int()
px_a.val = data[pos.val + 3].to_int()
pos.val += 4
} else if (tag & 0xC0) == 0x00 {
let idx = tag & 0x3F
px_r.val = index_r[idx]
px_g.val = index_g[idx]
px_b.val = index_b[idx]
px_a.val = index_a[idx]
} else if (tag & 0xC0) == 0x40 {
px_r.val = (px_r.val + ((tag >> 4) & 0x03) - 2) & 0xFF
px_g.val = (px_g.val + ((tag >> 2) & 0x03) - 2) & 0xFF
px_b.val = (px_b.val + (tag & 0x03) - 2) & 0xFF
} else if (tag & 0xC0) == 0x80 {
let dg = (tag & 0x3F) - 32
px_g.val = (px_g.val + dg) & 0xFF
let b2 = data[pos.val].to_int()
pos.val += 1
let dr_dg = (b2 >> 4) - 8
let db_dg = (b2 & 0x0F) - 8
px_r.val = (px_r.val + dr_dg + dg) & 0xFF
px_b.val = (px_b.val + db_dg + dg) & 0xFF
} else {
let run = (tag & 0x3F) + 1
for _ in 0..<run {
if pixel_idx.val < pixel_count {
pixels[pixel_idx.val * out_channels] = px_r.val.to_byte()
pixels[pixel_idx.val * out_channels + 1] = px_g.val.to_byte()
pixels[pixel_idx.val * out_channels + 2] = px_b.val.to_byte()
if out_channels == 4 {
pixels[pixel_idx.val * out_channels + 3] = px_a.val.to_byte()
}
pixel_idx.val += 1
}
}
continue
}
pixels[pixel_idx.val * out_channels] = px_r.val.to_byte()
pixels[pixel_idx.val * out_channels + 1] = px_g.val.to_byte()
pixels[pixel_idx.val * out_channels + 2] = px_b.val.to_byte()
if out_channels == 4 {
pixels[pixel_idx.val * out_channels + 3] = px_a.val.to_byte()
}
pixel_idx.val += 1
let h = qoi_hash(px_r.val, px_g.val, px_b.val, px_a.val)
index_r[h] = px_r.val
index_g[h] = px_g.val
index_b[h] = px_b.val
index_a[h] = px_a.val
}
@types.Image::{
width,
height,
channels: out_channels,
data: Bytes::from_array(pixels),
}
}