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 = 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
245fn 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 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); data.push(0x00); data.push(0x00); data.push(0x01); 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 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)]); 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 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 let mut data = Vec::new();
466 data.extend_from_slice(b"KCBC");
468 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");
473 data.extend_from_slice(&[0u8; 8]); data.extend_from_slice(b"KCBC");
476 data.extend_from_slice(&[0u8; 8]); 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}