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