1use std::io::Read;
39
40use crate::codec::{apply_transform, div_ceil, split_tiles, tile_sig, Atlas, Dims, TILE_SIZE};
41use crate::error::{Error, Result};
42use crate::mnf6::{
43 compress_stream, decode_record_walk, med_predict, modcolor_forward_iter, pack9, planar_pack,
44 proxy_cost, ycocg_forward, Coder, MnF6Stats, FLAG_PLANAR,
45};
46use crate::tokens;
47
48pub const HEADER8: usize = 32;
50
51pub const FLAG_PRED_MED: u32 = 4; pub const FLAG_TOKENS: u32 = 8;
56pub const FLAGS_RESERVED8: u32 = !0b1111;
58
59pub const BACKEND_ZLIB: u32 = 0;
61pub const BACKEND_ZSTD19: u32 = 1;
63pub const BACKEND_RESERVED2: u32 = 2; pub const BACKEND_RESERVED3: u32 = 3; pub const ZSTD_WINDOW_LOG_MAX: u32 = 23;
72
73use crate::codec::RANK_CANDIDATES;
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum Profile {
80 Balanced,
82 Maximum,
84}
85
86fn backend_candidates() -> Vec<(u32, Coder)> {
88 vec![
89 (BACKEND_ZSTD19, Coder::Zstd(19)),
90 (BACKEND_ZLIB, Coder::Zlib),
91 ]
92}
93
94fn backend_compress(backend: u32, buf: &[u8]) -> Result<Vec<u8>> {
95 match backend {
96 BACKEND_ZLIB => Ok(compress_stream(&Coder::Zlib, buf)),
97 BACKEND_ZSTD19 => Ok(compress_stream(&Coder::Zstd(19), buf)),
98 other => Err(Error::fmt_msg(format!(
99 "backend {other} not a compressed-output coder"
100 ))),
101 }
102}
103
104fn backend_decompress(backend: u32, buf: &[u8], limit: u64) -> Result<Vec<u8>> {
105 match backend {
106 BACKEND_ZLIB => {
107 let mut d = flate2::read::ZlibDecoder::new(buf).take(limit + 1);
111 let mut v = Vec::new();
112 d.read_to_end(&mut v)
113 .map_err(|_| Error::Decompress("corrupt zlib stream"))?;
114 if v.len() as u64 > limit {
115 return Err(Error::fmt_msg("bomb"));
116 }
117 if d.get_ref().total_in() as usize != buf.len() {
118 return Err(Error::fmt_msg("trailing bytes after zlib stream"));
119 }
120 Ok(v)
121 }
122 BACKEND_ZSTD19 => {
123 let cursor = std::io::Cursor::new(buf);
127 let mut d = zstd::stream::read::Decoder::new(cursor)
128 .map_err(|_| Error::Decompress("zstd init"))?;
129 d.window_log_max(ZSTD_WINDOW_LOG_MAX)
130 .map_err(|_| Error::Decompress("zstd window param"))?;
131 let mut v = Vec::new();
132 d.single_frame()
133 .take(limit + 1)
134 .read_to_end(&mut v)
135 .map_err(|_| Error::Decompress("corrupt zstd stream"))?;
136 if v.len() as u64 > limit {
137 return Err(Error::fmt_msg("bomb"));
138 }
139 Ok(v)
146 }
147 other => Err(Error::fmt_msg(format!(
148 "backend {other} not a decompressable coder"
149 ))),
150 }
151}
152
153pub fn encode_mnf8(
167 pixels: &[u8],
168 dims: Dims,
169 use_geom: bool,
170 profile: Profile,
171) -> (Vec<u8>, MnF6Stats, u32) {
172 let candidates: Vec<(u32, Coder)> = match profile {
173 Profile::Balanced => vec![(BACKEND_ZSTD19, Coder::Zstd(19))],
174 Profile::Maximum => backend_candidates(),
175 };
176 let mut best: Option<(Vec<u8>, u32, MnF6Stats, u32)> = None;
177 for (backend, coder) in candidates {
178 for flags in [
179 0u32,
180 FLAG_PLANAR,
181 FLAG_PRED_MED,
182 FLAG_PLANAR | FLAG_PRED_MED,
183 ] {
184 let planar = flags & FLAG_PLANAR != 0;
185 let med = flags & FLAG_PRED_MED != 0;
186 let (file, st) = encode_stream_coder(
187 pixels, dims, true, use_geom, flags, planar, med, &coder, backend,
188 )
189 .expect("encoding an in-memory image cannot fail");
190 let size = file.len();
191 if best.as_ref().is_none_or(|b| size < b.0.len()) {
192 best = Some((file, flags, st, backend));
193 }
194 }
195 let flags = FLAG_PRED_MED | FLAG_TOKENS;
200 let (file, st) = encode_tokens_coder(pixels, dims, backend, flags)
201 .expect("encoding an in-memory image cannot fail");
202 let size = file.len();
203 if best.as_ref().is_none_or(|b| size < b.0.len()) {
204 best = Some((file, flags, st, backend));
205 }
206 }
207 let (file, flags, mut st, backend) = best.expect("at least one candidate");
208 st.chosen_flags = flags;
209 (file, st, backend)
210}
211
212fn encode_tokens_coder(
216 pixels: &[u8],
217 dims: Dims,
218 backend: u32,
219 flags: u32,
220) -> Result<(Vec<u8>, MnF6Stats)> {
221 let med_pred = med_predict(pixels, dims.width as usize, dims.height as usize);
222 let res: Vec<u8> = pixels
223 .iter()
224 .zip(&med_pred)
225 .map(|(x, m)| x.wrapping_sub(*m))
226 .collect();
227 let (wire, ts) = tokens::encode_plane(&res, dims.width as usize, dims.height as usize)?;
228 let payload = backend_compress(backend, &wire)?;
229 let payload_len =
230 u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?;
231 let mut file = Vec::with_capacity(HEADER8 + payload.len());
232 file.extend_from_slice(b"MNF8");
233 file.extend_from_slice(&dims.width.to_le_bytes());
234 file.extend_from_slice(&dims.height.to_le_bytes());
235 file.extend_from_slice(&TILE_SIZE.to_le_bytes());
236 let cols = div_ceil(dims.width, TILE_SIZE);
237 let rows = div_ceil(dims.height, TILE_SIZE);
238 let tile_count: u32 = cols
239 .checked_mul(rows)
240 .ok_or_else(|| Error::fmt_msg("too many tiles"))?;
241 file.extend_from_slice(&tile_count.to_le_bytes());
242 file.extend_from_slice(&payload_len.to_le_bytes());
243 file.extend_from_slice(&flags.to_le_bytes());
244 file.extend_from_slice(&backend.to_le_bytes());
245 file.extend_from_slice(&payload);
246 let st = MnF6Stats {
247 literal: u32::try_from(ts.literals).unwrap_or(u32::MAX),
248 ref_exact: u32::try_from(ts.matches).unwrap_or(u32::MAX),
249 ..MnF6Stats::default()
250 };
251 Ok((file, st))
252}
253
254#[allow(clippy::too_many_arguments)]
265fn encode_stream_coder(
266 pixels: &[u8],
267 dims: Dims,
268 use_atlas: bool,
269 use_geom: bool,
270 flags: u32,
271 planar: bool,
272 med: bool,
273 coder: &Coder,
274 backend: u32,
275) -> Result<(Vec<u8>, MnF6Stats)> {
276 let med_pred = if med {
277 med_predict(pixels, dims.width as usize, dims.height as usize)
278 } else {
279 Vec::new()
280 };
281 let tiles = split_tiles(pixels, dims);
282 let mut raw: Vec<u8> = Vec::with_capacity(tiles.len() * 774);
283 let mut stats = MnF6Stats::default();
284 let mut atlas = Atlas::new();
285
286 for (idx, tile) in tiles.iter().enumerate() {
287 let n = tile.nbytes();
288 let pack = |rgb: &[u8]| -> Vec<u8> {
289 if planar {
290 planar_pack(rgb)
291 } else {
292 rgb.to_vec()
293 }
294 };
295
296 let lit_src: Vec<u8> = if med {
299 let (tw, th) = (tile.width as usize, tile.height as usize);
300 let cols_px = dims.width as usize; let tx = (idx % ((dims.width as usize).div_ceil(16))) * 16;
302 let ty = (idx / ((dims.width as usize).div_ceil(16))) * 16;
303 let mut src = Vec::with_capacity(n);
304 for y in 0..th {
305 let row_off = ((ty + y) * cols_px + tx) * 3;
306 src.extend_from_slice(&med_pred[row_off..row_off + tw * 3]);
307 }
308 tile.data
309 .iter()
310 .zip(&src)
311 .map(|(x, m)| x.wrapping_sub(*m))
312 .collect()
313 } else {
314 tile.data.clone()
315 };
316
317 let lit_body = pack(&lit_src);
318 let mut choice_body = lit_body.clone();
319 let mut choice_mode = 0u8;
320 let mut choice_ref: Option<(u32, u8)> = None;
321 let mut best_cost = 1 + proxy_cost(coder, &lit_body);
322
323 {
325 let (cy, cco, ccg) = ycocg_forward(&tile.data);
326 let mut m3 = Vec::with_capacity(n + 160);
327 m3.extend_from_slice(&cy);
328 m3.extend_from_slice(&pack9(&cco));
329 m3.extend_from_slice(&pack9(&ccg));
330 let m3_cost = 1 + proxy_cost(coder, &m3);
331 if m3_cost < best_cost {
332 best_cost = m3_cost;
333 choice_body = m3;
334 choice_mode = 3;
335 }
336 }
337
338 let tile_t = modcolor_forward_iter(&tile.data);
341
342 if use_atlas && tiles.len() > 1 {
344 let sig = tile_sig(tile.width, tile.height, &tile.data);
345 for prior in atlas.rank_by_sig(&sig, tile.width, tile.height, RANK_CANDIDATES) {
346 let (_, _, ref_rgb) = &atlas.patches[prior as usize];
347 let transforms: &[u8] = if !use_geom {
352 &[0]
353 } else if tile.width == tile.height {
354 &[0, 1, 2, 3, 4, 5, 6, 7]
355 } else {
356 &[0, 1, 2, 3]
357 };
358 for &t in transforms {
359 let base = if t == 0 {
360 ref_rgb.clone()
361 } else {
362 apply_transform(t, tile.width, tile.height, ref_rgb)
363 };
364 let mut r = vec![0u8; n];
365 for i in 0..n {
366 r[i] = tile.data[i].wrapping_sub(base[i]);
367 }
368 let body = pack(&r);
369 let meta: u64 = if t == 0 { 5 } else { 6 };
371 let cost = meta + proxy_cost(coder, &body);
372 if cost < best_cost {
373 best_cost = cost;
374 choice_body = body;
375 choice_mode = if t == 0 { 1 } else { 2 };
376 choice_ref = Some((prior, t));
377 }
378 let base_t = modcolor_forward_iter(&base);
385 let mut r_c = vec![0u8; n];
386 for i in 0..n {
387 r_c[i] = tile_t[i].wrapping_sub(base_t[i]);
388 }
389 let body_c = pack(&r_c);
390 let cost_c = meta + proxy_cost(coder, &body_c);
391 if cost_c < best_cost {
392 best_cost = cost_c;
393 choice_body = body_c;
394 choice_mode = if t == 0 { 4 } else { 5 };
395 choice_ref = Some((prior, t));
396 }
397 }
398 }
399 }
400
401 stats.modes.push(choice_mode);
402 stats.refs.push(choice_ref);
403 match choice_mode {
404 0 => stats.literal += 1,
405 1 => stats.ref_exact += 1,
406 2 => {
407 stats.ref_geom += 1;
408 if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
409 stats.geom_nontrivial += 1;
410 }
411 }
412 3 => stats.ycocg_literal += 1,
413 4 => stats.ref_exact_color += 1,
414 5 => {
415 stats.ref_geom_color += 1;
416 if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
417 stats.geom_nontrivial += 1;
418 }
419 }
420 _ => unreachable!("mode byte constrained above"),
421 }
422 raw.push(choice_mode);
423 if let Some((p, t)) = choice_ref {
424 raw.extend_from_slice(&p.to_le_bytes());
425 if t != 0 {
426 raw.push(t);
427 }
428 }
429 raw.extend_from_slice(&choice_body);
430 atlas.insert(tile);
431 }
432
433 let payload = backend_compress(backend, &raw)?;
434 let payload_len =
435 u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?; let mut file = Vec::with_capacity(HEADER8 + payload.len());
437 file.extend_from_slice(b"MNF8");
438 file.extend_from_slice(&dims.width.to_le_bytes());
439 file.extend_from_slice(&dims.height.to_le_bytes());
440 file.extend_from_slice(&TILE_SIZE.to_le_bytes());
441 let tile_count = u32::try_from(tiles.len()).map_err(|_| Error::fmt_msg("too many tiles"))?;
442 file.extend_from_slice(&tile_count.to_le_bytes());
443 file.extend_from_slice(&payload_len.to_le_bytes());
444 file.extend_from_slice(&flags.to_le_bytes());
445 file.extend_from_slice(&backend.to_le_bytes());
446 file.extend_from_slice(&payload);
447 Ok((file, stats))
448}
449
450pub fn decode_mnf8(buf: &[u8]) -> Result<(Dims, Vec<u8>, u32)> {
458 decode_mnf8_with_limits(buf, &crate::mnf6::DecodeLimits::default())
459}
460
461pub fn decode_mnf8_with_limits(
464 buf: &[u8],
465 lim: &crate::mnf6::DecodeLimits,
466) -> Result<(Dims, Vec<u8>, u32)> {
467 if buf.len() < HEADER8 {
468 return Err(Error::fmt_msg("short"));
469 }
470 if buf[0..4] != *b"MNF8" {
471 return Err(Error::fmt_msg("magic"));
472 }
473 let u32le = |o: usize| u32::from_le_bytes(buf[o..o + 4].try_into().unwrap());
474 let width = u32le(4);
475 let height = u32le(8);
476 if width == 0 || height == 0 {
477 return Err(Error::fmt_msg("dims"));
478 }
479 if width as u64 * height as u64 > lim.max_pixels {
480 return Err(Error::Limits {
481 which: "max_pixels",
482 limit: lim.max_pixels,
483 actual: width as u64 * height as u64,
484 });
485 }
486 if u32le(12) != TILE_SIZE {
487 return Err(Error::fmt_msg("tilesize"));
488 }
489 let cols = div_ceil(width, TILE_SIZE);
490 let rows = div_ceil(height, TILE_SIZE);
491 let tile_count = u32le(16);
492 if tile_count != cols * rows {
493 return Err(Error::fmt_msg("tilecount"));
494 }
495 if tile_count > lim.max_tiles {
496 return Err(Error::Limits {
497 which: "max_tiles",
498 limit: u64::from(lim.max_tiles),
499 actual: u64::from(tile_count),
500 });
501 }
502 let payload_len = u32le(20);
503 if payload_len > (1 << 31) - 1 {
504 return Err(Error::fmt_msg("payloadlen"));
505 }
506 if buf.len() != HEADER8 + payload_len as usize {
507 return Err(Error::fmt_msg("filelen"));
508 }
509 let flags = u32le(24);
510 if flags & FLAGS_RESERVED8 != 0 {
512 return Err(Error::fmt_msg("reserved-flags"));
513 }
514 let token_wire = flags & FLAG_TOKENS != 0;
515 if token_wire && flags & FLAG_PLANAR != 0 {
518 return Err(Error::fmt_msg("tokens+planar are exclusive"));
519 }
520 let backend = u32le(28);
521 if !matches!(
522 backend,
523 BACKEND_ZLIB | BACKEND_ZSTD19 | BACKEND_RESERVED2 | BACKEND_RESERVED3
524 ) {
525 return Err(Error::fmt_msg(format!("unknown backend {backend}")));
526 }
527 if matches!(backend, BACKEND_RESERVED2 | BACKEND_RESERVED3) {
528 return Err(Error::fmt_msg(format!(
529 "reserved backend {backend} (not shipped)"
530 )));
531 }
532 let planar = flags & FLAG_PLANAR != 0;
533
534 let px_bytes_total: u64 = width as u64 * height as u64 * 3;
549 let limit = if token_wire {
550 (4 * width as u64)
551 .checked_mul(height as u64)
552 .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
553 } else {
554 let mode3_max: u64 = 1
555 + (TILE_SIZE * TILE_SIZE) as u64
556 + 2 * ((TILE_SIZE * TILE_SIZE) as u64 * 9 + 7) / 8
557 + 2;
558 px_bytes_total
559 .checked_add(6 * tile_count as u64)
560 .and_then(|l| l.checked_add(2 * mode3_max + 2))
561 .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
562 };
563
564 let payload = &buf[HEADER8..];
565 let raw = match backend {
566 BACKEND_ZLIB | BACKEND_ZSTD19 => backend_decompress(backend, payload, limit)?,
567 other => return Err(Error::fmt_msg(format!("unsupported backend {other}"))),
568 };
569
570 let pixels = if token_wire {
571 let res = tokens::decode_plane(&raw, width as usize, height as usize)?;
575 med_residual_invert(&res, width as usize, height as usize)
576 } else {
577 decode_record_walk(
578 &raw,
579 width,
580 height,
581 tile_count,
582 planar,
583 flags & FLAG_PRED_MED != 0,
584 )?
585 };
586 Ok((Dims { width, height }, pixels, backend))
587}
588
589fn med_residual_invert(res: &[u8], w: usize, h: usize) -> Vec<u8> {
596 let mut out = vec![0u8; w * h * 3];
597 for y in 0..h {
598 for x in 0..w {
599 for c in 0..3usize {
600 let i = (y * w + x) * 3 + c;
601 let pred: u32 = if x == 0 && y == 0 {
602 127
603 } else if y == 0 {
604 u32::from(out[i - 3])
605 } else if x == 0 {
606 u32::from(out[i - w * 3])
607 } else {
608 let a = u32::from(out[i - 3]);
609 let b = u32::from(out[i - w * 3]);
610 let cc = u32::from(out[i - w * 3 - 3]);
611 crate::mnf6::med_predict_pixel(a, b, cc)
612 };
613 out[i] = (pred + u32::from(res[i])).wrapping_rem_euclid(256) as u8;
614 }
615 }
616 }
617 out
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623
624 fn roundtrip(px: &[u8], dims: Dims, profile: Profile) -> u32 {
625 let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
626 let (d, out, _) = decode_mnf8(&buf).expect("decode");
627 assert_eq!(d, dims);
628 assert_eq!(
629 out, px,
630 "profile {profile:?} backend {backend} flags {st:?}"
631 );
632 backend
633 }
634
635 #[test]
636 fn mnf8_balanced_roundtrip_all_classes() {
637 for (w, h, seed) in [(16u32, 16u32, 1), (48, 33, 2), (31, 17, 4), (1, 1, 5)] {
638 let mut s: u64 = (seed as u64).wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEF;
639 let mut rnd = move || {
640 s ^= s << 13;
641 s ^= s >> 7;
642 s ^= s << 17;
643 s
644 };
645 let px: Vec<u8> = (0..w as usize * h as usize * 3)
646 .map(|_| (rnd() & 0xFF) as u8)
647 .collect();
648 roundtrip(
649 &px,
650 Dims {
651 width: w,
652 height: h,
653 },
654 Profile::Balanced,
655 );
656 }
657 }
658
659 #[test]
660 fn mnf8_maximum_never_loses_to_mnf7() {
661 for name in [
664 "screenshot_640x400.png",
665 "solid_256x256.png",
666 "gradient_320x240.png",
667 ] {
668 let path = format!("{}/testdata/{name}", env!("CARGO_MANIFEST_DIR"));
669 let img = image::open(&path)
670 .unwrap_or_else(|e| panic!("{name}: {e}"))
671 .to_rgb8();
672 let d = Dims {
673 width: img.width(),
674 height: img.height(),
675 };
676 let px = img.into_raw();
677 let (buf8, _, backend) = encode_mnf8(&px, d, true, Profile::Maximum);
678 let (buf7, _) = crate::mnf6::encode_mnf7(&px, d, true);
679 assert!(
682 buf8.len() <= buf7.len() + 4,
683 "{name}: MNF8({backend}) {} > MNF7(+4) {}",
684 buf8.len(),
685 buf7.len()
686 );
687 let (_, out, _) = decode_mnf8(&buf8).expect("decode");
688 assert_eq!(out, px, "{name}: exact");
689 }
690 }
691
692 #[test]
693 fn mnf8_rejects() {
694 let mut f = vec![0u8; 32];
695 f[0..4].copy_from_slice(b"MNF8");
696 f[4..8].copy_from_slice(&16u32.to_le_bytes());
697 f[8..12].copy_from_slice(&16u32.to_le_bytes());
698 f[12..16].copy_from_slice(&16u32.to_le_bytes());
699 f[16..20].copy_from_slice(&1u32.to_le_bytes());
700 f[28..32].copy_from_slice(&7u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "unknown backend rejected");
703 f[28..32].copy_from_slice(&3u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "reserved backend rejected");
705 f[28..32].copy_from_slice(&2u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "reserved brotli backend rejected");
707 f[28..32].copy_from_slice(&2u32.to_le_bytes());
709 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
710 assert!(r.is_ok() && r.unwrap().is_err(), "must error, not panic");
711 f[28..32].copy_from_slice(&0u32.to_le_bytes());
712 f[24] = 0b100; assert!(decode_mnf8(&f).is_err(), "reserved flag rejected");
714 f[0] = b'X';
716 f[24] = 0;
717 assert!(decode_mnf8(&f).is_err(), "magic");
718 }
719
720 #[test]
721 fn mnf8_balanced_picks_zstd_not_hardcoded() {
722 let (w, h) = (64u32, 64u32);
726 let mut px = vec![0u8; (w * h * 3) as usize];
727 for y in 0..h {
728 for x in 0..w {
729 let i = ((y * w + x) * 3) as usize;
730 px[i] = (x * 4) as u8;
731 px[i + 1] = (y * 4) as u8;
732 px[i + 2] = 128;
733 }
734 }
735 let (buf, st, backend) = encode_mnf8(
736 &px,
737 Dims {
738 width: w,
739 height: h,
740 },
741 false,
742 Profile::Balanced,
743 );
744 assert_eq!(backend, BACKEND_ZSTD19);
745 assert!(
752 st.chosen_flags & !0b1111 == 0,
753 "balanced wrote a legal flags id, got {:#x}",
754 st.chosen_flags
755 );
756 let (_, out, _) = decode_mnf8(&buf).expect("decode");
757 assert_eq!(out, px);
758 }
759
760 #[test]
761 fn mnf8_no_geom_no_flips() {
762 let (w, h) = (32u32, 32u32); let mut px = vec![0u8; (w * h * 3) as usize];
767 for y in 0..h {
768 for x in 0..w {
769 let i = ((y * w + x) * 3) as usize;
770 px[i] = (x * 7 % 256) as u8;
771 px[i + 1] = (y * 3 % 256) as u8;
772 px[i + 2] = 5;
773 }
774 }
775 let tile_row = 16usize;
777 let tile_col = 0usize;
778 let src_off = (tile_row * w as usize + tile_col) * 3;
779 let tile = px[src_off..src_off + 16 * 16 * 3].to_vec();
780 let flipped = apply_transform(1, 16, 16, &tile);
781 px[src_off..src_off + 16 * 16 * 3].copy_from_slice(&flipped);
782 let stacked = px.clone();
783 let d = Dims {
784 width: w,
785 height: h,
786 };
787 let (buf, st, _) = encode_mnf8(&stacked, d, false, Profile::Balanced);
788 assert_eq!(st.ref_geom, 0, "no flips allowed with use_geom=false");
790 assert_eq!(st.geom_nontrivial, 0);
791 let (_, out, _) = decode_mnf8(&buf).expect("decode");
792 assert_eq!(out, stacked);
793 }
794
795 #[test]
796 fn mnf8_no_atlas_arm() {
797 let d = Dims {
800 width: 32,
801 height: 32,
802 };
803 let px: Vec<u8> = (0..d.width as usize * d.height as usize * 3)
804 .map(|i| (i * 7 % 256) as u8)
805 .collect();
806 let (buf, st, _) = encode_mnf8_noclimb(&px, d);
807 assert_eq!(
808 st.ref_exact + st.ref_geom + st.ref_exact_color + st.ref_geom_color,
809 0,
810 "no-atlas arm must emit zero references"
811 );
812 let (_, out, _) = decode_mnf8(&buf).expect("decode");
813 assert_eq!(out, px);
814 }
815
816 fn encode_mnf8_noclimb(px: &[u8], d: Dims) -> (Vec<u8>, MnF6Stats, u32) {
818 let mut best: Option<(Vec<u8>, MnF6Stats, u32)> = None;
820 for flags in [0u32, FLAG_PLANAR] {
821 let planar = flags & FLAG_PLANAR != 0;
822 let (file, st) = encode_stream_coder(
823 px,
824 d,
825 false,
826 false,
827 flags,
828 planar,
829 false,
830 &Coder::Zstd(19),
831 BACKEND_ZSTD19,
832 )
833 .expect("encode");
834 if best.as_ref().is_none_or(|b| file.len() < b.0.len()) {
835 best = Some((file, st, BACKEND_ZSTD19));
836 }
837 }
838 let (f, s, b) = best.expect("variant");
839 (f, s, b)
840 }
841
842 #[test]
843 fn mnf8_med_flag_roundtrip_photo_like() {
844 let (w, h) = (64u32, 64u32);
849 let mut px = vec![0u8; (w * h * 3) as usize];
850 for y in 0..h {
851 for x in 0..w {
852 let i = ((y * w + x) * 3) as usize;
853 px[i] = (x * 4) as u8;
854 px[i + 1] = (y * 4) as u8;
855 px[i + 2] = ((x + y) / 2) as u8;
856 }
857 }
858 let d = Dims {
859 width: w,
860 height: h,
861 };
862 let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
863 assert!(
868 st.chosen_flags & !0b1111 == 0,
869 "chosen flags must be a legal MNF8 id, got {:#x}",
870 st.chosen_flags
871 );
872 let (_, out, _) = decode_mnf8(&buf).expect("decode");
873 if out != px {
874 let first = out
875 .iter()
876 .zip(&px)
877 .position(|(a, b)| a != b)
878 .expect("differs");
879 let (b_y, rem) = (first / 3 / (w as usize), (first / 3) % (w as usize));
880 panic!(
881 "not exact: first diff at byte {first} (pixel ({b_y},{rem}), ch {}): out={:?} px={:?}",
882 first % 3,
883 &out[first.saturating_sub(4)..first + 4],
884 &px[first.saturating_sub(4)..first + 4]
885 );
886 }
887 assert_eq!(out, px, "exact reconstruction under the measured flags");
888 }
889
890 #[test]
891 fn mnf8_med_flag_picked_on_photo_like_smoothing() {
892 let (w, h) = (64u32, 64u32);
896 let mut px = vec![0u8; (w * h * 3) as usize];
897 for y in 0..h {
898 for x in 0..w {
899 let i = ((y * w + x) * 3) as usize;
900 px[i] = (x * 4 + y * 2) as u8;
902 px[i + 1] = (y * 6) as u8;
903 px[i + 2] = ((x + y) * 3) as u8;
904 }
905 }
906 let d = Dims {
907 width: w,
908 height: h,
909 };
910 let (_, st, _) = encode_mnf8(
911 &px,
912 d,
913 false, Profile::Balanced,
915 );
916 assert_eq!(
917 st.chosen_flags & FLAG_PRED_MED,
918 FLAG_PRED_MED,
919 "smooth no-repeat bodies must select the MED predictor (picked {:#x})",
920 st.chosen_flags
921 );
922 }
923
924 #[test]
925 fn mnf8_forced_mode4_mode5_records() {
926 let mut s: u64 = 42;
931 let mut rnd = move || {
932 s ^= s << 13;
933 s ^= s >> 7;
934 s ^= s << 17;
935 s
936 };
937 let a: Vec<u8> = (0..16 * 16 * 3).map(|_| (rnd() & 0xFF) as u8).collect();
938 let ta_t = crate::mnf6::modcolor_forward(&a);
939 let mut tb_t = ta_t.clone();
940 let ramp = [0u8, 1, 2, 255];
941 for i in (1..tb_t.len()).step_by(3) {
942 tb_t[i] = tb_t[i].wrapping_add(ramp[(i / 3) % 4]);
943 }
944 let b = crate::mnf6::modcolor_inverse(&tb_t);
945 let mut two = a.clone();
946 two.extend_from_slice(&b);
947 let d2 = Dims {
948 width: 16,
949 height: 32,
950 };
951 let (buf, st, _) = encode_mnf8(&two, d2, true, Profile::Balanced);
952 assert!(
953 st.ref_exact_color > 0,
954 "expected mode 4 (or a color-geom below) selected, stats: exact={} geom={} ec={} gc={}",
955 st.ref_exact,
956 st.ref_geom,
957 st.ref_exact_color,
958 st.ref_geom_color
959 );
960 let (_, out, _) = decode_mnf8(&buf).expect("decode");
961 assert_eq!(out, two, "mode-4 record reconstructs exactly through MNF8");
962
963 let flipped = apply_transform(1, 16, 16, &a);
965 let tb_t2 = crate::mnf6::modcolor_forward(&flipped);
966 let mut tc_t = tb_t2;
967 for i in (1..tc_t.len()).step_by(3) {
968 tc_t[i] = tc_t[i].wrapping_add(ramp[(i / 3) % 4]);
969 }
970 let c = crate::mnf6::modcolor_inverse(&tc_t);
971 let mut three = flipped.clone();
972 three.extend_from_slice(&c);
973 let d3 = Dims {
974 width: 16,
975 height: 32,
976 };
977 let (buf3, st3, _) = encode_mnf8(&three, d3, true, Profile::Balanced);
978 assert!(
979 st3.ref_exact_color > 0 || st3.ref_geom_color > 0,
980 "expected a color-domain reference (exact or geom), stats: ec={} gc={}",
981 st3.ref_exact_color,
982 st3.ref_geom_color
983 );
984 let (_, out3, _) = decode_mnf8(&buf3).expect("decode");
985 assert_eq!(out3, three);
986 }
987
988 fn roundtrip_tokens(px: &[u8], dims: Dims, profile: Profile) -> (u32, u32) {
991 let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
992 let (d, out, _) = decode_mnf8(&buf).expect("decode");
993 assert_eq!(d, dims);
994 assert_eq!(
995 out, px,
996 "profile {profile:?} backend {backend} flags {:#x} literal {} matches {}",
997 st.chosen_flags, st.literal, st.ref_exact
998 );
999 (st.literal, st.ref_exact)
1000 }
1001
1002 #[test]
1003 fn token_wire_unit_roundtrip_and_errors() {
1004 use crate::tokens::*;
1005 let (w, h) = (24usize, 16usize);
1007 let mut plane = vec![0u8; w * h * 3];
1008 for i in 0..w * h {
1009 plane[i * 3] = (i % 251) as u8;
1010 plane[i * 3 + 1] = ((i * 7) % 251) as u8;
1011 }
1012 for k in 300..384 {
1013 plane[k * 3..k * 3 + 3].copy_from_slice(&[9, 9, 9]);
1014 }
1015 let (wire, st) = encode_plane(&plane, w, h).expect("encode");
1016 assert!(st.matches >= 1, "planted structures must fire: {st:?}");
1017 let back = decode_plane(&wire, w, h).expect("decode");
1018 assert_eq!(back, plane);
1019 let mut bad = wire.clone();
1021 bad.push(2); assert!(decode_plane(&bad, w, h).is_err());
1023 assert!(decode_plane(&wire[..wire.len() - 1], w, h).is_err());
1024 let mut bad = wire.clone();
1025 bad.push(TAG_LITERAL);
1026 bad.extend_from_slice(&[1, 2, 3]);
1027 assert!(decode_plane(&bad, w, h).is_err()); }
1029
1030 #[test]
1031 fn mnf8_token_flag_picked_on_repeating_residual() {
1032 let (w, h) = (256u32, 256u32);
1037 let mut px = vec![0u8; (w * h * 3) as usize];
1038 for y in 0..h {
1039 for x in 0..w {
1040 let i = ((y * w + x) * 3) as usize;
1041 px[i] = (y * 3) as u8;
1043 px[i + 1] = (y / 4) as u8;
1044 px[i + 2] = 200;
1045 }
1046 }
1047 let d = Dims {
1048 width: w,
1049 height: h,
1050 };
1051 let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1052 assert_eq!(
1053 st.chosen_flags & FLAG_TOKENS,
1054 FLAG_TOKENS,
1055 "repeating-residual class must pick the token arm (picked {:#x})",
1056 st.chosen_flags
1057 );
1058 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1059 assert_eq!(out, px, "token wire reconstructs exactly");
1060 }
1061
1062 #[test]
1063 fn mnf8_token_flag_not_picked_on_noise() {
1064 let (w, h) = (64u32, 64u32);
1068 let mut s: u64 = 0x5EED;
1069 let mut rnd = move || {
1070 s ^= s << 13;
1071 s ^= s >> 7;
1072 s ^= s << 17;
1073 s
1074 };
1075 let px: Vec<u8> = (0..w as usize * h as usize * 3)
1076 .map(|_| (rnd() & 0xFF) as u8)
1077 .collect();
1078 let d = Dims {
1079 width: w,
1080 height: h,
1081 };
1082 let (_, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1083 assert_eq!(
1084 st.chosen_flags & FLAG_TOKENS,
1085 0,
1086 "noise class must NOT pick tokens (picked {:#x})",
1087 st.chosen_flags
1088 );
1089 roundtrip_tokens(&px, d, Profile::Balanced);
1090 }
1091
1092 #[test]
1093 fn mnf8_tokens_decode_rejects_malformed_wire() {
1094 let f = vec![0u8; HEADER8 + 7];
1098 let mut f = f;
1099 f[0..4].copy_from_slice(b"MNF8");
1100 f[4..8].copy_from_slice(&16u32.to_le_bytes());
1101 f[8..12].copy_from_slice(&16u32.to_le_bytes());
1102 f[12..16].copy_from_slice(&16u32.to_le_bytes());
1103 f[16..20].copy_from_slice(&1u32.to_le_bytes());
1104 f[20..24].copy_from_slice(&7u32.to_le_bytes());
1105 f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_TOKENS).to_le_bytes());
1106 f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
1107 f[28..32].copy_from_slice(&BACKEND_ZLIB.to_le_bytes());
1109 let res: Vec<u8> = vec![0u8; 16 * 16 * 3];
1110 let (wire, _) = crate::tokens::encode_plane(&res, 16, 16).expect("wire");
1111 let mut wire_bad = wire.clone();
1113 wire_bad[0] = 7;
1114 let comp: Vec<u8>;
1115 {
1116 let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
1117 std::io::Write::write_all(&mut e, &wire_bad).unwrap();
1118 comp = e.finish().unwrap();
1119 }
1120 let mut file = Vec::with_capacity(HEADER8 + comp.len());
1121 file.extend_from_slice(&f[..HEADER8]);
1122 let pl = comp.len() as u32;
1123 file[20..24].copy_from_slice(&pl.to_le_bytes());
1124 file.extend_from_slice(&comp);
1125 let r = std::panic::catch_unwind(|| decode_mnf8(&file));
1126 assert!(
1127 r.is_ok() && r.unwrap().is_err(),
1128 "corrupt wire: Err not panic"
1129 );
1130 let comp2: Vec<u8>;
1132 {
1133 let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
1134 std::io::Write::write_all(&mut e, &wire).unwrap();
1135 comp2 = e.finish().unwrap();
1136 }
1137 let mut file2 = Vec::with_capacity(HEADER8 + comp2.len());
1138 file2.extend_from_slice(&f[..HEADER8]);
1139 file2[20..24].copy_from_slice(&(comp2.len() as u32).to_le_bytes());
1140 file2.extend_from_slice(&comp2);
1141 let (_, out, _) = decode_mnf8(&file2).expect("valid token wire decodes");
1142 assert_eq!(out, med_residual_invert(&res, 16, 16));
1143 }
1144}