1use std::collections::BTreeMap;
2
3use crate::model::PixelFormat;
4use crate::{DecodedImage, Deepmap2Error, Deepmap2Result};
5
6struct KcbcBlock<'a> {
8 #[allow(dead_code)]
9 offset: usize,
10 #[allow(dead_code)]
12 col_index: u32,
13 #[allow(dead_code)]
15 row_index: u32,
16 row_hint: u32,
18 dmp2_data: &'a [u8],
20}
21
22fn 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 let block_end = if block_length >= 0x20 && block_offset + block_length <= data.len() {
60 block_offset + block_length
61 } else {
62 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 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
88pub fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
90 haystack.windows(needle.len()).position(|w| w == needle)
91}
92
93pub 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
106pub 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
235fn 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 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); data.push(0x00); data.push(0x00); data.push(0x01); 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 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)]); 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 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 let mut data = Vec::new();
413 data.extend_from_slice(b"KCBC");
415 data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(&10u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes()); data.extend_from_slice(b"dmp2");
420 data.extend_from_slice(&[0u8; 8]); data.extend_from_slice(b"KCBC");
423 data.extend_from_slice(&[0u8; 8]); 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}