Skip to main content

deepmap2/
model.rs

1use deku::prelude::*;
2
3/// deepmap2 解码类型
4#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuRead)]
5#[deku(endian = "little", id_type = "u8", ctx = "endian: deku::ctx::Endian")]
6pub enum DecodeType {
7    /// Type 1: 原始未压缩数据
8    #[deku(id = 1)]
9    None,
10    /// Type 2: LZFSE 多流 + zigzag + 预测器 + YCoCg
11    #[deku(id = 2)]
12    Default,
13    /// Type 3: LZFSE 压缩(无预测器变换)
14    #[deku(id = 3)]
15    Lossless,
16    /// Type 4: 调色板索引
17    #[deku(id = 4)]
18    Palette,
19    /// 未知类型
20    #[deku(id_pat = "_")]
21    Unknown { tag: u8 },
22}
23
24/// 像素格式
25#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuRead)]
26#[deku(endian = "little", id_type = "u8", ctx = "endian: deku::ctx::Endian")]
27pub enum PixelFormat {
28    /// 1 字节/像素: 灰度
29    #[deku(id = 1)]
30    G8,
31    /// 2 字节/像素: 灰度 + alpha
32    #[deku(id = 2)]
33    GA88,
34    /// 3 字节/像素: RGB
35    #[deku(id = 3)]
36    Rgb888,
37    /// 4 字节/像素: RGBA
38    #[deku(id = 4)]
39    Rgba8888,
40    /// 未知格式
41    #[deku(id_pat = "_")]
42    Unknown { tag: u8 },
43}
44
45impl PixelFormat {
46    pub fn bytes_per_pixel(self) -> usize {
47        match self {
48            Self::G8 => 1,
49            Self::GA88 => 2,
50            Self::Rgb888 => 3,
51            Self::Rgba8888 => 4,
52            Self::Unknown { tag } => tag as usize,
53        }
54    }
55
56    pub fn has_alpha(self) -> bool {
57        matches!(self, Self::GA88 | Self::Rgba8888)
58    }
59
60    pub fn is_color(self) -> bool {
61        matches!(self, Self::Rgb888 | Self::Rgba8888)
62    }
63
64    /// 每像素分量数(YCoCg 流分量数: 彩色=3, 灰度=1)
65    pub fn split_stream_components(self) -> usize {
66        if self.is_color() { 3 } else { 1 }
67    }
68}
69
70/// 预测器类型(按行编码在解压数据中,不是全局头字段)
71#[derive(Debug, Clone, Copy, PartialEq, Eq, DekuRead)]
72#[deku(endian = "little", id_type = "u8", ctx = "endian: deku::ctx::Endian")]
73pub enum Predictor {
74    #[deku(id = 0)]
75    None,
76    #[deku(id = 1)]
77    Paeth,
78    #[deku(id = 2)]
79    Left,
80    #[deku(id = 3)]
81    Up,
82    #[deku(id = 4)]
83    Mean,
84    #[deku(id_pat = "_")]
85    Unknown { tag: u8 },
86}
87
88/// deepmap2 文件头(小端序,deku 解析)
89///
90/// 二进制布局(12 字节基础,Palette 类型有额外字段):
91/// ```text
92/// offset  size  field
93/// 0       4     magic = b"dmp2"
94/// 4       1     decode_type
95/// 5       1     version (aux_flag_0: chroma scale 开关)
96/// 6       1     predictor_type (aux_flag_1: 实际为每行数据中的预测器,此字段含义待定)
97/// 7       1     pixel_format
98/// 8       2     width (LE)
99/// 10      2     height (LE)
100/// --- Palette 专用 ---
101/// 12      2     palette_size
102/// 14      2     palette_type (3 or 4)
103/// 16      palette_size*4  palette (BGRA u32 LE entries)
104/// ```
105#[derive(Debug, Clone, DekuRead)]
106#[deku(magic = b"dmp2", endian = "little")]
107pub struct Deepmap2Header {
108    pub decode_type: DecodeType,
109    /// chroma scale 开关(非零=开启 chroma scaling)
110    pub version: u8,
111    /// 保留/未知辅助标志
112    pub predictor_type: u8,
113    pub pixel_format: PixelFormat,
114    pub width: u16,
115    pub height: u16,
116    // Palette 专用字段
117    #[deku(cond = "matches!(decode_type, DecodeType::Palette)")]
118    pub palette_size: Option<u16>,
119    #[deku(cond = "matches!(decode_type, DecodeType::Palette)")]
120    pub palette_type: Option<u16>,
121    #[deku(count = "palette_size.unwrap_or(0) as usize")]
122    pub palette: Vec<u32>,
123}
124
125impl Deepmap2Header {
126    /// 返回该头部消耗的字节数(payload 的起始偏移量)
127    pub fn header_size(&self) -> usize {
128        if matches!(self.decode_type, DecodeType::Palette) {
129            16 + self.palette_size.unwrap_or(0) as usize * 4
130        } else {
131            12
132        }
133    }
134
135    /// chroma scale 值(用于 YCoCg 转换中的色度缩放)
136    pub fn chroma_scale(&self) -> u8 {
137        if self.version != 0 { 1 } else { 0 }
138    }
139
140    pub fn pixel_count(&self) -> usize {
141        self.width as usize * self.height as usize
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    fn parse_header(bytes: &[u8]) -> Deepmap2Header {
150        let mut cursor = std::io::Cursor::new(bytes);
151        let mut reader = Reader::new(&mut cursor);
152        Deepmap2Header::from_reader_with_ctx(&mut reader, ()).unwrap()
153    }
154
155    #[test]
156    fn parse_known_header_default_type() {
157        // 来自 Assets.car 的真实头部字节(decode_type=2=Default, GA88, 100x400)
158        let bytes: &[u8] = &[
159            0x64, 0x6d, 0x70, 0x32, // magic "dmp2"
160            0x02, // decode_type = Default (2)
161            0x01, // version (aux_flag_0)
162            0x0a, // predictor_type (aux_flag_1)
163            0x02, // pixel_format = GA88 (2)
164            0x64, 0x00, // width = 100
165            0x90, 0x01, // height = 400
166        ];
167        let header = parse_header(bytes);
168        assert_eq!(header.decode_type, DecodeType::Default);
169        assert_eq!(header.version, 1);
170        assert_eq!(header.predictor_type, 10);
171        assert_eq!(header.pixel_format, PixelFormat::GA88);
172        assert_eq!(header.width, 100);
173        assert_eq!(header.height, 400);
174        assert_eq!(header.header_size(), 12);
175        assert_eq!(header.chroma_scale(), 1);
176    }
177
178    #[test]
179    fn parse_known_header_lossless_type() {
180        // decode_type=3=Lossless, GA88, 30x50
181        let bytes: &[u8] = &[
182            0x64, 0x6d, 0x70, 0x32, 0x03, // Lossless
183            0x01, 0x0a, 0x02, 0x1e, 0x00, // width = 30
184            0x32, 0x00, // height = 50
185        ];
186        let header = parse_header(bytes);
187        assert_eq!(header.decode_type, DecodeType::Lossless);
188        assert_eq!(header.width, 30);
189        assert_eq!(header.height, 50);
190        assert_eq!(header.header_size(), 12);
191    }
192
193    #[test]
194    fn parse_unknown_decode_type_returns_unknown_variant() {
195        let bytes: &[u8] = &[
196            0x64, 0x6d, 0x70, 0x32, 0xFF, // unknown decode type
197            0x00, 0x00, 0x01, 0x01, 0x00, // width=1
198            0x01, 0x00, // height=1
199        ];
200        let header = parse_header(bytes);
201        assert!(matches!(
202            header.decode_type,
203            DecodeType::Unknown { tag: 0xFF }
204        ));
205    }
206
207    #[test]
208    fn header_size_palette_type() {
209        // Palette 类型头: palette_size=2 → header_size = 16 + 2*4 = 24
210        let mut bytes = vec![
211            0x64, 0x6d, 0x70, 0x32, 0x04, // Palette
212            0x01, 0x0a, 0x04, 0x10, 0x00, // width=16
213            0x10, 0x00, // height=16
214            0x02, 0x00, // palette_size=2
215            0x04, 0x00, // palette_type=4
216        ];
217        // palette: 2 u32 LE entries
218        bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0xFF]); // entry 0
219        bytes.extend_from_slice(&[0xFF, 0x00, 0x00, 0xFF]); // entry 1
220        let header = parse_header(&bytes);
221        assert_eq!(header.decode_type, DecodeType::Palette);
222        assert_eq!(header.palette_size, Some(2));
223        assert_eq!(header.palette_type, Some(4));
224        assert_eq!(header.palette.len(), 2);
225        assert_eq!(header.header_size(), 24);
226    }
227}