Skip to main content

deepmap2/
codec.rs

1use lzfse_rust::LzfseRingDecoder;
2
3use crate::apple_compression::{self, AppleCompressionError};
4use crate::color::{output_bytes_to_rgba, palette_entry_to_rgba, row_to_rgba};
5use crate::model::{DecodeType, Deepmap2Header, PixelFormat};
6use crate::predictor::apply_predictor;
7use crate::{Deepmap2Error, Deepmap2Result};
8
9/// Apple Deepmap2 Lossless 算法选择阈值:
10/// 期望输出大小 < 0x1000 → LZVN,否则 → LZFSE
11const LZVN_THRESHOLD: usize = 0x1000;
12const APPLE_STREAM_SCAN_LIMIT: usize = 8;
13const APPLE_STREAM_EOS_MAGIC: &[u8; 4] = b"bvx$";
14
15fn apply_pixel_format_override(
16    header: &Deepmap2Header,
17    pixel_format_override: Option<PixelFormat>,
18) -> Deepmap2Header {
19    let mut header = header.clone();
20    if let Some(pixel_format) = pixel_format_override {
21        header.pixel_format = pixel_format;
22    }
23    header
24}
25
26fn apple_stream_slice(data: &[u8]) -> &[u8] {
27    if apple_compression::is_apple_compression_stream(data) {
28        return data;
29    }
30
31    let scan_limit = data.len().min(APPLE_STREAM_SCAN_LIMIT);
32    if let Some(marker) = data[..scan_limit]
33        .windows(4)
34        .position(apple_compression::is_apple_compression_stream)
35    {
36        &data[marker..]
37    } else {
38        data
39    }
40}
41
42fn map_apple_compression_error(error: AppleCompressionError) -> Deepmap2Error {
43    match error {
44        AppleCompressionError::NativeFallbackUnavailable(message) => {
45            Deepmap2Error::NativeFallbackUnavailable(message)
46        }
47        AppleCompressionError::DecodeFailed(_) | AppleCompressionError::Io(_) => {
48            Deepmap2Error::LzfseDecompress
49        }
50    }
51}
52
53fn is_apple_stream_start_magic(data: &[u8]) -> bool {
54    matches!(data.get(..4), Some(b"bvx2") | Some(b"bvxn") | Some(b"bvx-"))
55}
56
57fn decode_lzfse_rust_only(data: &[u8]) -> Option<Vec<u8>> {
58    let cap = data.len().saturating_mul(16).max(4096);
59    let mut out = Vec::with_capacity(cap);
60    let mut decoder = LzfseRingDecoder::default();
61    if let Ok(n) = decoder.decode_bytes(data, &mut out)
62        && (n > 0 || data.is_empty())
63    {
64        out.truncate(n as usize);
65        Some(out)
66    } else {
67        None
68    }
69}
70
71fn find_concatenated_apple_segment_boundary(data: &[u8]) -> Option<(usize, usize)> {
72    let mut search_offset = 0usize;
73    while search_offset + APPLE_STREAM_EOS_MAGIC.len() <= data.len() {
74        let rel = data[search_offset..]
75            .windows(APPLE_STREAM_EOS_MAGIC.len())
76            .position(|window| window == APPLE_STREAM_EOS_MAGIC)?;
77        let eos_pos = search_offset + rel;
78        let next_scan_start = eos_pos + APPLE_STREAM_EOS_MAGIC.len();
79        let next_scan_end = data.len().min(next_scan_start + APPLE_STREAM_SCAN_LIMIT);
80
81        if let Some(next_rel) = data[next_scan_start..next_scan_end]
82            .windows(4)
83            .position(is_apple_stream_start_magic)
84        {
85            return Some((next_scan_start, next_scan_start + next_rel));
86        }
87
88        search_offset = next_scan_start;
89    }
90
91    None
92}
93
94fn decompress_concatenated_apple_stream(data: &[u8]) -> Deepmap2Result<Vec<u8>> {
95    let mut decoded = Vec::new();
96    let mut cursor = 0usize;
97
98    loop {
99        let remaining = &data[cursor..];
100        let segment = apple_stream_slice(remaining);
101        let start_offset = remaining.len().saturating_sub(segment.len());
102        if start_offset > APPLE_STREAM_SCAN_LIMIT
103            || !apple_compression::is_apple_compression_stream(segment)
104        {
105            return Err(Deepmap2Error::LzfseDecompress);
106        }
107        cursor += start_offset;
108
109        if let Some((segment_end, next_segment_start)) =
110            find_concatenated_apple_segment_boundary(&data[cursor..])
111        {
112            let segment = &data[cursor..cursor + segment_end];
113            let part = decode_lzfse_rust_only(segment).ok_or(Deepmap2Error::LzfseDecompress)?;
114            decoded.extend_from_slice(&part);
115            cursor += next_segment_start;
116            continue;
117        }
118
119        let part = decode_lzfse_rust_only(&data[cursor..]).ok_or(Deepmap2Error::LzfseDecompress)?;
120        decoded.extend_from_slice(&part);
121        return Ok(decoded);
122    }
123}
124
125/// LZFSE 解压,返回解压后字节(错误时返回 LzfseDecompress)
126fn decompress_lzfse(data: &[u8]) -> Deepmap2Result<Vec<u8>> {
127    let data = apple_stream_slice(data);
128    let is_apple_stream = apple_compression::is_apple_compression_stream(data);
129    if let Some(decoded) = decode_lzfse_rust_only(data) {
130        return Ok(decoded);
131    }
132
133    if !is_apple_stream {
134        return Err(Deepmap2Error::LzfseDecompress);
135    }
136
137    if let Ok(decoded) = decompress_concatenated_apple_stream(data) {
138        return Ok(decoded);
139    }
140
141    apple_compression::decode_lzfse_with_fallback(data).map_err(map_apple_compression_error)
142}
143
144/// LZVN 解压(raw stream),返回解压后字节
145pub fn decompress_lzvn(data: &[u8], expected_output_size: usize) -> Deepmap2Result<Vec<u8>> {
146    lzvn::decode_raw(data, expected_output_size).map_err(|_| Deepmap2Error::LzvnDecompress)
147}
148
149/// 尝试将 payload 解析为 size-prefixed 分块格式
150///
151/// 格式: [u32 LE chunk_size][chunk_data] 重复,直至消耗完所有字节。
152/// 若不满足则返回 None(回退到整体处理)。
153fn parse_size_prefixed_chunks(payload: &[u8]) -> Option<Vec<&[u8]>> {
154    let mut chunks = Vec::new();
155    let mut cursor = 0usize;
156    while cursor + 4 <= payload.len() {
157        let chunk_size = u32::from_le_bytes(payload[cursor..cursor + 4].try_into().ok()?) as usize;
158        cursor += 4;
159        if chunk_size == 0 || cursor + chunk_size > payload.len() {
160            return None;
161        }
162        chunks.push(&payload[cursor..cursor + chunk_size]);
163        cursor += chunk_size;
164    }
165    if cursor != payload.len() || chunks.is_empty() {
166        return None;
167    }
168    Some(chunks)
169}
170
171fn decompress_lzfse_or_lzvn(data: &[u8], expected_output_size: usize) -> Deepmap2Result<Vec<u8>> {
172    match decompress_lzfse(data) {
173        Ok(bytes) => Ok(bytes),
174        Err(err @ Deepmap2Error::LzfseDecompress) => {
175            decompress_lzvn(data, expected_output_size).map_err(|_| err)
176        }
177        Err(err) => Err(err),
178    }
179}
180
181/// 解码 Default 路径(Type 2)的解压后数据为 RGBA
182///
183/// 解压后数据布局:
184/// - alpha_plane:    [width * height] bytes(仅 has_alpha 格式)
185/// - predictor_bytes: [height] bytes(每行一个预测器类型)
186/// - high_stream:    [width * height * components] bytes
187/// - low_stream:     [width * height * components] bytes
188fn decode_default_decompressed(
189    header: &Deepmap2Header,
190    decompressed: &[u8],
191    width: usize,
192    height: usize,
193) -> Deepmap2Result<Vec<u8>> {
194    let (rgba, _) =
195        decode_default_decompressed_with_state(header, decompressed, width, height, None)?;
196    Ok(rgba)
197}
198
199fn decode_default_decompressed_with_state(
200    header: &Deepmap2Header,
201    decompressed: &[u8],
202    width: usize,
203    height: usize,
204    mut prev_row: Option<Vec<i16>>,
205) -> Deepmap2Result<(Vec<u8>, Option<Vec<i16>>)> {
206    let pixel_format = header.pixel_format;
207    let has_alpha = pixel_format.has_alpha();
208    let components = pixel_format.split_stream_components();
209    let pixel_count = width * height;
210    let alpha_size = if has_alpha { pixel_count } else { 0 };
211    let split_count = pixel_count * components;
212
213    let predictor_offset = alpha_size;
214    let predictor_end = predictor_offset + height;
215    let high_offset = predictor_end;
216    let high_end = high_offset + split_count;
217    let low_offset = high_end;
218    let low_end = low_offset + split_count;
219
220    if decompressed.len() < low_end {
221        return Err(Deepmap2Error::InvalidData(format!(
222            "Default 解压后数据不足: 需要 {} 字节,实际 {}",
223            low_end,
224            decompressed.len()
225        )));
226    }
227
228    let alpha_plane = if has_alpha {
229        Some(&decompressed[..alpha_size])
230    } else {
231        None
232    };
233    let predictor_bytes = &decompressed[predictor_offset..predictor_end];
234    let high_stream = &decompressed[high_offset..high_end];
235    let low_stream = &decompressed[low_offset..low_end];
236
237    let split_row_width = width * components;
238    let chroma_scale = header.chroma_scale();
239    let mut rgba = vec![0u8; pixel_count * 4];
240
241    for row in 0..height {
242        let row_offset = row * split_row_width;
243        let high_row = &high_stream[row_offset..row_offset + split_row_width];
244        let low_row = &low_stream[row_offset..row_offset + split_row_width];
245
246        let decoded_split_row: Vec<i16> = low_row
247            .iter()
248            .zip(high_row.iter())
249            .map(|(&lo, &hi)| {
250                let combined = (lo as u16) | ((hi as u16) << 8);
251                let magnitude = (combined >> 1) as i16;
252                // Apple deepmap2 zigzag: 偶数编码正值,奇数编码负值(负值 = -magnitude)
253                if combined & 1 != 0 {
254                    -magnitude
255                } else {
256                    magnitude
257                }
258            })
259            .collect();
260
261        // 灰度格式需要扩展为 3 分量组 [Y, 0, 0],与 predictor 和 row_to_rgba 的
262        // PREDICTOR_GROUP_SIZE=3 保持一致(匹配 Python 参考实现的 Deepmap2Coder 行为)
263        let decoded_split_row: Vec<i16> = if !pixel_format.is_color() {
264            decoded_split_row
265                .iter()
266                .flat_map(|&y| [y, 0i16, 0i16])
267                .collect()
268        } else {
269            decoded_split_row
270        };
271
272        let pred_type = predictor_bytes[row];
273        let predicted_row = apply_predictor(pred_type, &decoded_split_row, prev_row.as_deref());
274
275        let alpha_row = alpha_plane.map(|ap| &ap[row * width..(row + 1) * width]);
276        let rgba_row = row_to_rgba(pixel_format, &predicted_row, alpha_row, chroma_scale);
277
278        let base = row * width * 4;
279        rgba[base..base + rgba_row.len()].copy_from_slice(&rgba_row);
280
281        prev_row = Some(predicted_row);
282    }
283
284    Ok((rgba, prev_row))
285}
286
287/// Type 2 路径: LZFSE 压缩多流 + zigzag + 预测器 + YCoCg → RGBA
288fn decode_default(
289    header: &Deepmap2Header,
290    payload: &[u8],
291    width: usize,
292    height: usize,
293) -> Deepmap2Result<Vec<u8>> {
294    let decompressed = decompress_lzfse(payload)?;
295    decode_default_decompressed(header, &decompressed, width, height)
296}
297
298fn decode_default_with_state(
299    header: &Deepmap2Header,
300    payload: &[u8],
301    width: usize,
302    height: usize,
303    prev_row: Option<Vec<i16>>,
304) -> Deepmap2Result<(Vec<u8>, Option<Vec<i16>>)> {
305    let decompressed = decompress_lzfse(payload)?;
306    decode_default_decompressed_with_state(header, &decompressed, width, height, prev_row)
307}
308
309/// Type 1/3 路径: 原始或压缩的像素数据 → RGBA
310///
311/// Type 3 (Lossless) 按 Apple _Deepmap2DecodeLossless 阈值选算法:
312/// - 期望输出 < 0x1000 → 优先 LZVN,失败时回退 LZFSE
313/// - 期望输出 >= 0x1000 → LZFSE
314///
315/// Lossless header 不携带具体压缩算法,因此对小尺寸 payload
316/// 必须保留 LZFSE 回退以兼容实际以 LZFSE 压缩的小图。
317fn decode_none_or_lossless(
318    header: &Deepmap2Header,
319    payload: &[u8],
320    compressed: bool,
321    width: usize,
322    height: usize,
323) -> Deepmap2Result<Vec<u8>> {
324    let pixel_format = header.pixel_format;
325    let expected = width * height * pixel_format.bytes_per_pixel();
326    let output_bytes = if compressed {
327        if expected < LZVN_THRESHOLD {
328            match decompress_lzvn(payload, expected) {
329                Ok(bytes) => bytes,
330                Err(_) => decompress_lzfse(payload)?,
331            }
332        } else {
333            decompress_lzfse(payload)?
334        }
335    } else {
336        payload.to_vec()
337    };
338    if output_bytes.len() < expected {
339        return Err(Deepmap2Error::InvalidData(format!(
340            "输出数据不足: 期望 {} 字节,实际 {}",
341            expected,
342            output_bytes.len()
343        )));
344    }
345    Ok(output_bytes_to_rgba(
346        pixel_format,
347        width,
348        height,
349        &output_bytes[..expected],
350    ))
351}
352
353/// Type 4 路径: 调色板索引 → RGBA
354fn decode_palette(
355    header: &Deepmap2Header,
356    payload: &[u8],
357    width: usize,
358    height: usize,
359) -> Deepmap2Result<Vec<u8>> {
360    let palette_size = header.palette_size.unwrap_or(0) as usize;
361    let palette_type = header.palette_type.unwrap_or(0);
362    if palette_size == 0 {
363        return Err(Deepmap2Error::InvalidData(
364            "Palette 模式缺少调色板".to_string(),
365        ));
366    }
367    let palette = &header.palette;
368
369    let pixel_count = width * height;
370    let expected = match palette_type {
371        3 => pixel_count * 2,
372        4 => pixel_count,
373        other => return Err(Deepmap2Error::InvalidPaletteType(other)),
374    };
375    let decompressed = if let Some(chunks) = parse_size_prefixed_chunks(payload) {
376        let mut out = Vec::with_capacity(expected);
377        for chunk in chunks {
378            if out.len() >= expected {
379                break;
380            }
381            let remaining = expected.saturating_sub(out.len());
382            let part = decompress_lzfse_or_lzvn(chunk, remaining)?;
383            out.extend_from_slice(&part);
384        }
385        out
386    } else {
387        decompress_lzfse_or_lzvn(payload, expected)?
388    };
389
390    match palette_type {
391        3 => {
392            // alpha_plane + index_plane,各 pixel_count 字节
393            let needed = expected;
394            if decompressed.len() < needed {
395                return Err(Deepmap2Error::InvalidData(format!(
396                    "Palette type3 数据不足: 需要 {} 字节,实际 {}",
397                    needed,
398                    decompressed.len()
399                )));
400            }
401            let alpha_plane = &decompressed[..pixel_count];
402            let index_plane = &decompressed[pixel_count..pixel_count * 2];
403            let mut rgba = vec![0u8; pixel_count * 4];
404            for (i, (&idx, &alpha)) in index_plane.iter().zip(alpha_plane.iter()).enumerate() {
405                let entry = palette.get(idx as usize).copied().unwrap_or(0);
406                let bytes = palette_entry_to_rgba(entry, Some(alpha));
407                rgba[i * 4..i * 4 + 4].copy_from_slice(&bytes);
408            }
409            Ok(rgba)
410        }
411        4 => {
412            // 纯索引,pixel_count 字节
413            let needed = expected;
414            if decompressed.len() < needed {
415                return Err(Deepmap2Error::InvalidData(format!(
416                    "Palette type4 数据不足: 需要 {} 字节,实际 {}",
417                    needed,
418                    decompressed.len()
419                )));
420            }
421            let mut rgba = vec![0u8; pixel_count * 4];
422            for (i, &idx) in decompressed[..needed].iter().enumerate() {
423                let entry = palette.get(idx as usize).copied().unwrap_or(0);
424                let bytes = palette_entry_to_rgba(entry, None);
425                rgba[i * 4..i * 4 + 4].copy_from_slice(&bytes);
426            }
427            Ok(rgba)
428        }
429        other => Err(Deepmap2Error::InvalidPaletteType(other)),
430    }
431}
432
433/// 处理 size-prefixed 分块 payload(Default 路径)
434///
435/// source_height 从解压数据大小反推,最后一个 chunk 可能含填充行导致
436/// sum(source_heights) > total_height,因此已达目标高度后 break 跳过剩余 chunk。
437fn decode_size_prefixed_default(
438    header: &Deepmap2Header,
439    chunks: &[&[u8]],
440    width: usize,
441    total_height: usize,
442) -> Deepmap2Result<Vec<u8>> {
443    let pixel_format = header.pixel_format;
444    let components = pixel_format.split_stream_components();
445    let has_alpha = pixel_format.has_alpha();
446
447    // row_stride 用于从解压后数据大小反推行数
448    let row_stride = (if has_alpha { width } else { 0 }) + 1 + width * components * 2;
449
450    let mut rgba = vec![0u8; width * total_height * 4];
451    let mut cursor_height = 0usize;
452
453    for chunk in chunks {
454        let remaining = total_height - cursor_height;
455        if remaining == 0 {
456            break;
457        }
458        let decompressed = decompress_lzfse(chunk)?;
459        let source_height = decompressed.len().checked_div(row_stride).unwrap_or(0);
460        if source_height == 0 {
461            return Err(Deepmap2Error::InvalidData(
462                "无法从 Default chunk 推断有效高度".to_string(),
463            ));
464        }
465        let chunk_h = source_height.min(remaining);
466
467        let alpha_size = if has_alpha { width * chunk_h } else { 0 };
468        let predictor_size = chunk_h;
469        let split_count = width * components * chunk_h;
470        let needed = alpha_size + predictor_size + split_count * 2;
471        let truncated: &[u8] = if decompressed.len() >= needed {
472            &decompressed[..needed]
473        } else {
474            decompressed.as_slice()
475        };
476
477        let chunk_rgba = decode_default_decompressed(header, truncated, width, chunk_h)?;
478        let copy_len = (width * chunk_h * 4).min(chunk_rgba.len());
479        let dst = cursor_height * width * 4;
480        rgba[dst..dst + copy_len].copy_from_slice(&chunk_rgba[..copy_len]);
481        cursor_height += chunk_h;
482    }
483
484    if cursor_height != total_height {
485        return Err(Deepmap2Error::InvalidData(format!(
486            "size-prefixed 容器输出高度不足: 期望 {total_height},实际 {cursor_height}"
487        )));
488    }
489
490    Ok(rgba)
491}
492
493fn decode_size_prefixed_default_with_state(
494    header: &Deepmap2Header,
495    chunks: &[&[u8]],
496    width: usize,
497    total_height: usize,
498    mut prev_row: Option<Vec<i16>>,
499) -> Deepmap2Result<(Vec<u8>, Option<Vec<i16>>)> {
500    let pixel_format = header.pixel_format;
501    let components = pixel_format.split_stream_components();
502    let has_alpha = pixel_format.has_alpha();
503
504    // row_stride 用于从解压后数据大小反推行数
505    let row_stride = (if has_alpha { width } else { 0 }) + 1 + width * components * 2;
506
507    let mut rgba = vec![0u8; width * total_height * 4];
508    let mut cursor_height = 0usize;
509
510    for chunk in chunks {
511        let remaining = total_height - cursor_height;
512        if remaining == 0 {
513            break;
514        }
515        let decompressed = decompress_lzfse(chunk)?;
516        let source_height = decompressed.len().checked_div(row_stride).unwrap_or(0);
517        if source_height == 0 {
518            return Err(Deepmap2Error::InvalidData(
519                "无法从 Default chunk 推断有效高度".to_string(),
520            ));
521        }
522        let chunk_h = source_height.min(remaining);
523
524        let alpha_size = if has_alpha { width * chunk_h } else { 0 };
525        let predictor_size = chunk_h;
526        let split_count = width * components * chunk_h;
527        let needed = alpha_size + predictor_size + split_count * 2;
528        let truncated: &[u8] = if decompressed.len() >= needed {
529            &decompressed[..needed]
530        } else {
531            decompressed.as_slice()
532        };
533
534        let (chunk_rgba, next_prev_row) =
535            decode_default_decompressed_with_state(header, truncated, width, chunk_h, prev_row)?;
536        let copy_len = (width * chunk_h * 4).min(chunk_rgba.len());
537        let dst = cursor_height * width * 4;
538        rgba[dst..dst + copy_len].copy_from_slice(&chunk_rgba[..copy_len]);
539        cursor_height += chunk_h;
540        prev_row = next_prev_row;
541    }
542
543    if cursor_height != total_height {
544        return Err(Deepmap2Error::InvalidData(format!(
545            "size-prefixed 容器输出高度不足: 期望 {total_height},实际 {cursor_height}"
546        )));
547    }
548
549    Ok((rgba, prev_row))
550}
551
552/// 处理 size-prefixed 分块 payload(None / Lossless 路径)
553#[allow(dead_code)]
554fn decode_size_prefixed_none_or_lossless(
555    header: &Deepmap2Header,
556    chunks: &[&[u8]],
557    compressed: bool,
558    width: usize,
559    total_height: usize,
560) -> Deepmap2Result<Vec<u8>> {
561    // 匹配参考实现: source_heights = [header.height] * len(chunks)
562    let source_height = header.height as usize;
563
564    let mut rgba = vec![0u8; width * total_height * 4];
565    let mut cursor_height = 0usize;
566
567    for chunk in chunks {
568        let remaining = total_height - cursor_height;
569        if remaining == 0 {
570            return Err(Deepmap2Error::InvalidData(
571                "size-prefixed 容器包含超出目标高度的额外 chunk".to_string(),
572            ));
573        }
574        let chunk_height = source_height.min(remaining);
575        let chunk_rgba = decode_none_or_lossless(header, chunk, compressed, width, chunk_height)?;
576        let copy_len = (width * chunk_height * 4).min(chunk_rgba.len());
577        let dst = cursor_height * width * 4;
578        rgba[dst..dst + copy_len].copy_from_slice(&chunk_rgba[..copy_len]);
579        cursor_height += chunk_height;
580    }
581
582    if cursor_height != total_height {
583        return Err(Deepmap2Error::InvalidData(format!(
584            "size-prefixed 容器输出高度不足: 期望 {total_height},实际 {cursor_height}"
585        )));
586    }
587
588    Ok(rgba)
589}
590
591/// 解码 payload 为 RGBA(考虑 size-prefixed 分块)
592fn decode_payload(
593    header: &Deepmap2Header,
594    payload: &[u8],
595    width: usize,
596    height: usize,
597) -> Deepmap2Result<Vec<u8>> {
598    let decode_type = header.decode_type;
599
600    // 拒绝不支持的像素格式,避免静默产出空数据
601    if let PixelFormat::Unknown { tag } = header.pixel_format {
602        return Err(Deepmap2Error::UnsupportedPixelFormat(tag));
603    }
604
605    match decode_type {
606        DecodeType::Default => {
607            if let Some(chunks) = parse_size_prefixed_chunks(payload) {
608                decode_size_prefixed_default(header, &chunks, width, height)
609            } else {
610                decode_default(header, payload, width, height)
611            }
612        }
613        DecodeType::Lossless => {
614            if let Some(chunks) = parse_size_prefixed_chunks(payload) {
615                decode_size_prefixed_none_or_lossless(header, &chunks, true, width, height)
616            } else {
617                decode_none_or_lossless(header, payload, true, width, height)
618            }
619        }
620        DecodeType::None => decode_none_or_lossless(header, payload, false, width, height),
621        DecodeType::Palette => decode_palette(header, payload, width, height),
622        DecodeType::Unknown { tag } => Err(Deepmap2Error::UnsupportedDecodeType(tag)),
623    }
624}
625
626fn decode_payload_with_state(
627    header: &Deepmap2Header,
628    payload: &[u8],
629    width: usize,
630    height: usize,
631    prev_row: Option<Vec<i16>>,
632) -> Deepmap2Result<(Vec<u8>, Option<Vec<i16>>)> {
633    if let PixelFormat::Unknown { tag } = header.pixel_format {
634        return Err(Deepmap2Error::UnsupportedPixelFormat(tag));
635    }
636
637    match header.decode_type {
638        DecodeType::Default => {
639            if let Some(chunks) = parse_size_prefixed_chunks(payload) {
640                decode_size_prefixed_default_with_state(header, &chunks, width, height, prev_row)
641            } else {
642                decode_default_with_state(header, payload, width, height, prev_row)
643            }
644        }
645        _ => Ok((decode_payload(header, payload, width, height)?, None)),
646    }
647}
648
649/// 解码 payload 为 RGBA,使用 header 中的 width / height
650pub fn decode_raw(header: &Deepmap2Header, payload: &[u8]) -> Deepmap2Result<Vec<u8>> {
651    decode_raw_with_options(header, payload, None, None, None)
652}
653
654/// 解码 payload 为 RGBA,使用指定的输出高度(KCBC tile 路径)
655pub fn decode_raw_with_height(
656    header: &Deepmap2Header,
657    payload: &[u8],
658    output_height: u16,
659) -> Deepmap2Result<Vec<u8>> {
660    decode_raw_with_options(header, payload, None, Some(output_height), None)
661}
662
663pub fn decode_raw_with_options(
664    header: &Deepmap2Header,
665    payload: &[u8],
666    output_width: Option<u16>,
667    output_height: Option<u16>,
668    pixel_format_override: Option<PixelFormat>,
669) -> Deepmap2Result<Vec<u8>> {
670    let header = apply_pixel_format_override(header, pixel_format_override);
671    decode_payload(
672        &header,
673        payload,
674        output_width.unwrap_or(header.width) as usize,
675        output_height.unwrap_or(header.height) as usize,
676    )
677}
678
679pub fn decode_raw_with_options_and_state(
680    header: &Deepmap2Header,
681    payload: &[u8],
682    output_width: Option<u16>,
683    output_height: Option<u16>,
684    pixel_format_override: Option<PixelFormat>,
685    prev_row: Option<Vec<i16>>,
686) -> Deepmap2Result<(Vec<u8>, Option<Vec<i16>>)> {
687    let header = apply_pixel_format_override(header, pixel_format_override);
688    decode_payload_with_state(
689        &header,
690        payload,
691        output_width.unwrap_or(header.width) as usize,
692        output_height.unwrap_or(header.height) as usize,
693        prev_row,
694    )
695}
696
697#[cfg(test)]
698mod tests {
699    use super::decompress_lzfse;
700    use lzfse_rust::{LzfseRingDecoder, LzfseRingEncoder};
701
702    #[cfg(target_arch = "wasm32")]
703    use crate::Deepmap2Error;
704
705    fn encode_lzfse(data: &[u8]) -> Vec<u8> {
706        let mut encoded = Vec::new();
707        let mut encoder = LzfseRingEncoder::default();
708        encoder
709            .encode_bytes(data, &mut encoded)
710            .expect("test lzfse encode should succeed");
711        encoded
712    }
713
714    fn build_palette4_size_prefixed_lzvn(width: u16, height: u16, indexes: &[u8]) -> Vec<u8> {
715        assert_eq!(indexes.len(), width as usize * height as usize);
716
717        let palette = [
718            0xFFFF0000u32, // red
719            0xFF00FF00u32, // green
720            0xFF0000FFu32, // blue
721        ];
722        let compressed = lzvn::encode_raw(indexes);
723        let mut data = Vec::new();
724        data.extend_from_slice(b"dmp2");
725        data.push(4); // Palette
726        data.push(0);
727        data.push(0);
728        data.push(4); // Rgba8888
729        data.extend_from_slice(&width.to_le_bytes());
730        data.extend_from_slice(&height.to_le_bytes());
731        data.extend_from_slice(&(palette.len() as u16).to_le_bytes());
732        data.extend_from_slice(&4u16.to_le_bytes()); // palette type 4: index plane
733        for entry in palette {
734            data.extend_from_slice(&entry.to_le_bytes());
735        }
736        data.extend_from_slice(&(compressed.len() as u32).to_le_bytes());
737        data.extend_from_slice(&compressed);
738        data
739    }
740
741    #[test]
742    fn single_apple_stream_keeps_pure_rust_decode_path() {
743        let expected = b"single-stream-decode-regression";
744        let encoded = encode_lzfse(expected);
745
746        let decoded = decompress_lzfse(&encoded).expect("single Apple stream should decode");
747        assert_eq!(decoded, expected);
748    }
749
750    #[test]
751    fn concatenated_apple_streams_decode_in_pure_rust() {
752        let first_raw = b"deepmap2 concatenated apple stream part one";
753        let second_raw = b"deepmap2 concatenated apple stream part two";
754        let first = encode_lzfse(first_raw);
755        let second = encode_lzfse(second_raw);
756
757        let mut concatenated = Vec::new();
758        concatenated.extend_from_slice(&first);
759        concatenated.extend_from_slice(&[0u8; 4]);
760        concatenated.extend_from_slice(&second);
761
762        let mut single_stream_output = Vec::new();
763        let mut decoder = LzfseRingDecoder::default();
764        assert!(
765            decoder
766                .decode_bytes(&concatenated, &mut single_stream_output)
767                .is_err(),
768            "single-stream decoder should reject concatenated Apple streams"
769        );
770
771        let decoded =
772            decompress_lzfse(&concatenated).expect("concatenated Apple streams should decode");
773        assert_eq!(decoded, [first_raw.as_ref(), second_raw.as_ref()].concat());
774    }
775
776    #[test]
777    fn palette_type4_size_prefixed_lzvn_decodes() {
778        let indexes = [0, 1, 2, 2, 1, 0];
779        let data = build_palette4_size_prefixed_lzvn(3, 2, &indexes);
780
781        let image = crate::decode(&data).expect("palette LZVN payload should decode");
782
783        assert_eq!((image.width, image.height), (3, 2));
784        assert_eq!(
785            image.rgba,
786            vec![
787                255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 255, 0, 255,
788                255, 0, 0, 255,
789            ]
790        );
791    }
792
793    #[cfg(target_arch = "wasm32")]
794    use wasm_bindgen_test::wasm_bindgen_test;
795
796    #[cfg(target_arch = "wasm32")]
797    #[wasm_bindgen_test]
798    fn invalid_non_apple_stream_reports_lzfse_decompress() {
799        let err =
800            decompress_lzfse(b"not-valid-lzfse").expect_err("invalid non-Apple stream should fail");
801        assert!(matches!(err, Deepmap2Error::LzfseDecompress));
802    }
803
804    #[cfg(target_arch = "wasm32")]
805    #[wasm_bindgen_test]
806    fn invalid_apple_stream_reports_native_fallback_unavailable() {
807        let err = decompress_lzfse(b"bvx2not-valid-lzfse")
808            .expect_err("invalid Apple compression stream should fail on wasm fallback path");
809        assert!(matches!(err, Deepmap2Error::NativeFallbackUnavailable(_)));
810    }
811}