1use std::io::Read;
48
49use crate::codec::{apply_transform, div_ceil, split_tiles, tile_sig, Atlas, Dims, TILE_SIZE};
50use crate::error::{Error, Result};
51use crate::mnf6::{
52 compress_stream, decode_record_walk, med_predict, modcolor_forward_iter, pack9, planar_pack,
53 proxy_cost, ycocg_forward, Coder, MnF6Stats, FLAG_PLANAR,
54};
55use crate::tokens;
56
57pub const HEADER8: usize = 32;
59
60pub const FLAG_PRED_MED: u32 = 4; pub const FLAG_TOKENS: u32 = 8;
65pub const FLAG_PRED_PLANE: u32 = 16;
67pub const FLAG_PRED_LEFT: u32 = 32;
69pub const FLAGS_RESERVED8: u32 = !0b111111;
71
72pub const BACKEND_ZLIB: u32 = 0;
74pub const BACKEND_ZSTD19: u32 = 1;
76pub const BACKEND_RESERVED2: u32 = 2; pub const BACKEND_RESERVED3: u32 = 3;
81
82pub const ZSTD_WINDOW_LOG_MAX: u32 = 23;
86
87use crate::codec::RANK_CANDIDATES;
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum Profile {
94 Balanced,
96 Maximum,
98}
99
100fn backend_candidates() -> Vec<(u32, Coder)> {
102 vec![
103 (BACKEND_ZSTD19, Coder::Zstd(19)),
104 (BACKEND_ZLIB, Coder::Zlib),
105 ]
106}
107
108fn backend_compress(backend: u32, buf: &[u8]) -> Result<Vec<u8>> {
109 match backend {
110 BACKEND_ZLIB => Ok(compress_stream(&Coder::Zlib, buf)),
111 BACKEND_ZSTD19 => Ok(compress_stream(&Coder::Zstd(19), buf)),
112 other => Err(Error::fmt_msg(format!(
113 "backend {other} not a compressed-output coder"
114 ))),
115 }
116}
117
118fn backend_decompress(backend: u32, buf: &[u8], limit: u64) -> Result<Vec<u8>> {
119 match backend {
120 BACKEND_ZLIB => {
121 let mut d = flate2::read::ZlibDecoder::new(buf).take(limit + 1);
125 let mut v = Vec::new();
126 d.read_to_end(&mut v)
127 .map_err(|_| Error::Decompress("corrupt zlib stream"))?;
128 if v.len() as u64 > limit {
129 return Err(Error::fmt_msg("bomb"));
130 }
131 if d.get_ref().total_in() as usize != buf.len() {
132 return Err(Error::fmt_msg("trailing bytes after zlib stream"));
133 }
134 Ok(v)
135 }
136 BACKEND_ZSTD19 => {
137 let cursor = std::io::Cursor::new(buf);
141 let mut d = zstd::stream::read::Decoder::new(cursor)
142 .map_err(|_| Error::Decompress("zstd init"))?;
143 d.window_log_max(ZSTD_WINDOW_LOG_MAX)
144 .map_err(|_| Error::Decompress("zstd window param"))?;
145 let mut v = Vec::new();
146 d.single_frame()
147 .take(limit + 1)
148 .read_to_end(&mut v)
149 .map_err(|_| Error::Decompress("corrupt zstd stream"))?;
150 if v.len() as u64 > limit {
151 return Err(Error::fmt_msg("bomb"));
152 }
153 Ok(v)
160 }
161 other => Err(Error::fmt_msg(format!(
162 "backend {other} not a decompressable coder"
163 ))),
164 }
165}
166
167pub fn encode_mnf8(
181 pixels: &[u8],
182 dims: Dims,
183 use_geom: bool,
184 profile: Profile,
185) -> (Vec<u8>, MnF6Stats, u32) {
186 let candidates: Vec<(u32, Coder)> = match profile {
187 Profile::Balanced => vec![(BACKEND_ZSTD19, Coder::Zstd(19))],
188 Profile::Maximum => backend_candidates(),
189 };
190 let mut best: Option<(Vec<u8>, u32, MnF6Stats, u32)> = None;
191 let pred_arms = [
196 PredKind::None,
197 PredKind::Med,
198 PredKind::Plane,
199 PredKind::Left,
200 ];
201 for (backend, coder) in candidates {
202 for kind in pred_arms {
203 for planar in [false, true] {
204 let med = kind == PredKind::Med;
205 let flags = kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 };
206 let (file, st) = encode_stream_coder(
207 pixels, dims, true, use_geom, flags, planar, med, kind, &coder, backend,
208 )
209 .expect("encoding an in-memory image cannot fail");
210 let size = file.len();
211 if best.as_ref().is_none_or(|b| size < b.0.len()) {
212 best = Some((file, flags, st, backend));
213 }
214 }
215 }
216 let flags = FLAG_PRED_MED | FLAG_TOKENS;
220 for cap in [16, tokens::MAX_CANDIDATES] {
221 let (file, st) = encode_tokens_coder_cap(pixels, dims, backend, flags, cap)
222 .expect("encoding an in-memory image cannot fail");
223 let size = file.len();
224 if best.as_ref().is_none_or(|b| size < b.0.len()) {
225 best = Some((file, flags, st, backend));
226 }
227 }
228 }
229 let (file, flags, mut st, backend) = best.expect("at least one candidate");
230 st.chosen_flags = flags;
231 (file, st, backend)
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum PredKind {
238 None,
240 Med,
242 Plane,
244 Left,
246}
247
248impl PredKind {
249 fn from_flags(flags: u32) -> PredKind {
250 if flags & FLAG_PRED_MED != 0 {
251 PredKind::Med
252 } else if flags & FLAG_PRED_PLANE != 0 {
253 PredKind::Plane
254 } else if flags & FLAG_PRED_LEFT != 0 {
255 PredKind::Left
256 } else {
257 PredKind::None
258 }
259 }
260
261 fn flag_bit(self) -> u32 {
262 match self {
263 PredKind::None => 0,
264 PredKind::Med => FLAG_PRED_MED,
265 PredKind::Plane => FLAG_PRED_PLANE,
266 PredKind::Left => FLAG_PRED_LEFT,
267 }
268 }
269
270 fn predict(self, a: u32, b: u32, cc: u32) -> u32 {
273 match self {
274 PredKind::None | PredKind::Med => crate::mnf6::med_predict_pixel(a, b, cc),
275 PredKind::Plane => {
276 (a as i64 + b as i64 - cc as i64).clamp(0, 255) as u32
279 }
280 PredKind::Left => a,
281 }
282 }
283}
284
285fn image_predict(pixels: &[u8], w: usize, h: usize, kind: PredKind) -> Vec<u8> {
292 let mut out = vec![0u8; pixels.len()];
293 for y in 0..h {
294 for x in 0..w {
295 for c in 0..3usize {
296 let i = (y * w + x) * 3 + c;
297 let pred: u32 = if x == 0 && y == 0 {
298 127
299 } else if y == 0 {
300 u32::from(pixels[i - 3])
301 } else if x == 0 {
302 u32::from(pixels[i - w * 3])
303 } else {
304 let a = u32::from(pixels[i - 3]);
305 let b = u32::from(pixels[i - w * 3]);
306 let cc = u32::from(pixels[i - w * 3 - 3]);
307 kind.predict(a, b, cc)
308 };
309 out[i] = pred as u8;
310 }
311 }
312 }
313 out
314}
315
316fn encode_tokens_coder_cap(
322 pixels: &[u8],
323 dims: Dims,
324 backend: u32,
325 flags: u32,
326 cap: usize,
327) -> Result<(Vec<u8>, MnF6Stats)> {
328 let med_pred = med_predict(pixels, dims.width as usize, dims.height as usize);
329 let res: Vec<u8> = pixels
330 .iter()
331 .zip(&med_pred)
332 .map(|(x, m)| x.wrapping_sub(*m))
333 .collect();
334 let (wire, ts) =
335 tokens::encode_plane_cap(&res, dims.width as usize, dims.height as usize, cap)?;
336 let payload = backend_compress(backend, &wire)?;
337 let payload_len =
338 u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?;
339 let mut file = Vec::with_capacity(HEADER8 + payload.len());
340 file.extend_from_slice(b"MNF8");
341 file.extend_from_slice(&dims.width.to_le_bytes());
342 file.extend_from_slice(&dims.height.to_le_bytes());
343 file.extend_from_slice(&TILE_SIZE.to_le_bytes());
344 let cols = div_ceil(dims.width, TILE_SIZE);
345 let rows = div_ceil(dims.height, TILE_SIZE);
346 let tile_count: u32 = cols
347 .checked_mul(rows)
348 .ok_or_else(|| Error::fmt_msg("too many tiles"))?;
349 file.extend_from_slice(&tile_count.to_le_bytes());
350 file.extend_from_slice(&payload_len.to_le_bytes());
351 file.extend_from_slice(&flags.to_le_bytes());
352 file.extend_from_slice(&backend.to_le_bytes());
353 file.extend_from_slice(&payload);
354 let st = MnF6Stats {
355 literal: u32::try_from(ts.literals).unwrap_or(u32::MAX),
356 ref_exact: u32::try_from(ts.matches).unwrap_or(u32::MAX),
357 ..MnF6Stats::default()
358 };
359 Ok((file, st))
360}
361
362#[allow(clippy::too_many_arguments)]
373fn encode_stream_coder(
374 pixels: &[u8],
375 dims: Dims,
376 use_atlas: bool,
377 use_geom: bool,
378 flags: u32,
379 planar: bool,
380 med: bool,
381 kind: PredKind,
382 coder: &Coder,
383 backend: u32,
384) -> Result<(Vec<u8>, MnF6Stats)> {
385 let pure_pred = kind == PredKind::Plane || kind == PredKind::Left;
388 let med_pred = match kind {
389 PredKind::Med => med_predict(pixels, dims.width as usize, dims.height as usize),
390 PredKind::Plane => image_predict(
391 pixels,
392 dims.width as usize,
393 dims.height as usize,
394 PredKind::Plane,
395 ),
396 PredKind::Left => image_predict(
397 pixels,
398 dims.width as usize,
399 dims.height as usize,
400 PredKind::Left,
401 ),
402 PredKind::None => Vec::new(),
403 };
404 let tiles = split_tiles(pixels, dims);
405 let mut raw: Vec<u8> = Vec::with_capacity(tiles.len() * 774);
406 let mut stats = MnF6Stats::default();
407 let mut atlas = Atlas::new();
408
409 for (idx, tile) in tiles.iter().enumerate() {
410 let n = tile.nbytes();
411 let pack = |rgb: &[u8]| -> Vec<u8> {
412 if planar {
413 planar_pack(rgb)
414 } else {
415 rgb.to_vec()
416 }
417 };
418
419 let lit_src: Vec<u8> = if med || pure_pred {
422 let (tw, th) = (tile.width as usize, tile.height as usize);
423 let cols_px = dims.width as usize; let tx = (idx % ((dims.width as usize).div_ceil(16))) * 16;
425 let ty = (idx / ((dims.width as usize).div_ceil(16))) * 16;
426 let mut src = Vec::with_capacity(n);
427 for y in 0..th {
428 let row_off = ((ty + y) * cols_px + tx) * 3;
429 src.extend_from_slice(&med_pred[row_off..row_off + tw * 3]);
430 }
431 tile.data
432 .iter()
433 .zip(&src)
434 .map(|(x, m)| x.wrapping_sub(*m))
435 .collect()
436 } else {
437 tile.data.clone()
438 };
439
440 let lit_body = pack(&lit_src);
441 let mut choice_body = lit_body.clone();
442 let mut choice_mode = 0u8;
443 let mut choice_ref: Option<(u32, u8)> = None;
444 let mut best_cost = 1 + proxy_cost(coder, &lit_body);
445
446 if !pure_pred {
449 let (cy, cco, ccg) = ycocg_forward(&tile.data);
450 let mut m3 = Vec::with_capacity(n + 160);
451 m3.extend_from_slice(&cy);
452 m3.extend_from_slice(&pack9(&cco));
453 m3.extend_from_slice(&pack9(&ccg));
454 let m3_cost = 1 + proxy_cost(coder, &m3);
455 if m3_cost < best_cost {
456 best_cost = m3_cost;
457 choice_body = m3;
458 choice_mode = 3;
459 }
460 }
461
462 let tile_t = modcolor_forward_iter(&tile.data);
465
466 if use_atlas && !pure_pred && tiles.len() > 1 {
469 let sig = tile_sig(tile.width, tile.height, &tile.data);
470 for prior in atlas.rank_by_sig(&sig, tile.width, tile.height, RANK_CANDIDATES) {
471 let (_, _, ref_rgb) = &atlas.patches[prior as usize];
472 let transforms: &[u8] = if !use_geom {
477 &[0]
478 } else if tile.width == tile.height {
479 &[0, 1, 2, 3, 4, 5, 6, 7]
480 } else {
481 &[0, 1, 2, 3]
482 };
483 for &t in transforms {
484 let base = if t == 0 {
485 ref_rgb.clone()
486 } else {
487 apply_transform(t, tile.width, tile.height, ref_rgb)
488 };
489 let mut r = vec![0u8; n];
490 for i in 0..n {
491 r[i] = tile.data[i].wrapping_sub(base[i]);
492 }
493 let body = pack(&r);
494 let meta: u64 = if t == 0 { 5 } else { 6 };
496 let cost = meta + proxy_cost(coder, &body);
497 if cost < best_cost {
498 best_cost = cost;
499 choice_body = body;
500 choice_mode = if t == 0 { 1 } else { 2 };
501 choice_ref = Some((prior, t));
502 }
503 let base_t = modcolor_forward_iter(&base);
510 let mut r_c = vec![0u8; n];
511 for i in 0..n {
512 r_c[i] = tile_t[i].wrapping_sub(base_t[i]);
513 }
514 let body_c = pack(&r_c);
515 let cost_c = meta + proxy_cost(coder, &body_c);
516 if cost_c < best_cost {
517 best_cost = cost_c;
518 choice_body = body_c;
519 choice_mode = if t == 0 { 4 } else { 5 };
520 choice_ref = Some((prior, t));
521 }
522 }
523 }
524 }
525
526 stats.modes.push(choice_mode);
527 stats.refs.push(choice_ref);
528 match choice_mode {
529 0 => stats.literal += 1,
530 1 => stats.ref_exact += 1,
531 2 => {
532 stats.ref_geom += 1;
533 if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
534 stats.geom_nontrivial += 1;
535 }
536 }
537 3 => stats.ycocg_literal += 1,
538 4 => stats.ref_exact_color += 1,
539 5 => {
540 stats.ref_geom_color += 1;
541 if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
542 stats.geom_nontrivial += 1;
543 }
544 }
545 _ => unreachable!("mode byte constrained above"),
546 }
547 raw.push(choice_mode);
548 if let Some((p, t)) = choice_ref {
549 raw.extend_from_slice(&p.to_le_bytes());
550 if t != 0 {
551 raw.push(t);
552 }
553 }
554 raw.extend_from_slice(&choice_body);
555 atlas.insert(tile);
556 }
557
558 let payload = backend_compress(backend, &raw)?;
559 let payload_len =
560 u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?; let mut file = Vec::with_capacity(HEADER8 + payload.len());
562 file.extend_from_slice(b"MNF8");
563 file.extend_from_slice(&dims.width.to_le_bytes());
564 file.extend_from_slice(&dims.height.to_le_bytes());
565 file.extend_from_slice(&TILE_SIZE.to_le_bytes());
566 let tile_count = u32::try_from(tiles.len()).map_err(|_| Error::fmt_msg("too many tiles"))?;
567 file.extend_from_slice(&tile_count.to_le_bytes());
568 file.extend_from_slice(&payload_len.to_le_bytes());
569 file.extend_from_slice(&flags.to_le_bytes());
570 file.extend_from_slice(&backend.to_le_bytes());
571 file.extend_from_slice(&payload);
572 Ok((file, stats))
573}
574
575pub fn decode_mnf8(buf: &[u8]) -> Result<(Dims, Vec<u8>, u32)> {
583 decode_mnf8_with_limits(buf, &crate::mnf6::DecodeLimits::default())
584}
585
586pub fn decode_mnf8_with_limits(
589 buf: &[u8],
590 lim: &crate::mnf6::DecodeLimits,
591) -> Result<(Dims, Vec<u8>, u32)> {
592 if buf.len() < HEADER8 {
593 return Err(Error::fmt_msg("short"));
594 }
595 if buf[0..4] != *b"MNF8" {
596 return Err(Error::fmt_msg("magic"));
597 }
598 let u32le = |o: usize| u32::from_le_bytes(buf[o..o + 4].try_into().unwrap());
599 let width = u32le(4);
600 let height = u32le(8);
601 if width == 0 || height == 0 {
602 return Err(Error::fmt_msg("dims"));
603 }
604 if width as u64 * height as u64 > lim.max_pixels {
605 return Err(Error::Limits {
606 which: "max_pixels",
607 limit: lim.max_pixels,
608 actual: width as u64 * height as u64,
609 });
610 }
611 if u32le(12) != TILE_SIZE {
612 return Err(Error::fmt_msg("tilesize"));
613 }
614 let cols = div_ceil(width, TILE_SIZE);
615 let rows = div_ceil(height, TILE_SIZE);
616 let tile_count = u32le(16);
617 if tile_count != cols * rows {
618 return Err(Error::fmt_msg("tilecount"));
619 }
620 if tile_count > lim.max_tiles {
621 return Err(Error::Limits {
622 which: "max_tiles",
623 limit: u64::from(lim.max_tiles),
624 actual: u64::from(tile_count),
625 });
626 }
627 let payload_len = u32le(20);
628 if payload_len > (1 << 31) - 1 {
629 return Err(Error::fmt_msg("payloadlen"));
630 }
631 if buf.len() != HEADER8 + payload_len as usize {
632 return Err(Error::fmt_msg("filelen"));
633 }
634 let flags = u32le(24);
635 if flags & FLAGS_RESERVED8 != 0 {
638 return Err(Error::fmt_msg("reserved-flags"));
639 }
640 let token_wire = flags & FLAG_TOKENS != 0;
641 if token_wire && flags & FLAG_PLANAR != 0 {
644 return Err(Error::fmt_msg("tokens+planar are exclusive"));
645 }
646 let pred_count = u32::from(flags & FLAG_PRED_MED != 0)
648 + u32::from(flags & FLAG_PRED_PLANE != 0)
649 + u32::from(flags & FLAG_PRED_LEFT != 0);
650 if pred_count > 1 {
651 return Err(Error::fmt_msg("multiple predictors selected"));
652 }
653 if token_wire && flags & (FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0 {
656 return Err(Error::fmt_msg("tokens+plane/left predictor exclusive"));
657 }
658 let backend = u32le(28);
659 if !matches!(
660 backend,
661 BACKEND_ZLIB | BACKEND_ZSTD19 | BACKEND_RESERVED2 | BACKEND_RESERVED3
662 ) {
663 return Err(Error::fmt_msg(format!("unknown backend {backend}")));
664 }
665 if matches!(backend, BACKEND_RESERVED2 | BACKEND_RESERVED3) {
669 return Err(Error::fmt_msg(format!(
670 "reserved backend {backend} (not shipped)"
671 )));
672 }
673 let planar = flags & FLAG_PLANAR != 0;
674
675 let px_bytes_total: u64 = width as u64 * height as u64 * 3;
690 let limit = if token_wire {
691 (4 * width as u64)
692 .checked_mul(height as u64)
693 .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
694 } else {
695 let mode3_max: u64 = 1
696 + (TILE_SIZE * TILE_SIZE) as u64
697 + 2 * ((TILE_SIZE * TILE_SIZE) as u64 * 9 + 7) / 8
698 + 2;
699 px_bytes_total
700 .checked_add(6 * tile_count as u64)
701 .and_then(|l| l.checked_add(2 * mode3_max + 2))
702 .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
703 };
704
705 let payload = &buf[HEADER8..];
706 let raw = match backend {
707 BACKEND_ZLIB | BACKEND_ZSTD19 => backend_decompress(backend, payload, limit)?,
708 other => return Err(Error::fmt_msg(format!("unsupported backend {other}"))),
709 };
710
711 let pixels = if token_wire {
712 let res = tokens::decode_plane(&raw, width as usize, height as usize)?;
716 pred_residual_invert(&res, width as usize, height as usize, PredKind::Med)
717 } else {
718 let kind = PredKind::from_flags(flags);
719 match kind {
723 PredKind::None => decode_record_walk(&raw, width, height, tile_count, planar, false)?,
724 PredKind::Med => decode_record_walk(&raw, width, height, tile_count, planar, true)?,
725 other => decode_pred_walk(&raw, width, height, tile_count, planar, other)?,
726 }
727 };
728 Ok((Dims { width, height }, pixels, backend))
729}
730
731fn pred_residual_invert(res: &[u8], w: usize, h: usize, kind: PredKind) -> Vec<u8> {
736 let mut out = vec![0u8; w * h * 3];
737 for y in 0..h {
738 for x in 0..w {
739 for c in 0..3usize {
740 let i = (y * w + x) * 3 + c;
741 let pred: u32 = if x == 0 && y == 0 {
742 127
743 } else if y == 0 {
744 u32::from(out[i - 3])
745 } else if x == 0 {
746 u32::from(out[i - w * 3])
747 } else {
748 let a = u32::from(out[i - 3]);
749 let b = u32::from(out[i - w * 3]);
750 let cc = u32::from(out[i - w * 3 - 3]);
751 kind.predict(a, b, cc)
752 };
753 out[i] = (pred + u32::from(res[i])).wrapping_rem_euclid(256) as u8;
754 }
755 }
756 }
757 out
758}
759
760fn decode_pred_walk(
767 raw: &[u8],
768 width: u32,
769 height: u32,
770 tile_count: u32,
771 planar: bool,
772 kind: PredKind,
773) -> Result<Vec<u8>> {
774 let cols = div_ceil(width, TILE_SIZE);
783 let mut pixels = vec![0u8; width as usize * height as usize * 3];
784 let mut decoded: Vec<(u32, u32, Vec<u8>)> = Vec::with_capacity(tile_count as usize);
785 let mut ip = 0usize;
786 for i in 0..tile_count {
787 let row = i / cols;
788 let col = i % cols;
789 let tw = TILE_SIZE.min(width - col * TILE_SIZE);
790 let th = TILE_SIZE.min(height - row * TILE_SIZE);
791 let n = (tw * th * 3) as usize;
792 if ip >= raw.len() {
793 return Err(Error::fmt_msg(format!("eof at tile {i}")));
794 }
795 let mode = raw[ip];
796 ip += 1;
797 let tile = match mode {
798 0 => {
799 if ip + n > raw.len() {
800 return Err(Error::fmt_msg("body"));
801 }
802 let body = raw[ip..ip + n].to_vec();
803 ip += n;
804 let body = if planar {
805 crate::mnf6::planar_unpack(&body)
806 } else {
807 body
808 };
809 let mut recon = vec![0u8; n];
812 let gy0 = row as usize * TILE_SIZE as usize;
813 let gx0 = col as usize * TILE_SIZE as usize;
814 for y in 0..th as usize {
815 for x in 0..tw as usize {
816 for c in 0..3usize {
817 let k = (y * tw as usize + x) * 3 + c;
818 let gy = gy0 + y;
819 let gx = gx0 + x;
820 let tl = |ggy: usize, ggx: usize| -> u32 {
821 if ggy >= gy0 && ggx >= gx0 {
822 u32::from(
823 recon[((ggy - gy0) * tw as usize + (ggx - gx0)) * 3 + c],
824 )
825 } else {
826 u32::from(pixels[(ggy * width as usize + ggx) * 3 + c])
827 }
828 };
829 let pred: u32 = if gy == 0 && gx == 0 {
830 127
831 } else if gy == 0 {
832 if gx - 1 < gx0 {
833 u32::from(pixels[(gy * width as usize + gx - 1) * 3 + c])
834 } else {
835 u32::from(recon[(y * tw as usize + x - 1) * 3 + c])
836 }
837 } else if gx == 0 {
838 if gy - 1 < gy0 {
839 u32::from(pixels[((gy - 1) * width as usize + gx) * 3 + c])
840 } else {
841 u32::from(recon[((y - 1) * tw as usize + x) * 3 + c])
842 }
843 } else {
844 let a = tl(gy, gx - 1);
845 let b = tl(gy - 1, gx);
846 let cc = tl(gy - 1, gx - 1);
847 kind.predict(a, b, cc)
848 };
849 recon[k] = (pred + u32::from(body[k])).wrapping_rem_euclid(256) as u8;
850 }
851 }
852 }
853 recon
854 }
855 _ => {
856 return Err(Error::fmt_msg(format!(
861 "tile {i}: mode {mode} under PRED_PLANE/LEFT — not yet admitted"
862 )));
863 }
864 };
865 decoded.push((tw, th, tile.clone()));
866 for y_ in 0..th as usize {
867 let py = row as usize * TILE_SIZE as usize + y_;
868 for x_ in 0..tw as usize {
869 let pxx = col as usize * TILE_SIZE as usize + x_;
870 let d = (py * width as usize + pxx) * 3;
871 let s = (y_ * tw as usize + x_) * 3;
872 pixels[d..d + 3].copy_from_slice(&tile[s..s + 3]);
873 }
874 }
875 }
876 if ip != raw.len() {
877 return Err(Error::fmt_msg("extra"));
878 }
879 Ok(pixels)
880}
881
882#[cfg(test)]
883mod gate30 {
884 use super::*;
885 use crate::mnf6::Coder;
886 use std::path::Path;
887 fn load(name: &str) -> (Dims, Vec<u8>) {
888 let p = format!("{}/testdata/{}", env!("CARGO_MANIFEST_DIR"), name);
889 let img = image::open(Path::new(&p))
890 .unwrap_or_else(|e| panic!("{name}: {e}"))
891 .to_rgb8();
892 (
893 Dims {
894 width: img.width(),
895 height: img.height(),
896 },
897 img.into_raw(),
898 )
899 }
900 #[test]
903 #[ignore]
904 fn gate_all_arms_real_wire() {
905 let files = [
906 "gradient_320x240.png",
907 "noise_256x256.png",
908 "repeat_256x256.png",
909 "screenshot_640x400.png",
910 "solid_256x256.png",
911 "heldout/blob_224x160.png",
912 "heldout/blocks_192x128.png",
913 "heldout/document_200x280.png",
914 "heldout/plaid_192.png",
915 "heldout/poster_208x144.png",
916 "heldout-photos/autumn_160x120_s3100006.png",
917 "heldout-photos/dusk_160x120_s3100003.png",
918 "heldout-photos/dusk_224x160_s3100002.png",
919 "heldout-photos/forest_192x144_s3100001.png",
920 "heldout-photos/forest_224x160_s3100005.png",
921 "heldout-photos/forest_224x160_s3100008.png",
922 "heldout-photos/ocean_192x144_s3100007.png",
923 "heldout-photos/sand_192x144_s3100004.png",
924 "heldout-photos/slate_160x120_s3100009.png",
925 "heldout-photos/terrain_160x120_s3100000.png",
926 "photos/autumn_192x144_s900001.png",
927 "photos/dusk_160x120_s900003.png",
928 "photos/forest_224x160_s900002.png",
929 "photos/ocean_192x144_s900007.png",
930 "photos/sand_160x120_s900000.png",
931 "photos/slate_192x144_s900004.png",
932 "photos/slate_224x160_s900008.png",
933 "photos/stone_160x120_s900006.png",
934 "photos/terrain_160x120_s900009.png",
935 "photos/terrain_224x160_s900005.png",
936 ];
937 let mut old_tot = 0usize;
938 let mut new_tot = 0usize;
939 let mut fires = 0usize;
940 for name in files {
941 let (d, px) = load(name);
942 let mut old = usize::MAX;
944 for kind in [PredKind::None, PredKind::Med] {
945 for (backend, coder) in [
946 (BACKEND_ZSTD19, Coder::Zstd(19)),
947 (BACKEND_ZLIB, Coder::Zlib),
948 ] {
949 for planar in [false, true] {
950 let med = kind == PredKind::Med;
951 let (file, _) = encode_stream_coder(
952 &px,
953 d,
954 true,
955 true,
956 kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 },
957 planar,
958 med,
959 kind,
960 &coder,
961 backend,
962 )
963 .unwrap();
964 let _ = &file;
966 old = old.min(file.len());
967 }
968 }
969 }
970 for (backend, _coder) in [
971 (BACKEND_ZSTD19, Coder::Zstd(19)),
972 (BACKEND_ZLIB, Coder::Zlib),
973 ] {
974 let (tw, _) =
975 encode_tokens_coder_cap(&px, d, backend, FLAG_PRED_MED | FLAG_TOKENS, 16)
976 .unwrap();
977 old = old.min(tw.len());
978 }
979 let mut new_ = old;
981 let mut new_win = String::new();
982 for kind in [PredKind::Plane, PredKind::Left] {
983 for (backend, coder) in [
984 (BACKEND_ZSTD19, Coder::Zstd(19)),
985 (BACKEND_ZLIB, Coder::Zlib),
986 ] {
987 let (file, _) = encode_stream_coder(
988 &px,
989 d,
990 true,
991 true,
992 kind.flag_bit(),
993 false,
994 false,
995 kind,
996 &coder,
997 backend,
998 )
999 .unwrap();
1000 let (dd, out, _) =
1002 decode_mnf8(&file).unwrap_or_else(|e| panic!("{name} {kind:?}: {e}"));
1003 if out != px {
1004 let pos = out
1005 .iter()
1006 .zip(&px)
1007 .position(|(a, b)| a != b)
1008 .expect("differs");
1009 panic!(
1010 "{name} {kind:?} NOT exact: first diff byte {pos} (pixel y={}, x={}, c={}): dec={} enc={}",
1011 pos / 3 / d.width as usize, pos / 3 % d.width as usize, pos % 3,
1012 out[pos], px[pos]
1013 );
1014 }
1015 let _ = dd;
1016 if file.len() < new_ {
1017 new_ = file.len();
1018 new_win = format!("{kind:?}-{backend}");
1019 }
1020 }
1021 }
1022 if new_ < old {
1023 fires += 1;
1024 }
1025 old_tot += old;
1026 new_tot += new_;
1027 println!(
1028 "{name:44} old={old:>8} new={new_:>8} {:+6} {}",
1029 new_ as i64 - old as i64,
1030 new_win
1031 );
1032 }
1033 println!(
1034 "TOTAL old={old_tot} new={new_tot} delta={:+} fires={fires}",
1035 new_tot as i64 - old_tot as i64
1036 );
1037 assert!(new_tot < old_tot, "aggregate must strictly improve");
1038 }
1039
1040 #[test]
1041 fn branch_arms_roundtrip_and_pick_fires() {
1042 let path = format!(
1046 "{}/testdata/heldout/poster_208x144.png",
1047 env!("CARGO_MANIFEST_DIR")
1048 );
1049 let img = image::open(std::path::Path::new(&path)).unwrap().to_rgb8();
1050 let d = Dims {
1051 width: img.width(),
1052 height: img.height(),
1053 };
1054 let px = img.into_raw();
1055 let (buf, st, _) = encode_mnf8(&px, d, true, Profile::Balanced);
1056 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1057 assert_eq!(out, px, "picked arm decodes exactly");
1058 assert!(
1059 st.chosen_flags & (FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1060 "poster must pick a branch arm (picked {:#x})",
1061 st.chosen_flags
1062 );
1063 let (bufold, _, _) = {
1065 let mut best: Option<(Vec<u8>, u32)> = None;
1067 for (backend, coder) in [(BACKEND_ZSTD19, Coder::Zstd(19))] {
1068 for kind in [PredKind::None, PredKind::Med] {
1069 for planar in [false, true] {
1070 let (f, _) = encode_stream_coder(
1071 &px,
1072 d,
1073 true,
1074 true,
1075 kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 },
1076 planar,
1077 kind == PredKind::Med,
1078 kind,
1079 &coder,
1080 backend,
1081 )
1082 .unwrap();
1083 if best.as_ref().is_none_or(|b| f.len() < b.0.len()) {
1084 best = Some((f, kind.flag_bit()));
1085 }
1086 }
1087 }
1088 let (t, _) =
1089 encode_tokens_coder_cap(&px, d, backend, FLAG_PRED_MED | FLAG_TOKENS, 16)
1090 .unwrap();
1091 if best.as_ref().is_none_or(|b| t.len() < b.0.len()) {
1092 best = Some((t, FLAG_TOKENS));
1093 }
1094 }
1095 let (f, fl) = best.unwrap();
1096 (f, fl, 0u32)
1097 };
1098 assert!(
1099 buf.len() <= bufold.len(),
1100 "new pick ({}) must not exceed the old pick ({})",
1101 buf.len(),
1102 bufold.len()
1103 );
1104 }
1105
1106 #[test]
1107 fn rejects_multiple_predictors() {
1108 let mut f = vec![0u8; HEADER8];
1110 f[0..4].copy_from_slice(b"MNF8");
1111 f[4..8].copy_from_slice(&16u32.to_le_bytes());
1112 f[8..12].copy_from_slice(&16u32.to_le_bytes());
1113 f[12..16].copy_from_slice(&16u32.to_le_bytes());
1114 f[16..20].copy_from_slice(&1u32.to_le_bytes());
1115 f[20..24].copy_from_slice(&0u32.to_le_bytes());
1116 f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_PRED_PLANE).to_le_bytes());
1117 f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
1118 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1119 assert!(
1120 r.is_ok() && r.unwrap().is_err(),
1121 "two predictors must reject"
1122 );
1123 f[24..28].copy_from_slice(&(FLAG_TOKENS | FLAG_PRED_LEFT).to_le_bytes());
1124 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1125 assert!(r.is_ok() && r.unwrap().is_err(), "tokens+left must reject");
1126 }
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131 use super::*;
1132
1133 fn roundtrip(px: &[u8], dims: Dims, profile: Profile) -> u32 {
1134 let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
1135 let (d, out, _) = decode_mnf8(&buf).expect("decode");
1136 assert_eq!(d, dims);
1137 assert_eq!(
1138 out, px,
1139 "profile {profile:?} backend {backend} flags {st:?}"
1140 );
1141 backend
1142 }
1143
1144 #[test]
1145 fn mnf8_balanced_roundtrip_all_classes() {
1146 for (w, h, seed) in [(16u32, 16u32, 1), (48, 33, 2), (31, 17, 4), (1, 1, 5)] {
1147 let mut s: u64 = (seed as u64).wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEF;
1148 let mut rnd = move || {
1149 s ^= s << 13;
1150 s ^= s >> 7;
1151 s ^= s << 17;
1152 s
1153 };
1154 let px: Vec<u8> = (0..w as usize * h as usize * 3)
1155 .map(|_| (rnd() & 0xFF) as u8)
1156 .collect();
1157 roundtrip(
1158 &px,
1159 Dims {
1160 width: w,
1161 height: h,
1162 },
1163 Profile::Balanced,
1164 );
1165 }
1166 }
1167
1168 #[test]
1169 fn mnf8_maximum_never_loses_to_mnf7() {
1170 for name in [
1173 "screenshot_640x400.png",
1174 "solid_256x256.png",
1175 "gradient_320x240.png",
1176 ] {
1177 let path = format!("{}/testdata/{name}", env!("CARGO_MANIFEST_DIR"));
1178 let img = image::open(&path)
1179 .unwrap_or_else(|e| panic!("{name}: {e}"))
1180 .to_rgb8();
1181 let d = Dims {
1182 width: img.width(),
1183 height: img.height(),
1184 };
1185 let px = img.into_raw();
1186 let (buf8, _, backend) = encode_mnf8(&px, d, true, Profile::Maximum);
1187 let (buf7, _) = crate::mnf6::encode_mnf7(&px, d, true);
1188 assert!(
1191 buf8.len() <= buf7.len() + 4,
1192 "{name}: MNF8({backend}) {} > MNF7(+4) {}",
1193 buf8.len(),
1194 buf7.len()
1195 );
1196 let (_, out, _) = decode_mnf8(&buf8).expect("decode");
1197 assert_eq!(out, px, "{name}: exact");
1198 }
1199 }
1200
1201 #[test]
1202 fn mnf8_rejects() {
1203 let mut f = vec![0u8; 32];
1204 f[0..4].copy_from_slice(b"MNF8");
1205 f[4..8].copy_from_slice(&16u32.to_le_bytes());
1206 f[8..12].copy_from_slice(&16u32.to_le_bytes());
1207 f[12..16].copy_from_slice(&16u32.to_le_bytes());
1208 f[16..20].copy_from_slice(&1u32.to_le_bytes());
1209 f[28..32].copy_from_slice(&7u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "unknown backend rejected");
1212 f[28..32].copy_from_slice(&3u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "reserved backend rejected");
1214 f[28..32].copy_from_slice(&2u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "reserved brotli backend rejected");
1216 f[28..32].copy_from_slice(&2u32.to_le_bytes());
1218 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1219 assert!(r.is_ok() && r.unwrap().is_err(), "must error, not panic");
1220 f[28..32].copy_from_slice(&0u32.to_le_bytes());
1221 f[24] = 0b100; assert!(decode_mnf8(&f).is_err(), "reserved flag rejected");
1223 f[0] = b'X';
1225 f[24] = 0;
1226 assert!(decode_mnf8(&f).is_err(), "magic");
1227 }
1228
1229 #[test]
1230 fn mnf8_balanced_picks_zstd_not_hardcoded() {
1231 let (w, h) = (64u32, 64u32);
1235 let mut px = vec![0u8; (w * h * 3) as usize];
1236 for y in 0..h {
1237 for x in 0..w {
1238 let i = ((y * w + x) * 3) as usize;
1239 px[i] = (x * 4) as u8;
1240 px[i + 1] = (y * 4) as u8;
1241 px[i + 2] = 128;
1242 }
1243 }
1244 let (buf, st, backend) = encode_mnf8(
1245 &px,
1246 Dims {
1247 width: w,
1248 height: h,
1249 },
1250 false,
1251 Profile::Balanced,
1252 );
1253 assert_eq!(backend, BACKEND_ZSTD19);
1254 assert!(
1261 st.chosen_flags & !0b1111 == 0,
1262 "balanced wrote a legal flags id, got {:#x}",
1263 st.chosen_flags
1264 );
1265 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1266 assert_eq!(out, px);
1267 }
1268
1269 #[test]
1270 fn mnf8_no_geom_no_flips() {
1271 let (w, h) = (32u32, 32u32); let mut px = vec![0u8; (w * h * 3) as usize];
1276 for y in 0..h {
1277 for x in 0..w {
1278 let i = ((y * w + x) * 3) as usize;
1279 px[i] = (x * 7 % 256) as u8;
1280 px[i + 1] = (y * 3 % 256) as u8;
1281 px[i + 2] = 5;
1282 }
1283 }
1284 let tile_row = 16usize;
1286 let tile_col = 0usize;
1287 let src_off = (tile_row * w as usize + tile_col) * 3;
1288 let tile = px[src_off..src_off + 16 * 16 * 3].to_vec();
1289 let flipped = apply_transform(1, 16, 16, &tile);
1290 px[src_off..src_off + 16 * 16 * 3].copy_from_slice(&flipped);
1291 let stacked = px.clone();
1292 let d = Dims {
1293 width: w,
1294 height: h,
1295 };
1296 let (buf, st, _) = encode_mnf8(&stacked, d, false, Profile::Balanced);
1297 assert_eq!(st.ref_geom, 0, "no flips allowed with use_geom=false");
1299 assert_eq!(st.geom_nontrivial, 0);
1300 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1301 assert_eq!(out, stacked);
1302 }
1303
1304 #[test]
1305 fn mnf8_no_atlas_arm() {
1306 let d = Dims {
1309 width: 32,
1310 height: 32,
1311 };
1312 let px: Vec<u8> = (0..d.width as usize * d.height as usize * 3)
1313 .map(|i| (i * 7 % 256) as u8)
1314 .collect();
1315 let (buf, st, _) = encode_mnf8_noclimb(&px, d);
1316 assert_eq!(
1317 st.ref_exact + st.ref_geom + st.ref_exact_color + st.ref_geom_color,
1318 0,
1319 "no-atlas arm must emit zero references"
1320 );
1321 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1322 assert_eq!(out, px);
1323 }
1324
1325 fn encode_mnf8_noclimb(px: &[u8], d: Dims) -> (Vec<u8>, MnF6Stats, u32) {
1327 let mut best: Option<(Vec<u8>, MnF6Stats, u32)> = None;
1329 for flags in [0u32, FLAG_PLANAR] {
1330 let planar = flags & FLAG_PLANAR != 0;
1331 let (file, st) = encode_stream_coder(
1332 px,
1333 d,
1334 false,
1335 false,
1336 flags,
1337 planar,
1338 false,
1339 PredKind::None,
1340 &Coder::Zstd(19),
1341 BACKEND_ZSTD19,
1342 )
1343 .expect("encode");
1344 if best.as_ref().is_none_or(|b| file.len() < b.0.len()) {
1345 best = Some((file, st, BACKEND_ZSTD19));
1346 }
1347 }
1348 let (f, s, b) = best.expect("variant");
1349 (f, s, b)
1350 }
1351
1352 #[test]
1353 fn mnf8_med_flag_roundtrip_photo_like() {
1354 let (w, h) = (64u32, 64u32);
1359 let mut px = vec![0u8; (w * h * 3) as usize];
1360 for y in 0..h {
1361 for x in 0..w {
1362 let i = ((y * w + x) * 3) as usize;
1363 px[i] = (x * 4) as u8;
1364 px[i + 1] = (y * 4) as u8;
1365 px[i + 2] = ((x + y) / 2) as u8;
1366 }
1367 }
1368 let d = Dims {
1369 width: w,
1370 height: h,
1371 };
1372 let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1373 assert!(
1378 st.chosen_flags & !0b1111 == 0,
1379 "chosen flags must be a legal MNF8 id, got {:#x}",
1380 st.chosen_flags
1381 );
1382 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1383 if out != px {
1384 let first = out
1385 .iter()
1386 .zip(&px)
1387 .position(|(a, b)| a != b)
1388 .expect("differs");
1389 let (b_y, rem) = (first / 3 / (w as usize), (first / 3) % (w as usize));
1390 panic!(
1391 "not exact: first diff at byte {first} (pixel ({b_y},{rem}), ch {}): out={:?} px={:?}",
1392 first % 3,
1393 &out[first.saturating_sub(4)..first + 4],
1394 &px[first.saturating_sub(4)..first + 4]
1395 );
1396 }
1397 assert_eq!(out, px, "exact reconstruction under the measured flags");
1398 }
1399
1400 #[test]
1401 fn mnf8_med_flag_picked_on_photo_like_smoothing() {
1402 let (w, h) = (64u32, 64u32);
1406 let mut px = vec![0u8; (w * h * 3) as usize];
1407 for y in 0..h {
1408 for x in 0..w {
1409 let i = ((y * w + x) * 3) as usize;
1410 px[i] = (x * 4 + y * 2) as u8;
1412 px[i + 1] = (y * 6) as u8;
1413 px[i + 2] = ((x + y) * 3) as u8;
1414 }
1415 }
1416 let d = Dims {
1417 width: w,
1418 height: h,
1419 };
1420 let (_, st, _) = encode_mnf8(
1421 &px,
1422 d,
1423 false, Profile::Balanced,
1425 );
1426 assert!(
1431 st.chosen_flags & (FLAG_PRED_MED | FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1432 "smooth no-repeat bodies must select a predictor (picked {:#x})",
1433 st.chosen_flags
1434 );
1435 let (_, out, _) =
1436 decode_mnf8(&encode_mnf8(&px, d, false, Profile::Balanced).0).expect("decode");
1437 assert_eq!(out, px, "exact under the measured predictor pick");
1438 }
1439
1440 #[test]
1441 fn mnf8_forced_mode4_mode5_records() {
1442 let mut s: u64 = 42;
1447 let mut rnd = move || {
1448 s ^= s << 13;
1449 s ^= s >> 7;
1450 s ^= s << 17;
1451 s
1452 };
1453 let a: Vec<u8> = (0..16 * 16 * 3).map(|_| (rnd() & 0xFF) as u8).collect();
1454 let ta_t = crate::mnf6::modcolor_forward(&a);
1455 let mut tb_t = ta_t.clone();
1456 let ramp = [0u8, 1, 2, 255];
1457 for i in (1..tb_t.len()).step_by(3) {
1458 tb_t[i] = tb_t[i].wrapping_add(ramp[(i / 3) % 4]);
1459 }
1460 let b = crate::mnf6::modcolor_inverse(&tb_t);
1461 let mut two = a.clone();
1462 two.extend_from_slice(&b);
1463 let d2 = Dims {
1464 width: 16,
1465 height: 32,
1466 };
1467 let (buf, st, _) = encode_mnf8(&two, d2, true, Profile::Balanced);
1468 assert!(
1469 st.ref_exact_color > 0,
1470 "expected mode 4 (or a color-geom below) selected, stats: exact={} geom={} ec={} gc={}",
1471 st.ref_exact,
1472 st.ref_geom,
1473 st.ref_exact_color,
1474 st.ref_geom_color
1475 );
1476 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1477 assert_eq!(out, two, "mode-4 record reconstructs exactly through MNF8");
1478
1479 let flipped = apply_transform(1, 16, 16, &a);
1481 let tb_t2 = crate::mnf6::modcolor_forward(&flipped);
1482 let mut tc_t = tb_t2;
1483 for i in (1..tc_t.len()).step_by(3) {
1484 tc_t[i] = tc_t[i].wrapping_add(ramp[(i / 3) % 4]);
1485 }
1486 let c = crate::mnf6::modcolor_inverse(&tc_t);
1487 let mut three = flipped.clone();
1488 three.extend_from_slice(&c);
1489 let d3 = Dims {
1490 width: 16,
1491 height: 32,
1492 };
1493 let (buf3, st3, _) = encode_mnf8(&three, d3, true, Profile::Balanced);
1494 assert!(
1495 st3.ref_exact_color > 0 || st3.ref_geom_color > 0,
1496 "expected a color-domain reference (exact or geom), stats: ec={} gc={}",
1497 st3.ref_exact_color,
1498 st3.ref_geom_color
1499 );
1500 let (_, out3, _) = decode_mnf8(&buf3).expect("decode");
1501 assert_eq!(out3, three);
1502 }
1503
1504 fn roundtrip_tokens(px: &[u8], dims: Dims, profile: Profile) -> (u32, u32) {
1507 let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
1508 let (d, out, _) = decode_mnf8(&buf).expect("decode");
1509 assert_eq!(d, dims);
1510 assert_eq!(
1511 out, px,
1512 "profile {profile:?} backend {backend} flags {:#x} literal {} matches {}",
1513 st.chosen_flags, st.literal, st.ref_exact
1514 );
1515 (st.literal, st.ref_exact)
1516 }
1517
1518 #[test]
1519 fn token_wire_unit_roundtrip_and_errors() {
1520 use crate::tokens::*;
1521 let (w, h) = (24usize, 16usize);
1523 let mut plane = vec![0u8; w * h * 3];
1524 for i in 0..w * h {
1525 plane[i * 3] = (i % 251) as u8;
1526 plane[i * 3 + 1] = ((i * 7) % 251) as u8;
1527 }
1528 for k in 300..384 {
1529 plane[k * 3..k * 3 + 3].copy_from_slice(&[9, 9, 9]);
1530 }
1531 let (wire, st) = encode_plane(&plane, w, h).expect("encode");
1532 assert!(st.matches >= 1, "planted structures must fire: {st:?}");
1533 let back = decode_plane(&wire, w, h).expect("decode");
1534 assert_eq!(back, plane);
1535 let mut bad = wire.clone();
1537 bad.push(2); assert!(decode_plane(&bad, w, h).is_err());
1539 assert!(decode_plane(&wire[..wire.len() - 1], w, h).is_err());
1540 let mut bad = wire.clone();
1541 bad.push(TAG_LITERAL);
1542 bad.extend_from_slice(&[1, 2, 3]);
1543 assert!(decode_plane(&bad, w, h).is_err()); }
1545
1546 #[test]
1547 fn mnf8_token_flag_picked_on_repeating_residual() {
1548 let (w, h) = (256u32, 256u32);
1553 let mut px = vec![0u8; (w * h * 3) as usize];
1554 for y in 0..h {
1555 for x in 0..w {
1556 let i = ((y * w + x) * 3) as usize;
1557 px[i] = (y * 3) as u8;
1559 px[i + 1] = (y / 4) as u8;
1560 px[i + 2] = 200;
1561 }
1562 }
1563 let d = Dims {
1564 width: w,
1565 height: h,
1566 };
1567 let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1568 assert!(
1573 st.chosen_flags & (FLAG_TOKENS | FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1574 "repeating-residual class must pick a measured-winning arm (picked {:#x})",
1575 st.chosen_flags
1576 );
1577 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1578 assert_eq!(out, px, "wire reconstructs exactly under the pick");
1579 }
1580
1581 #[test]
1582 fn mnf8_token_flag_not_picked_on_noise() {
1583 let (w, h) = (64u32, 64u32);
1587 let mut s: u64 = 0x5EED;
1588 let mut rnd = move || {
1589 s ^= s << 13;
1590 s ^= s >> 7;
1591 s ^= s << 17;
1592 s
1593 };
1594 let px: Vec<u8> = (0..w as usize * h as usize * 3)
1595 .map(|_| (rnd() & 0xFF) as u8)
1596 .collect();
1597 let d = Dims {
1598 width: w,
1599 height: h,
1600 };
1601 let (_, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1602 assert_eq!(
1603 st.chosen_flags & FLAG_TOKENS,
1604 0,
1605 "noise class must NOT pick tokens (picked {:#x})",
1606 st.chosen_flags
1607 );
1608 roundtrip_tokens(&px, d, Profile::Balanced);
1609 }
1610
1611 #[test]
1612 fn mnf8_tokens_decode_rejects_malformed_wire() {
1613 let f = vec![0u8; HEADER8 + 7];
1617 let mut f = f;
1618 f[0..4].copy_from_slice(b"MNF8");
1619 f[4..8].copy_from_slice(&16u32.to_le_bytes());
1620 f[8..12].copy_from_slice(&16u32.to_le_bytes());
1621 f[12..16].copy_from_slice(&16u32.to_le_bytes());
1622 f[16..20].copy_from_slice(&1u32.to_le_bytes());
1623 f[20..24].copy_from_slice(&7u32.to_le_bytes());
1624 f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_TOKENS).to_le_bytes());
1625 f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
1626 f[28..32].copy_from_slice(&BACKEND_ZLIB.to_le_bytes());
1628 let res: Vec<u8> = vec![0u8; 16 * 16 * 3];
1629 let (wire, _) = crate::tokens::encode_plane(&res, 16, 16).expect("wire");
1630 let mut wire_bad = wire.clone();
1632 wire_bad[0] = 7;
1633 let comp: Vec<u8>;
1634 {
1635 let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
1636 std::io::Write::write_all(&mut e, &wire_bad).unwrap();
1637 comp = e.finish().unwrap();
1638 }
1639 let mut file = Vec::with_capacity(HEADER8 + comp.len());
1640 file.extend_from_slice(&f[..HEADER8]);
1641 let pl = comp.len() as u32;
1642 file[20..24].copy_from_slice(&pl.to_le_bytes());
1643 file.extend_from_slice(&comp);
1644 let r = std::panic::catch_unwind(|| decode_mnf8(&file));
1645 assert!(
1646 r.is_ok() && r.unwrap().is_err(),
1647 "corrupt wire: Err not panic"
1648 );
1649 let comp2: Vec<u8>;
1651 {
1652 let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
1653 std::io::Write::write_all(&mut e, &wire).unwrap();
1654 comp2 = e.finish().unwrap();
1655 }
1656 let mut file2 = Vec::with_capacity(HEADER8 + comp2.len());
1657 file2.extend_from_slice(&f[..HEADER8]);
1658 file2[20..24].copy_from_slice(&(comp2.len() as u32).to_le_bytes());
1659 file2.extend_from_slice(&comp2);
1660 let (_, out, _) = decode_mnf8(&file2).expect("valid token wire decodes");
1661 assert_eq!(out, pred_residual_invert(&res, 16, 16, PredKind::Med));
1662 }
1663}