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 = total_width
169            .checked_add(width)
170            .filter(|&value| value <= u16::MAX as usize)
171            .ok_or_else(|| Deepmap2Error::InvalidData("KCBC 输出宽度超出 u16 范围".to_string()))?;
172    }
173
174    let mut y_offsets = BTreeMap::new();
175    let mut total_height = 0usize;
176    for (&row_index, &height) in &row_heights {
177        y_offsets.insert(row_index, total_height);
178        total_height = total_height
179            .checked_add(height)
180            .filter(|&value| value <= u16::MAX as usize)
181            .ok_or_else(|| Deepmap2Error::InvalidData("KCBC 输出高度超出 u16 范围".to_string()))?;
182    }
183
184    if let Some(expected_width) = expected_width
185        && total_width != expected_width as usize
186    {
187        return Err(Deepmap2Error::InvalidData(format!(
188            "KCBC 输出宽度不一致: 期望 {}, 实际 {}",
189            expected_width, total_width
190        )));
191    }
192    if let Some(expected_height) = expected_height
193        && total_height != expected_height as usize
194    {
195        return Err(Deepmap2Error::InvalidData(format!(
196            "KCBC 输出高度不一致: 期望 {}, 实际 {}",
197            expected_height, total_height
198        )));
199    }
200
201    let rgba_len = total_width
202        .checked_mul(total_height)
203        .and_then(|pixels| pixels.checked_mul(4))
204        .ok_or_else(|| Deepmap2Error::InvalidData("KCBC 输出像素缓冲区大小溢出".to_string()))?;
205    let mut rgba = vec![0u8; rgba_len];
206    for tile in &tiles {
207        let Some(&dst_x) = x_offsets.get(&tile.col_index) else {
208            return Err(Deepmap2Error::InvalidData(format!(
209                "KCBC 缺少列偏移: col_index={}",
210                tile.col_index
211            )));
212        };
213        let Some(&dst_y) = y_offsets.get(&tile.row_index) else {
214            return Err(Deepmap2Error::InvalidData(format!(
215                "KCBC 缺少行偏移: row_index={}",
216                tile.row_index
217            )));
218        };
219
220        for row in 0..tile.height {
221            let src_start = row * tile.width * 4;
222            let src_end = src_start + tile.width * 4;
223            let dst_start = ((dst_y + row) * total_width + dst_x) * 4;
224            let dst_end = dst_start + tile.width * 4;
225            rgba[dst_start..dst_end].copy_from_slice(&tile.rgba[src_start..src_end]);
226        }
227    }
228
229    let mut cursor = std::io::Cursor::new(blocks[0].dmp2_data);
230    let mut reader = Reader::new(&mut cursor);
231    let source_header = tiles
232        .first()
233        .map(|tile| tile.header.clone())
234        .unwrap_or_else(|| Deepmap2Header::from_reader_with_ctx(&mut reader, ()).unwrap());
235
236    Ok(DecodedImage {
237        source_header: source_header.clone(),
238        width: total_width as u16,
239        height: total_height as u16,
240        pixel_format: pixel_format_override.unwrap_or(source_header.pixel_format),
241        rgba,
242    })
243}
244
245/// 解码单个 tile 的 dmp2 数据,row_hint=0 时使用 header.height
246fn decode_tile(
247    dmp2_data: &[u8],
248    row_hint: u32,
249    pixel_format_override: Option<PixelFormat>,
250) -> Deepmap2Result<(u16, u16, Vec<u8>, crate::model::Deepmap2Header)> {
251    use crate::raw::Deepmap2Header;
252    use deku::prelude::*;
253
254    let mut cursor = std::io::Cursor::new(dmp2_data);
255    let mut reader = Reader::new(&mut cursor);
256    let header = Deepmap2Header::from_reader_with_ctx(&mut reader, ())?;
257    let header_size = header.header_size();
258    let payload = dmp2_data
259        .get(header_size..)
260        .ok_or(Deepmap2Error::Truncated)?;
261
262    let output_height = if row_hint > 0 {
263        u16::try_from(row_hint).map_err(|_| {
264            Deepmap2Error::InvalidData(format!("KCBC row_hint 超出 u16 范围: {row_hint}"))
265        })?
266    } else {
267        header.height
268    };
269
270    let output_width = header.width;
271    let rgba = crate::codec::decode_raw_with_options(
272        &header,
273        payload,
274        Some(output_width),
275        Some(output_height),
276        pixel_format_override,
277    )?;
278
279    let mut header = header;
280    header.width = output_width;
281    header.height = output_height;
282    if let Some(pixel_format) = pixel_format_override {
283        header.pixel_format = pixel_format;
284    }
285
286    Ok((output_width, output_height, rgba, header))
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292
293    /// 构造最小 dmp2 数据(Type 1=None, G8, 无压缩)
294    fn build_dmp2_none_g8(width: u16, height: u16, pixels: &[u8]) -> Vec<u8> {
295        assert_eq!(pixels.len(), width as usize * height as usize);
296        let mut data = Vec::new();
297        data.extend_from_slice(b"dmp2");
298        data.push(0x01); // decode_type = None
299        data.push(0x00); // version
300        data.push(0x00); // predictor_type
301        data.push(0x01); // pixel_format = G8
302        data.extend_from_slice(&width.to_le_bytes());
303        data.extend_from_slice(&height.to_le_bytes());
304        data.extend_from_slice(pixels);
305        data
306    }
307
308    /// 构造 KCBC 容器,tiles: &[(row, col, row_hint, dmp2_data)]
309    fn build_kcbc(tiles: &[(u32, u32, u32, &[u8])]) -> Vec<u8> {
310        let mut data = Vec::new();
311        for &(row, col, row_hint, dmp2) in tiles {
312            let block_len = (20 + dmp2.len()) as u32;
313            data.extend_from_slice(b"KCBC");
314            data.extend_from_slice(&col.to_le_bytes());
315            data.extend_from_slice(&row.to_le_bytes());
316            data.extend_from_slice(&row_hint.to_le_bytes());
317            data.extend_from_slice(&block_len.to_le_bytes());
318            data.extend_from_slice(dmp2);
319        }
320        data
321    }
322
323    #[test]
324    fn decode_kcbc_single_tile_matches_decode() {
325        let pixels = [10u8, 20, 30, 40];
326        let dmp2 = build_dmp2_none_g8(2, 2, &pixels);
327
328        let kcbc = build_kcbc(&[(0, 0, 2, &dmp2)]);
329        let kcbc_img = crate::decode_kcbc(&kcbc).unwrap();
330        let raw_img = crate::decode(&dmp2).unwrap();
331
332        assert_eq!(kcbc_img.width, raw_img.width);
333        assert_eq!(kcbc_img.height, raw_img.height);
334        assert_eq!(kcbc_img.rgba, raw_img.rgba);
335    }
336
337    #[test]
338    fn decode_kcbc_multi_band_vertical_stitch() {
339        let top = build_dmp2_none_g8(2, 2, &[1, 2, 3, 4]);
340        let middle = build_dmp2_none_g8(2, 1, &[5, 6]);
341        let bottom = build_dmp2_none_g8(2, 2, &[7, 8, 9, 10]);
342
343        let kcbc = build_kcbc(&[(0, 0, 2, &top), (1, 0, 1, &middle), (2, 0, 2, &bottom)]);
344
345        let img = crate::decode_kcbc(&kcbc).unwrap();
346        assert_eq!(img.width, 2);
347        assert_eq!(img.height, 5);
348
349        let px = |gray: u8| -> [u8; 4] { [gray, gray, gray, 0xFF] };
350        let expected: Vec<u8> = [
351            px(1),
352            px(2),
353            px(3),
354            px(4),
355            px(5),
356            px(6),
357            px(7),
358            px(8),
359            px(9),
360            px(10),
361        ]
362        .concat();
363        assert_eq!(img.rgba, expected);
364    }
365
366    #[test]
367    fn decode_kcbc_grid_tiles_respect_row_and_col_indices() {
368        let top_left = build_dmp2_none_g8(1, 1, &[1]);
369        let top_right = build_dmp2_none_g8(1, 1, &[2]);
370        let bottom_left = build_dmp2_none_g8(1, 1, &[3]);
371        let bottom_right = build_dmp2_none_g8(1, 1, &[4]);
372
373        let kcbc = build_kcbc(&[
374            (0, 0, 1, &top_left),
375            (0, 1, 1, &top_right),
376            (1, 0, 1, &bottom_left),
377            (1, 1, 1, &bottom_right),
378        ]);
379
380        let img = decode_kcbc_sequence_with_options(&kcbc, Some(2), Some(2), None).unwrap();
381        assert_eq!(img.width, 2);
382        assert_eq!(img.height, 2);
383
384        let px = |gray: u8| -> [u8; 4] { [gray, gray, gray, 0xFF] };
385        let expected: Vec<u8> = [px(1), px(2), px(3), px(4)].concat();
386        assert_eq!(img.rgba, expected);
387    }
388
389    #[test]
390    fn decode_kcbc_rejects_stitched_dimensions_exceeding_u16() {
391        for vertical in [false, true] {
392            let (width, height) = if vertical {
393                (1, u16::MAX)
394            } else {
395                (u16::MAX, 1)
396            };
397            let large = build_dmp2_none_g8(width, height, &vec![5; u16::MAX as usize]);
398            let extra = build_dmp2_none_g8(1, 1, &[6]);
399            let (row, col) = if vertical { (1, 0) } else { (0, 1) };
400            let kcbc = build_kcbc(&[(0, 0, 0, &large), (row, col, 0, &extra)]);
401            let err = crate::decode_kcbc(&kcbc)
402                .err()
403                .expect("stitched dimensions must fit u16");
404            assert!(format!("{err}").contains("u16"));
405        }
406    }
407
408    #[test]
409    fn decode_kcbc_accepts_stitched_dimensions_at_u16_limit() {
410        for vertical in [false, true] {
411            let (width, height) = if vertical {
412                (1, u16::MAX - 1)
413            } else {
414                (u16::MAX - 1, 1)
415            };
416            let large = build_dmp2_none_g8(width, height, &vec![5; (u16::MAX - 1) as usize]);
417            let extra = build_dmp2_none_g8(1, 1, &[6]);
418            let (row, col) = if vertical { (1, 0) } else { (0, 1) };
419            let kcbc = build_kcbc(&[(0, 0, 0, &large), (row, col, 0, &extra)]);
420            let image = crate::decode_kcbc(&kcbc).unwrap();
421            let expected_size = if vertical {
422                (1, u16::MAX)
423            } else {
424                (u16::MAX, 1)
425            };
426            assert_eq!((image.width, image.height), expected_size);
427            assert_eq!(image.rgba.len(), u16::MAX as usize * 4);
428            assert_eq!(&image.rgba[image.rgba.len() - 4..], &[6, 6, 6, 255]);
429        }
430    }
431
432    #[test]
433    fn decode_tile_rejects_row_hint_exceeding_u16() {
434        let dmp2 = build_dmp2_none_g8(2, 2, &[0; 4]);
435        let kcbc = build_kcbc(&[(0, 0, 65536, &dmp2)]); // u16::MAX + 1
436        let result = crate::decode_kcbc(&kcbc);
437        let err = result.err().expect("should error on oversized row_hint");
438        let msg = format!("{err}");
439        assert!(msg.contains("u16"), "should mention u16 overflow: {msg}");
440    }
441
442    #[test]
443    fn parse_kcbc_empty_data_returns_error() {
444        let result = parse_kcbc(b"no kcbc here");
445        assert!(result.is_err());
446    }
447
448    #[test]
449    fn parse_kcbc_truncated_header_returns_error() {
450        // KCBC magic 后面只有 10 字节(不足 16 字节头部数据),应返回错误
451        let mut data = b"KCBC".to_vec();
452        data.extend_from_slice(&[0u8; 10]);
453        let result = parse_kcbc(&data);
454        assert!(result.is_err());
455        let err_msg = format!("{}", result.unwrap_err());
456        assert!(
457            err_msg.contains("截断"),
458            "error should mention truncation: {err_msg}"
459        );
460    }
461
462    #[test]
463    fn parse_kcbc_truncated_second_block_returns_error() {
464        // 第一个完整 KCBC 块 + 第二个截断的 KCBC 块
465        let mut data = Vec::new();
466        // Block 1: KCBC header (20 bytes) + "dmp2" magic + some payload
467        data.extend_from_slice(b"KCBC");
468        data.extend_from_slice(&0u32.to_le_bytes()); // col_index
469        data.extend_from_slice(&0u32.to_le_bytes()); // row_index
470        data.extend_from_slice(&10u32.to_le_bytes()); // row_hint
471        data.extend_from_slice(&0u32.to_le_bytes()); // block_length=0 (will use next KCBC)
472        data.extend_from_slice(b"dmp2");
473        data.extend_from_slice(&[0u8; 8]); // some dmp2 data
474        // Block 2: truncated KCBC (only magic + 8 bytes, need 16 more)
475        data.extend_from_slice(b"KCBC");
476        data.extend_from_slice(&[0u8; 8]); // only 8 bytes, need 16
477
478        let result = parse_kcbc(&data);
479        assert!(result.is_err(), "should error on truncated second block");
480    }
481
482    #[test]
483    fn find_bytes_found() {
484        assert_eq!(find_bytes(b"hello world", b"world"), Some(6));
485    }
486
487    #[test]
488    fn find_bytes_not_found() {
489        assert_eq!(find_bytes(b"hello", b"xyz"), None);
490    }
491}