Skip to main content

deepmap2/
tile.rs

1use std::collections::BTreeMap;
2
3use crate::model::PixelFormat;
4use crate::{DecodedImage, Deepmap2Error, Deepmap2Result};
5
6/// KCBC 块信息
7struct KcbcBlock<'a> {
8    #[allow(dead_code)]
9    offset: usize,
10    /// 0x04 字段:列索引(tile 在网格中的列号,从 0 开始)
11    #[allow(dead_code)]
12    col_index: u32,
13    /// 0x08 字段:行索引(tile 在网格中的行号,从 0 开始)
14    #[allow(dead_code)]
15    row_index: u32,
16    /// row_hint:该块实际输出的行数(从 offset+0x0C 读取)
17    row_hint: u32,
18    /// 该块包含的 dmp2 原始数据(从 dmp2 magic 到下一个块开始或数据末尾)
19    dmp2_data: &'a [u8],
20}
21
22/// 扫描 data 中所有 "KCBC" magic,返回块信息列表。
23///
24/// 如果发现 KCBC magic 但头部不完整(截断),返回错误。
25fn parse_kcbc_blocks(data: &[u8]) -> Deepmap2Result<Vec<KcbcBlock<'_>>> {
26    let mut blocks = Vec::new();
27    let mut search_offset = 0;
28
29    while let Some(rel) = find_bytes(&data[search_offset..], b"KCBC") {
30        let block_offset = search_offset + rel;
31        if block_offset + 20 > data.len() {
32            return Err(Deepmap2Error::InvalidData(
33                "KCBC 块头被截断:数据不足 20 字节".to_string(),
34            ));
35        }
36
37        let col_index = u32::from_le_bytes(
38            data[block_offset + 0x04..block_offset + 0x08]
39                .try_into()
40                .unwrap(),
41        );
42        let row_index = u32::from_le_bytes(
43            data[block_offset + 0x08..block_offset + 0x0C]
44                .try_into()
45                .unwrap(),
46        );
47        let row_hint = u32::from_le_bytes(
48            data[block_offset + 0x0C..block_offset + 0x10]
49                .try_into()
50                .unwrap(),
51        );
52        let block_length = u32::from_le_bytes(
53            data[block_offset + 0x10..block_offset + 0x14]
54                .try_into()
55                .unwrap(),
56        ) as usize;
57
58        // 确定块的数据范围
59        let block_end = if block_length >= 0x20 && block_offset + block_length <= data.len() {
60            block_offset + block_length
61        } else {
62            // 找到下一个 KCBC 或使用剩余全部数据
63            find_bytes(&data[block_offset + 4..], b"KCBC")
64                .map(|r| block_offset + 4 + r)
65                .unwrap_or(data.len())
66        };
67
68        let block_span = &data[block_offset..block_end];
69
70        // 在块范围内查找 dmp2 magic(缺失则报错,不静默跳过)
71        let dmp2_rel = find_bytes(block_span, b"dmp2")
72            .ok_or_else(|| Deepmap2Error::InvalidData("KCBC 块中未找到 dmp2 头部".to_string()))?;
73        let dmp2_data = &block_span[dmp2_rel..];
74        blocks.push(KcbcBlock {
75            offset: block_offset,
76            col_index,
77            row_index,
78            row_hint,
79            dmp2_data,
80        });
81
82        search_offset = block_end;
83    }
84
85    Ok(blocks)
86}
87
88/// 在切片中搜索子序列,返回第一次出现的偏移量
89pub fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
90    haystack.windows(needle.len()).position(|w| w == needle)
91}
92
93/// 从包含 KCBC 块的 data 中解析各块内嵌的 dmp2 数据切片
94///
95/// 返回每个 tile 的原始 dmp2 字节(可直接传给 `decode()`)
96pub fn parse_kcbc(data: &[u8]) -> Deepmap2Result<Vec<Vec<u8>>> {
97    let blocks = parse_kcbc_blocks(data)?;
98    if blocks.is_empty() {
99        return Err(Deepmap2Error::InvalidData(
100            "输入数据中未找到 KCBC 块".to_string(),
101        ));
102    }
103    Ok(blocks.into_iter().map(|b| b.dmp2_data.to_vec()).collect())
104}
105
106/// 解码 KCBC 序列,按 row_index/col_index 还原 tile 网格。
107pub fn decode_kcbc_sequence(data: &[u8]) -> Deepmap2Result<DecodedImage> {
108    decode_kcbc_sequence_with_options(data, None, None, None)
109}
110
111pub fn decode_kcbc_sequence_with_options(
112    data: &[u8],
113    expected_width: Option<u16>,
114    expected_height: Option<u16>,
115    pixel_format_override: Option<PixelFormat>,
116) -> Deepmap2Result<DecodedImage> {
117    use crate::raw::Deepmap2Header;
118    use deku::prelude::*;
119
120    let blocks = parse_kcbc_blocks(data)?;
121    if blocks.is_empty() {
122        return Err(Deepmap2Error::InvalidData(
123            "输入数据中未找到 KCBC 块".to_string(),
124        ));
125    }
126
127    struct TileInfo {
128        col_index: u32,
129        row_index: u32,
130        width: usize,
131        height: usize,
132        rgba: Vec<u8>,
133        header: Deepmap2Header,
134    }
135
136    let mut tiles = Vec::with_capacity(blocks.len());
137    let mut column_widths: BTreeMap<u32, usize> = BTreeMap::new();
138    let mut row_heights: BTreeMap<u32, usize> = BTreeMap::new();
139
140    for block in &blocks {
141        let (tw, th, rgba, header) =
142            decode_tile(block.dmp2_data, block.row_hint, pixel_format_override)?;
143        let width = tw as usize;
144        let height = th as usize;
145        column_widths
146            .entry(block.col_index)
147            .and_modify(|existing| *existing = (*existing).max(width))
148            .or_insert(width);
149        row_heights
150            .entry(block.row_index)
151            .and_modify(|existing| *existing = (*existing).max(height))
152            .or_insert(height);
153
154        tiles.push(TileInfo {
155            col_index: block.col_index,
156            row_index: block.row_index,
157            width: tw as usize,
158            height: th as usize,
159            rgba,
160            header,
161        });
162    }
163
164    let mut x_offsets = BTreeMap::new();
165    let mut total_width = 0usize;
166    for (&col_index, &width) in &column_widths {
167        x_offsets.insert(col_index, total_width);
168        total_width += width;
169    }
170
171    let mut y_offsets = BTreeMap::new();
172    let mut total_height = 0usize;
173    for (&row_index, &height) in &row_heights {
174        y_offsets.insert(row_index, total_height);
175        total_height += height;
176    }
177
178    if let Some(expected_width) = expected_width
179        && total_width != expected_width as usize
180    {
181        return Err(Deepmap2Error::InvalidData(format!(
182            "KCBC 输出宽度不一致: 期望 {}, 实际 {}",
183            expected_width, total_width
184        )));
185    }
186    if let Some(expected_height) = expected_height
187        && total_height != expected_height as usize
188    {
189        return Err(Deepmap2Error::InvalidData(format!(
190            "KCBC 输出高度不一致: 期望 {}, 实际 {}",
191            expected_height, total_height
192        )));
193    }
194
195    let mut rgba = vec![0u8; total_width * total_height * 4];
196    for tile in &tiles {
197        let Some(&dst_x) = x_offsets.get(&tile.col_index) else {
198            return Err(Deepmap2Error::InvalidData(format!(
199                "KCBC 缺少列偏移: col_index={}",
200                tile.col_index
201            )));
202        };
203        let Some(&dst_y) = y_offsets.get(&tile.row_index) else {
204            return Err(Deepmap2Error::InvalidData(format!(
205                "KCBC 缺少行偏移: row_index={}",
206                tile.row_index
207            )));
208        };
209
210        for row in 0..tile.height {
211            let src_start = row * tile.width * 4;
212            let src_end = src_start + tile.width * 4;
213            let dst_start = ((dst_y + row) * total_width + dst_x) * 4;
214            let dst_end = dst_start + tile.width * 4;
215            rgba[dst_start..dst_end].copy_from_slice(&tile.rgba[src_start..src_end]);
216        }
217    }
218
219    let mut cursor = std::io::Cursor::new(blocks[0].dmp2_data);
220    let mut reader = Reader::new(&mut cursor);
221    let source_header = tiles
222        .first()
223        .map(|tile| tile.header.clone())
224        .unwrap_or_else(|| Deepmap2Header::from_reader_with_ctx(&mut reader, ()).unwrap());
225
226    Ok(DecodedImage {
227        source_header: source_header.clone(),
228        width: total_width as u16,
229        height: total_height as u16,
230        pixel_format: pixel_format_override.unwrap_or(source_header.pixel_format),
231        rgba,
232    })
233}
234
235/// 解码单个 tile 的 dmp2 数据,row_hint=0 时使用 header.height
236fn decode_tile(
237    dmp2_data: &[u8],
238    row_hint: u32,
239    pixel_format_override: Option<PixelFormat>,
240) -> Deepmap2Result<(u16, u16, Vec<u8>, crate::model::Deepmap2Header)> {
241    use crate::raw::Deepmap2Header;
242    use deku::prelude::*;
243
244    let mut cursor = std::io::Cursor::new(dmp2_data);
245    let mut reader = Reader::new(&mut cursor);
246    let header = Deepmap2Header::from_reader_with_ctx(&mut reader, ())?;
247    let header_size = header.header_size();
248    let payload = dmp2_data
249        .get(header_size..)
250        .ok_or(Deepmap2Error::Truncated)?;
251
252    let output_height = if row_hint > 0 {
253        u16::try_from(row_hint).map_err(|_| {
254            Deepmap2Error::InvalidData(format!("KCBC row_hint 超出 u16 范围: {row_hint}"))
255        })?
256    } else {
257        header.height
258    };
259
260    let output_width = header.width;
261    let rgba = crate::codec::decode_raw_with_options(
262        &header,
263        payload,
264        Some(output_width),
265        Some(output_height),
266        pixel_format_override,
267    )?;
268
269    let mut header = header;
270    header.width = output_width;
271    header.height = output_height;
272    if let Some(pixel_format) = pixel_format_override {
273        header.pixel_format = pixel_format;
274    }
275
276    Ok((output_width, output_height, rgba, header))
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    /// 构造最小 dmp2 数据(Type 1=None, G8, 无压缩)
284    fn build_dmp2_none_g8(width: u16, height: u16, pixels: &[u8]) -> Vec<u8> {
285        assert_eq!(pixels.len(), width as usize * height as usize);
286        let mut data = Vec::new();
287        data.extend_from_slice(b"dmp2");
288        data.push(0x01); // decode_type = None
289        data.push(0x00); // version
290        data.push(0x00); // predictor_type
291        data.push(0x01); // pixel_format = G8
292        data.extend_from_slice(&width.to_le_bytes());
293        data.extend_from_slice(&height.to_le_bytes());
294        data.extend_from_slice(pixels);
295        data
296    }
297
298    /// 构造 KCBC 容器,tiles: &[(row, col, row_hint, dmp2_data)]
299    fn build_kcbc(tiles: &[(u32, u32, u32, &[u8])]) -> Vec<u8> {
300        let mut data = Vec::new();
301        for &(row, col, row_hint, dmp2) in tiles {
302            let block_len = (20 + dmp2.len()) as u32;
303            data.extend_from_slice(b"KCBC");
304            data.extend_from_slice(&col.to_le_bytes());
305            data.extend_from_slice(&row.to_le_bytes());
306            data.extend_from_slice(&row_hint.to_le_bytes());
307            data.extend_from_slice(&block_len.to_le_bytes());
308            data.extend_from_slice(dmp2);
309        }
310        data
311    }
312
313    #[test]
314    fn decode_kcbc_single_tile_matches_decode() {
315        let pixels = [10u8, 20, 30, 40];
316        let dmp2 = build_dmp2_none_g8(2, 2, &pixels);
317
318        let kcbc = build_kcbc(&[(0, 0, 2, &dmp2)]);
319        let kcbc_img = crate::decode_kcbc(&kcbc).unwrap();
320        let raw_img = crate::decode(&dmp2).unwrap();
321
322        assert_eq!(kcbc_img.width, raw_img.width);
323        assert_eq!(kcbc_img.height, raw_img.height);
324        assert_eq!(kcbc_img.rgba, raw_img.rgba);
325    }
326
327    #[test]
328    fn decode_kcbc_multi_band_vertical_stitch() {
329        let top = build_dmp2_none_g8(2, 2, &[1, 2, 3, 4]);
330        let middle = build_dmp2_none_g8(2, 1, &[5, 6]);
331        let bottom = build_dmp2_none_g8(2, 2, &[7, 8, 9, 10]);
332
333        let kcbc = build_kcbc(&[(0, 0, 2, &top), (1, 0, 1, &middle), (2, 0, 2, &bottom)]);
334
335        let img = crate::decode_kcbc(&kcbc).unwrap();
336        assert_eq!(img.width, 2);
337        assert_eq!(img.height, 5);
338
339        let px = |gray: u8| -> [u8; 4] { [gray, gray, gray, 0xFF] };
340        let expected: Vec<u8> = [
341            px(1),
342            px(2),
343            px(3),
344            px(4),
345            px(5),
346            px(6),
347            px(7),
348            px(8),
349            px(9),
350            px(10),
351        ]
352        .concat();
353        assert_eq!(img.rgba, expected);
354    }
355
356    #[test]
357    fn decode_kcbc_grid_tiles_respect_row_and_col_indices() {
358        let top_left = build_dmp2_none_g8(1, 1, &[1]);
359        let top_right = build_dmp2_none_g8(1, 1, &[2]);
360        let bottom_left = build_dmp2_none_g8(1, 1, &[3]);
361        let bottom_right = build_dmp2_none_g8(1, 1, &[4]);
362
363        let kcbc = build_kcbc(&[
364            (0, 0, 1, &top_left),
365            (0, 1, 1, &top_right),
366            (1, 0, 1, &bottom_left),
367            (1, 1, 1, &bottom_right),
368        ]);
369
370        let img = decode_kcbc_sequence_with_options(&kcbc, Some(2), Some(2), None).unwrap();
371        assert_eq!(img.width, 2);
372        assert_eq!(img.height, 2);
373
374        let px = |gray: u8| -> [u8; 4] { [gray, gray, gray, 0xFF] };
375        let expected: Vec<u8> = [px(1), px(2), px(3), px(4)].concat();
376        assert_eq!(img.rgba, expected);
377    }
378
379    #[test]
380    fn decode_tile_rejects_row_hint_exceeding_u16() {
381        let dmp2 = build_dmp2_none_g8(2, 2, &[0; 4]);
382        let kcbc = build_kcbc(&[(0, 0, 65536, &dmp2)]); // u16::MAX + 1
383        let result = crate::decode_kcbc(&kcbc);
384        let err = result.err().expect("should error on oversized row_hint");
385        let msg = format!("{err}");
386        assert!(msg.contains("u16"), "should mention u16 overflow: {msg}");
387    }
388
389    #[test]
390    fn parse_kcbc_empty_data_returns_error() {
391        let result = parse_kcbc(b"no kcbc here");
392        assert!(result.is_err());
393    }
394
395    #[test]
396    fn parse_kcbc_truncated_header_returns_error() {
397        // KCBC magic 后面只有 10 字节(不足 16 字节头部数据),应返回错误
398        let mut data = b"KCBC".to_vec();
399        data.extend_from_slice(&[0u8; 10]);
400        let result = parse_kcbc(&data);
401        assert!(result.is_err());
402        let err_msg = format!("{}", result.unwrap_err());
403        assert!(
404            err_msg.contains("截断"),
405            "error should mention truncation: {err_msg}"
406        );
407    }
408
409    #[test]
410    fn parse_kcbc_truncated_second_block_returns_error() {
411        // 第一个完整 KCBC 块 + 第二个截断的 KCBC 块
412        let mut data = Vec::new();
413        // Block 1: KCBC header (20 bytes) + "dmp2" magic + some payload
414        data.extend_from_slice(b"KCBC");
415        data.extend_from_slice(&0u32.to_le_bytes()); // col_index
416        data.extend_from_slice(&0u32.to_le_bytes()); // row_index
417        data.extend_from_slice(&10u32.to_le_bytes()); // row_hint
418        data.extend_from_slice(&0u32.to_le_bytes()); // block_length=0 (will use next KCBC)
419        data.extend_from_slice(b"dmp2");
420        data.extend_from_slice(&[0u8; 8]); // some dmp2 data
421        // Block 2: truncated KCBC (only magic + 8 bytes, need 16 more)
422        data.extend_from_slice(b"KCBC");
423        data.extend_from_slice(&[0u8; 8]); // only 8 bytes, need 16
424
425        let result = parse_kcbc(&data);
426        assert!(result.is_err(), "should error on truncated second block");
427    }
428
429    #[test]
430    fn find_bytes_found() {
431        assert_eq!(find_bytes(b"hello world", b"world"), Some(6));
432    }
433
434    #[test]
435    fn find_bytes_not_found() {
436        assert_eq!(find_bytes(b"hello", b"xyz"), None);
437    }
438}