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 FLAG_CHANNEL_DIFF: u32 = 64; pub const FLAG_REF_R: u32 = 128;
79pub const FLAGS_RESERVED8: u32 = !0b11111111;
81
82fn cd_reference(flags: u32) -> u8 {
84 ((flags >> 6) & 0b11) as u8
85}
86
87pub const BACKEND_ZLIB: u32 = 0;
89pub const BACKEND_ZSTD19: u32 = 1;
91pub const BACKEND_RESERVED2: u32 = 2; pub const BACKEND_RESERVED3: u32 = 3;
96pub const BACKEND_MIX: u32 = 4;
99
100pub const ZSTD_WINDOW_LOG_MAX: u32 = 23;
104
105use crate::codec::RANK_CANDIDATES;
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum Profile {
112 Balanced,
114 Maximum,
116}
117
118fn backend_candidates() -> Vec<(u32, Coder)> {
122 vec![
123 (BACKEND_MIX, Coder::Zstd(19)),
124 (BACKEND_ZSTD19, Coder::Zstd(19)),
125 (BACKEND_ZLIB, Coder::Zlib),
126 ]
127}
128
129fn backend_compress(backend: u32, buf: &[u8]) -> Result<Vec<u8>> {
130 match backend {
131 BACKEND_ZLIB => Ok(compress_stream(&Coder::Zlib, buf)),
132 BACKEND_ZSTD19 => Ok(compress_stream(&Coder::Zstd(19), buf)),
133 BACKEND_MIX => Ok(crate::mix_coder::mix_encode(buf)),
134 other => Err(Error::fmt_msg(format!(
135 "backend {other} not a compressed-output coder"
136 ))),
137 }
138}
139
140fn backend_decompress(backend: u32, buf: &[u8], limit: u64) -> Result<Vec<u8>> {
141 match backend {
142 BACKEND_ZLIB => {
143 let mut d = flate2::read::ZlibDecoder::new(buf).take(limit + 1);
147 let mut v = Vec::new();
148 d.read_to_end(&mut v)
149 .map_err(|_| Error::Decompress("corrupt zlib stream"))?;
150 if v.len() as u64 > limit {
151 return Err(Error::fmt_msg("bomb"));
152 }
153 if d.get_ref().total_in() as usize != buf.len() {
154 return Err(Error::fmt_msg("trailing bytes after zlib stream"));
155 }
156 Ok(v)
157 }
158 BACKEND_ZSTD19 => {
159 let cursor = std::io::Cursor::new(buf);
163 let mut d = zstd::stream::read::Decoder::new(cursor)
164 .map_err(|_| Error::Decompress("zstd init"))?;
165 d.window_log_max(ZSTD_WINDOW_LOG_MAX)
166 .map_err(|_| Error::Decompress("zstd window param"))?;
167 let mut v = Vec::new();
168 d.single_frame()
169 .take(limit + 1)
170 .read_to_end(&mut v)
171 .map_err(|_| Error::Decompress("corrupt zstd stream"))?;
172 if v.len() as u64 > limit {
173 return Err(Error::fmt_msg("bomb"));
174 }
175 Ok(v)
182 }
183 other => Err(Error::fmt_msg(format!(
184 "backend {other} not a decompressable coder"
185 ))),
186 }
187}
188
189pub fn encode_mnf8(
203 pixels: &[u8],
204 dims: Dims,
205 use_geom: bool,
206 profile: Profile,
207) -> (Vec<u8>, MnF6Stats, u32) {
208 let candidates: Vec<(u32, Coder)> = match profile {
209 Profile::Balanced => vec![(BACKEND_ZSTD19, Coder::Zstd(19))],
210 Profile::Maximum => backend_candidates(),
211 };
212 let mut best: Option<(Vec<u8>, u32, MnF6Stats, u32)> = None;
213 let pred_arms = [
218 PredKind::None,
219 PredKind::Med,
220 PredKind::Plane,
221 PredKind::Left,
222 ];
223 for (backend, coder) in candidates {
224 for kind in pred_arms {
225 for planar in [false, true] {
226 let med = kind == PredKind::Med;
227 for reference in 0u8..=3 {
231 let flags = kind.flag_bit()
232 | if planar { FLAG_PLANAR } else { 0 }
233 | (u32::from(reference) << 6);
234 let (file, st) = encode_stream_coder(
235 pixels, dims, true, use_geom, flags, planar, med, kind, &coder, backend,
236 reference,
237 )
238 .expect("encoding an in-memory image cannot fail");
239 let size = file.len();
240 if best.as_ref().is_none_or(|b| size < b.0.len()) {
241 best = Some((file, flags, st, backend));
242 }
243 }
244 }
245 }
246 let base = FLAG_PRED_MED | FLAG_TOKENS;
250 for cap in [16, tokens::MAX_CANDIDATES] {
251 for reference in 0u8..=3 {
252 let flags = base | (u32::from(reference) << 6);
253 let (file, st) =
254 encode_tokens_coder_cap(pixels, dims, backend, flags, cap, reference)
255 .expect("encoding an in-memory image cannot fail");
256 let size = file.len();
257 if best.as_ref().is_none_or(|b| size < b.0.len()) {
258 best = Some((file, flags, st, backend));
259 }
260 }
261 }
262 }
263 let (file, flags, mut st, backend) = best.expect("at least one candidate");
264 st.chosen_flags = flags;
265 (file, st, backend)
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum PredKind {
272 None,
274 Med,
276 Plane,
278 Left,
280}
281
282impl PredKind {
283 fn from_flags(flags: u32) -> PredKind {
284 if flags & FLAG_PRED_MED != 0 {
285 PredKind::Med
286 } else if flags & FLAG_PRED_PLANE != 0 {
287 PredKind::Plane
288 } else if flags & FLAG_PRED_LEFT != 0 {
289 PredKind::Left
290 } else {
291 PredKind::None
292 }
293 }
294
295 fn flag_bit(self) -> u32 {
296 match self {
297 PredKind::None => 0,
298 PredKind::Med => FLAG_PRED_MED,
299 PredKind::Plane => FLAG_PRED_PLANE,
300 PredKind::Left => FLAG_PRED_LEFT,
301 }
302 }
303
304 fn predict(self, a: u32, b: u32, cc: u32) -> u32 {
307 match self {
308 PredKind::None | PredKind::Med => crate::mnf6::med_predict_pixel(a, b, cc),
309 PredKind::Plane => {
310 (a as i64 + b as i64 - cc as i64).clamp(0, 255) as u32
313 }
314 PredKind::Left => a,
315 }
316 }
317}
318
319fn image_predict(pixels: &[u8], w: usize, h: usize, kind: PredKind) -> Vec<u8> {
326 let mut out = vec![0u8; pixels.len()];
327 for y in 0..h {
328 for x in 0..w {
329 for c in 0..3usize {
330 let i = (y * w + x) * 3 + c;
331 let pred: u32 = if x == 0 && y == 0 {
332 127
333 } else if y == 0 {
334 u32::from(pixels[i - 3])
335 } else if x == 0 {
336 u32::from(pixels[i - w * 3])
337 } else {
338 let a = u32::from(pixels[i - 3]);
339 let b = u32::from(pixels[i - w * 3]);
340 let cc = u32::from(pixels[i - w * 3 - 3]);
341 kind.predict(a, b, cc)
342 };
343 out[i] = pred as u8;
344 }
345 }
346 }
347 out
348}
349
350fn channel_diff_relabel(
357 stream: &[u8],
358 planar: bool,
359 tokens: bool,
360 w: u32,
361 h: u32,
362 reference: u8,
363) -> Vec<u8> {
364 if tokens {
365 let mut out = Vec::with_capacity(stream.len());
367 let mut pos = 0usize;
368 let npx = (w as usize) * (h as usize);
369 let mut ppos = 0usize;
370 while ppos < npx && pos < stream.len() {
371 let tag = stream[pos];
372 out.push(tag);
373 pos += 1;
374 match tag {
375 tokens::TAG_LITERAL => {
376 let px = &stream[pos..pos + 3];
377 let (refc, a, b) = ref_pick(px, reference);
378 out.push(refc);
379 out.push(a.wrapping_sub(refc));
380 out.push(b.wrapping_sub(refc));
381 pos += 3;
382 ppos += 1;
383 }
384 tokens::TAG_MATCH => {
385 if pos + 5 > stream.len() {
386 panic!("cd: token match truncated (pos {pos}/{})", stream.len());
387 }
388 let len = u32::from(stream[pos + 4]) as usize;
389 out.extend_from_slice(&stream[pos..pos + 5]);
390 pos += 5;
391 ppos += len;
392 }
393 tokens::TAG_NEAR => {
394 if pos + 3 > stream.len() {
395 panic!("cd: token near truncated (pos {pos}/{})", stream.len());
396 }
397 let len = u32::from(stream[pos + 2]) as usize;
398 out.extend_from_slice(&stream[pos..pos + 3]);
399 pos += 3;
400 ppos += len;
401 }
402 other => panic!(
403 "token tag {other} outside the recorded grammar (pos {pos}/{}, ppos {ppos}/{npx})",
404 stream.len()
405 ),
406 }
407 }
408 debug_assert_eq!(ppos, npx);
409 out
410 } else if planar {
411 let mut out = Vec::with_capacity(stream.len());
414 let mut pos = 0usize;
415 let cols = (w as usize).div_ceil(TILE_SIZE as usize);
416 let tile_count =
417 (w as usize).div_ceil(TILE_SIZE as usize) * (h as usize).div_ceil(TILE_SIZE as usize);
418 for i in 0..tile_count {
419 let (row, col) = (i / cols, i % cols);
420 let tw = (TILE_SIZE as usize).min(w as usize - col * TILE_SIZE as usize);
421 let th = (TILE_SIZE as usize).min(h as usize - row * TILE_SIZE as usize);
422 let nb = tw * th * 3;
423 let mode = stream[pos];
424 out.push(mode);
425 pos += 1;
426 if mode == 0 {
427 let body = &stream[pos..pos + nb];
428 let third = nb / 3;
429 let (pr, pg, pb) = (&body[..third], &body[third..2 * third], &body[2 * third..]);
430 match reference {
431 1 => {
432 out.extend_from_slice(pg);
433 for (r, g) in pr.iter().zip(pg) {
434 out.push(r.wrapping_sub(*g));
435 }
436 for (b, g) in pb.iter().zip(pg) {
437 out.push(b.wrapping_sub(*g));
438 }
439 }
440 2 => {
441 out.extend_from_slice(pr);
442 for (g, r) in pg.iter().zip(pr) {
443 out.push(g.wrapping_sub(*r));
444 }
445 for (b, r) in pb.iter().zip(pr) {
446 out.push(b.wrapping_sub(*r));
447 }
448 }
449 3 => {
450 out.extend_from_slice(pb);
451 for (r, b) in pr.iter().zip(pb) {
452 out.push(r.wrapping_sub(*b));
453 }
454 for (g, b) in pg.iter().zip(pb) {
455 out.push(g.wrapping_sub(*b));
456 }
457 }
458 _ => unreachable!("reference constrained"),
459 }
460 pos += nb;
461 } else if mode == 1 {
462 const META: usize = 4;
463 out.extend_from_slice(&stream[pos..pos + META + nb]);
464 pos += META + nb;
465 } else if mode == 2 {
466 const META: usize = 5;
467 out.extend_from_slice(&stream[pos..pos + META + nb]);
468 pos += META + nb;
469 } else if mode == 3 {
470 let np = nb / 3;
471 let n9 = (np * 9).div_ceil(8);
472 out.extend_from_slice(&stream[pos..pos + np + 2 * n9]);
473 pos += np + 2 * n9;
474 } else if mode == 4 || mode == 5 {
475 let meta = if mode == 5 { 5 } else { 4 };
476 out.extend_from_slice(&stream[pos..pos + meta + nb]);
477 pos += meta + nb;
478 } else {
479 panic!(
480 "mode {mode} outside the recorded grammar (tile {i} pos {pos}/{})",
481 stream.len()
482 );
483 }
484 }
485 debug_assert_eq!(pos, stream.len());
486 out
487 } else {
488 let mut out = Vec::with_capacity(stream.len());
491 let mut pos = 0usize;
492 let cols = (w as usize).div_ceil(TILE_SIZE as usize);
493 let tile_count =
494 (w as usize).div_ceil(TILE_SIZE as usize) * (h as usize).div_ceil(TILE_SIZE as usize);
495 for i in 0..tile_count {
496 let (row, col) = (i / cols, i % cols);
497 let tw = (TILE_SIZE as usize).min(w as usize - col * TILE_SIZE as usize);
498 let th = (TILE_SIZE as usize).min(h as usize - row * TILE_SIZE as usize);
499 let nb = tw * th * 3;
500 let mode = stream[pos];
501 out.push(mode);
502 pos += 1;
503 if mode == 0 {
504 for k in 0..nb / 3 {
505 let t = &stream[pos + k * 3..pos + k * 3 + 3];
506 let (refc, a, b) = ref_pick(t, reference);
507 out.push(refc);
508 out.push(a.wrapping_sub(refc));
509 out.push(b.wrapping_sub(refc));
510 }
511 pos += nb;
512 } else {
513 let extra = match mode {
516 1 | 4 => 4 + nb,
517 2 | 5 => 5 + nb,
518 3 => {
519 let np = nb / 3;
520 (np * 9).div_ceil(8) * 2 + np
521 }
522 other => panic!(
523 "mode {other} outside the recorded grammar (pos {pos}/{})",
524 stream.len()
525 ),
526 };
527 out.extend_from_slice(&stream[pos..pos + extra]);
528 pos += extra;
529 }
530 }
531 debug_assert_eq!(pos, stream.len());
532 out
533 }
534}
535
536fn ref_pick(px: &[u8], reference: u8) -> (u8, u8, u8) {
539 let (r, g, b) = (px[0], px[1], px[2]);
540 match reference {
541 1 => (g, r, b),
542 2 => (r, g, b),
543 3 => (b, r, g),
544 _ => (0, 0, 0),
545 }
546}
547
548fn unpermute_wire_to_planar(wire: &[u8], reference: u8) -> Vec<u8> {
551 let third = wire.len() / 3;
552 let (pref, pa, pb) = (&wire[..third], &wire[third..2 * third], &wire[2 * third..]);
553 match reference {
554 1 => {
556 let mut out: Vec<u8> = pa
557 .iter()
558 .zip(pref)
559 .map(|(&da, &g)| da.wrapping_add(g))
560 .collect();
561 out.extend_from_slice(pref);
562 out.extend(
563 pb.iter()
564 .zip(pref)
565 .map(|(&db, &g)| db.wrapping_add(g))
566 .collect::<Vec<u8>>(),
567 );
568 out
569 }
570 2 => {
572 let mut out = pref.to_vec();
573 out.extend(
574 pa.iter()
575 .zip(pref)
576 .map(|(&da, &rf)| da.wrapping_add(rf))
577 .collect::<Vec<u8>>(),
578 );
579 out.extend(
580 pb.iter()
581 .zip(pref)
582 .map(|(&db, &rf)| db.wrapping_add(rf))
583 .collect::<Vec<u8>>(),
584 );
585 out
586 }
587 3 => {
589 let mut out: Vec<u8> = pa
590 .iter()
591 .zip(pref)
592 .map(|(&da, &bf)| da.wrapping_add(bf))
593 .collect();
594 out.extend(
595 pb.iter()
596 .zip(pref)
597 .map(|(&db, &bf)| db.wrapping_add(bf))
598 .collect::<Vec<u8>>(),
599 );
600 out.extend_from_slice(pref);
601 out
602 }
603 _ => {
604 let mut out = pref.to_vec();
605 out.extend_from_slice(pa);
606 out.extend_from_slice(pb);
607 out
608 }
609 }
610}
611
612fn push_inverse_pixel(out: &mut Vec<u8>, refc: u8, da: u8, db: u8, reference: u8) {
614 match reference {
615 1 => {
616 out.push(da.wrapping_add(refc)); out.push(refc); out.push(db.wrapping_add(refc)); }
620 2 => {
621 out.push(refc); out.push(da.wrapping_add(refc)); out.push(db.wrapping_add(refc)); }
625 3 => {
626 out.push(da.wrapping_add(refc)); out.push(db.wrapping_add(refc)); out.push(refc); }
630 _ => out.extend_from_slice(&[0, 0, 0]),
631 }
632}
633
634fn channel_diff_unrelabel(
639 stream: &[u8],
640 planar: bool,
641 tokens: bool,
642 w: u32,
643 h: u32,
644 reference: u8,
645) -> Result<Vec<u8>> {
646 if tokens {
647 let mut out = Vec::with_capacity(stream.len());
648 let mut pos = 0usize;
649 let npx = (w as usize) * (h as usize);
650 let mut ppos = 0usize;
651 while ppos < npx {
652 if pos >= stream.len() {
653 return Err(Error::fmt_msg("cd: token wire truncated"));
654 }
655 let tag = stream[pos];
656 out.push(tag);
657 pos += 1;
658 match tag {
659 tokens::TAG_LITERAL => {
660 if pos + 3 > stream.len() {
661 return Err(Error::fmt_msg("cd: token literal truncated"));
662 }
663 push_inverse_pixel(
664 &mut out,
665 stream[pos],
666 stream[pos + 1],
667 stream[pos + 2],
668 reference,
669 );
670 pos += 3;
671 ppos += 1;
672 }
673 tokens::TAG_MATCH => {
674 if pos + 5 > stream.len() {
675 return Err(Error::fmt_msg("cd: token match truncated"));
676 }
677 let len = u32::from(stream[pos + 4]) as usize;
678 out.extend_from_slice(&stream[pos..pos + 5]);
679 pos += 5;
680 ppos += len;
681 }
682 tokens::TAG_NEAR => {
683 if pos + 3 > stream.len() {
684 return Err(Error::fmt_msg("cd: token near truncated"));
685 }
686 let len = u32::from(stream[pos + 2]) as usize;
687 out.extend_from_slice(&stream[pos..pos + 3]);
688 pos += 3;
689 ppos += len;
690 }
691 _ => return Err(Error::fmt_msg("token wire: unknown tag")),
692 }
693 }
694 if pos != stream.len() {
695 return Err(Error::fmt_msg("cd: token wire trailing bytes"));
696 }
697 if ppos != npx {
698 return Err(Error::fmt_msg("cd: token pixel accounting"));
699 }
700 Ok(out)
701 } else {
702 let cols = (w as usize).div_ceil(TILE_SIZE as usize);
703 let tile_count =
704 (w as usize).div_ceil(TILE_SIZE as usize) * (h as usize).div_ceil(TILE_SIZE as usize);
705 let mut out = Vec::with_capacity(stream.len());
706 let mut pos = 0usize;
707 for i in 0..tile_count {
708 let (row, col) = (i / cols, i % cols);
709 let tw = (TILE_SIZE as usize).min(w as usize - col * TILE_SIZE as usize);
710 let th = (TILE_SIZE as usize).min(h as usize - row * TILE_SIZE as usize);
711 let nb = tw * th * 3;
712 if pos >= stream.len() {
713 return Err(Error::fmt_msg("cd: record stream truncated"));
714 }
715 let mode = stream[pos];
716 out.push(mode);
717 pos += 1;
718 match mode {
719 0 => {
720 #[allow(clippy::collapsible_if)]
721 if pos + nb > stream.len() {
722 return Err(Error::fmt_msg("cd: mode-0 body truncated"));
723 }
724 if planar {
725 let wire_planes = &stream[pos..pos + nb];
726 out.extend_from_slice(&unpermute_wire_to_planar(wire_planes, reference));
727 } else {
728 for k in 0..nb / 3 {
729 push_inverse_pixel(
730 &mut out,
731 stream[pos + k * 3],
732 stream[pos + k * 3 + 1],
733 stream[pos + k * 3 + 2],
734 reference,
735 );
736 }
737 }
738 pos += nb;
739 }
740 1 | 4 => {
741 if pos + 4 + nb > stream.len() {
742 return Err(Error::fmt_msg("cd: mode truncated"));
743 }
744 out.extend_from_slice(&stream[pos..pos + 4 + nb]);
745 pos += 4 + nb;
746 }
747 2 | 5 => {
748 if pos + 5 + nb > stream.len() {
749 return Err(Error::fmt_msg("cd: mode truncated"));
750 }
751 out.extend_from_slice(&stream[pos..pos + 5 + nb]);
752 pos += 5 + nb;
753 }
754 3 => {
755 let np = nb / 3;
756 let n9 = (np * 9).div_ceil(8);
757 if pos + np + 2 * n9 > stream.len() {
758 return Err(Error::fmt_msg("cd: mode-3 truncated"));
759 }
760 out.extend_from_slice(&stream[pos..pos + np + 2 * n9]);
761 pos += np + 2 * n9;
762 }
763 _ => return Err(Error::fmt_msg(format!("cd: mode {mode}"))),
764 }
765 }
766 if pos != stream.len() {
767 return Err(Error::fmt_msg("cd: trailing stream bytes"));
768 }
769 Ok(out)
770 }
771}
772fn encode_tokens_coder_cap(
778 pixels: &[u8],
779 dims: Dims,
780 backend: u32,
781 flags: u32,
782 cap: usize,
783 reference: u8,
784) -> Result<(Vec<u8>, MnF6Stats)> {
785 let med_pred = med_predict(pixels, dims.width as usize, dims.height as usize);
786 let res: Vec<u8> = pixels
787 .iter()
788 .zip(&med_pred)
789 .map(|(x, m)| x.wrapping_sub(*m))
790 .collect();
791 let (wire, ts) =
792 tokens::encode_plane_cap(&res, dims.width as usize, dims.height as usize, cap)?;
793 let wire = if reference != 0 {
795 channel_diff_relabel(&wire, false, true, dims.width, dims.height, reference)
796 } else {
797 wire
798 };
799 let payload = backend_compress(backend, &wire)?;
800 let payload_len =
801 u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?;
802 let mut file = Vec::with_capacity(HEADER8 + payload.len());
803 file.extend_from_slice(b"MNF8");
804 file.extend_from_slice(&dims.width.to_le_bytes());
805 file.extend_from_slice(&dims.height.to_le_bytes());
806 file.extend_from_slice(&TILE_SIZE.to_le_bytes());
807 let cols = div_ceil(dims.width, TILE_SIZE);
808 let rows = div_ceil(dims.height, TILE_SIZE);
809 let tile_count: u32 = cols
810 .checked_mul(rows)
811 .ok_or_else(|| Error::fmt_msg("too many tiles"))?;
812 file.extend_from_slice(&tile_count.to_le_bytes());
813 file.extend_from_slice(&payload_len.to_le_bytes());
814 file.extend_from_slice(&flags.to_le_bytes());
815 file.extend_from_slice(&backend.to_le_bytes());
816 file.extend_from_slice(&payload);
817 let st = MnF6Stats {
818 literal: u32::try_from(ts.literals).unwrap_or(u32::MAX),
819 ref_exact: u32::try_from(ts.matches).unwrap_or(u32::MAX),
820 ..MnF6Stats::default()
821 };
822 Ok((file, st))
823}
824
825#[allow(clippy::too_many_arguments)]
836fn encode_stream_coder(
837 pixels: &[u8],
838 dims: Dims,
839 use_atlas: bool,
840 use_geom: bool,
841 flags: u32,
842 planar: bool,
843 med: bool,
844 kind: PredKind,
845 coder: &Coder,
846 backend: u32,
847 reference: u8,
848) -> Result<(Vec<u8>, MnF6Stats)> {
849 let pure_pred = kind == PredKind::Plane || kind == PredKind::Left;
852 let med_pred = match kind {
853 PredKind::Med => med_predict(pixels, dims.width as usize, dims.height as usize),
854 PredKind::Plane => image_predict(
855 pixels,
856 dims.width as usize,
857 dims.height as usize,
858 PredKind::Plane,
859 ),
860 PredKind::Left => image_predict(
861 pixels,
862 dims.width as usize,
863 dims.height as usize,
864 PredKind::Left,
865 ),
866 PredKind::None => Vec::new(),
867 };
868 let tiles = split_tiles(pixels, dims);
869 let mut raw: Vec<u8> = Vec::with_capacity(tiles.len() * 774);
870 let mut stats = MnF6Stats::default();
871 let mut atlas = Atlas::new();
872
873 for (idx, tile) in tiles.iter().enumerate() {
874 let n = tile.nbytes();
875 let pack = |rgb: &[u8]| -> Vec<u8> {
876 if planar {
877 planar_pack(rgb)
878 } else {
879 rgb.to_vec()
880 }
881 };
882
883 let lit_src: Vec<u8> = if med || pure_pred {
886 let (tw, th) = (tile.width as usize, tile.height as usize);
887 let cols_px = dims.width as usize; let tx = (idx % ((dims.width as usize).div_ceil(16))) * 16;
889 let ty = (idx / ((dims.width as usize).div_ceil(16))) * 16;
890 let mut src = Vec::with_capacity(n);
891 for y in 0..th {
892 let row_off = ((ty + y) * cols_px + tx) * 3;
893 src.extend_from_slice(&med_pred[row_off..row_off + tw * 3]);
894 }
895 tile.data
896 .iter()
897 .zip(&src)
898 .map(|(x, m)| x.wrapping_sub(*m))
899 .collect()
900 } else {
901 tile.data.clone()
902 };
903
904 let lit_body = pack(&lit_src);
905 let mut choice_body = lit_body.clone();
906 let mut choice_mode = 0u8;
907 let mut choice_ref: Option<(u32, u8)> = None;
908 let mut best_cost = 1 + proxy_cost(coder, &lit_body);
909
910 if !pure_pred {
913 let (cy, cco, ccg) = ycocg_forward(&tile.data);
914 let mut m3 = Vec::with_capacity(n + 160);
915 m3.extend_from_slice(&cy);
916 m3.extend_from_slice(&pack9(&cco));
917 m3.extend_from_slice(&pack9(&ccg));
918 let m3_cost = 1 + proxy_cost(coder, &m3);
919 if m3_cost < best_cost {
920 best_cost = m3_cost;
921 choice_body = m3;
922 choice_mode = 3;
923 }
924 }
925
926 let tile_t = modcolor_forward_iter(&tile.data);
929
930 if use_atlas && !pure_pred && tiles.len() > 1 {
933 let sig = tile_sig(tile.width, tile.height, &tile.data);
934 for prior in atlas.rank_by_sig(&sig, tile.width, tile.height, RANK_CANDIDATES) {
935 let (_, _, ref_rgb) = &atlas.patches[prior as usize];
936 let transforms: &[u8] = if !use_geom {
941 &[0]
942 } else if tile.width == tile.height {
943 &[0, 1, 2, 3, 4, 5, 6, 7]
944 } else {
945 &[0, 1, 2, 3]
946 };
947 for &t in transforms {
948 let base = if t == 0 {
949 ref_rgb.clone()
950 } else {
951 apply_transform(t, tile.width, tile.height, ref_rgb)
952 };
953 let mut r = vec![0u8; n];
954 for i in 0..n {
955 r[i] = tile.data[i].wrapping_sub(base[i]);
956 }
957 let body = pack(&r);
958 let meta: u64 = if t == 0 { 5 } else { 6 };
960 let cost = meta + proxy_cost(coder, &body);
961 if cost < best_cost {
962 best_cost = cost;
963 choice_body = body;
964 choice_mode = if t == 0 { 1 } else { 2 };
965 choice_ref = Some((prior, t));
966 }
967 let base_t = modcolor_forward_iter(&base);
974 let mut r_c = vec![0u8; n];
975 for i in 0..n {
976 r_c[i] = tile_t[i].wrapping_sub(base_t[i]);
977 }
978 let body_c = pack(&r_c);
979 let cost_c = meta + proxy_cost(coder, &body_c);
980 if cost_c < best_cost {
981 best_cost = cost_c;
982 choice_body = body_c;
983 choice_mode = if t == 0 { 4 } else { 5 };
984 choice_ref = Some((prior, t));
985 }
986 }
987 }
988 }
989
990 stats.modes.push(choice_mode);
991 stats.refs.push(choice_ref);
992 match choice_mode {
993 0 => stats.literal += 1,
994 1 => stats.ref_exact += 1,
995 2 => {
996 stats.ref_geom += 1;
997 if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
998 stats.geom_nontrivial += 1;
999 }
1000 }
1001 3 => stats.ycocg_literal += 1,
1002 4 => stats.ref_exact_color += 1,
1003 5 => {
1004 stats.ref_geom_color += 1;
1005 if choice_ref.map(|(_, t)| t != 0).unwrap_or(false) {
1006 stats.geom_nontrivial += 1;
1007 }
1008 }
1009 _ => unreachable!("mode byte constrained above"),
1010 }
1011 raw.push(choice_mode);
1012 if let Some((p, t)) = choice_ref {
1013 raw.extend_from_slice(&p.to_le_bytes());
1014 if t != 0 {
1015 raw.push(t);
1016 }
1017 }
1018 raw.extend_from_slice(&choice_body);
1019 atlas.insert(tile);
1020 }
1021
1022 let raw = if reference != 0 {
1024 channel_diff_relabel(&raw, planar, false, dims.width, dims.height, reference)
1025 } else {
1026 raw
1027 };
1028 let payload = backend_compress(backend, &raw)?;
1029 let payload_len =
1030 u32::try_from(payload.len()).map_err(|_| Error::fmt_msg("payload exceeds u32"))?; let mut file = Vec::with_capacity(HEADER8 + payload.len());
1032 file.extend_from_slice(b"MNF8");
1033 file.extend_from_slice(&dims.width.to_le_bytes());
1034 file.extend_from_slice(&dims.height.to_le_bytes());
1035 file.extend_from_slice(&TILE_SIZE.to_le_bytes());
1036 let tile_count = u32::try_from(tiles.len()).map_err(|_| Error::fmt_msg("too many tiles"))?;
1037 file.extend_from_slice(&tile_count.to_le_bytes());
1038 file.extend_from_slice(&payload_len.to_le_bytes());
1039 file.extend_from_slice(&flags.to_le_bytes());
1040 file.extend_from_slice(&backend.to_le_bytes());
1041 file.extend_from_slice(&payload);
1042 Ok((file, stats))
1043}
1044
1045pub fn decode_mnf8(buf: &[u8]) -> Result<(Dims, Vec<u8>, u32)> {
1053 decode_mnf8_with_limits(buf, &crate::mnf6::DecodeLimits::default())
1054}
1055
1056pub fn decode_mnf8_with_limits(
1059 buf: &[u8],
1060 lim: &crate::mnf6::DecodeLimits,
1061) -> Result<(Dims, Vec<u8>, u32)> {
1062 if buf.len() < HEADER8 {
1063 return Err(Error::fmt_msg("short"));
1064 }
1065 if buf[0..4] != *b"MNF8" {
1066 return Err(Error::fmt_msg("magic"));
1067 }
1068 let u32le = |o: usize| u32::from_le_bytes(buf[o..o + 4].try_into().unwrap());
1069 let width = u32le(4);
1070 let height = u32le(8);
1071 if width == 0 || height == 0 {
1072 return Err(Error::fmt_msg("dims"));
1073 }
1074 if width as u64 * height as u64 > lim.max_pixels {
1075 return Err(Error::Limits {
1076 which: "max_pixels",
1077 limit: lim.max_pixels,
1078 actual: width as u64 * height as u64,
1079 });
1080 }
1081 if u32le(12) != TILE_SIZE {
1082 return Err(Error::fmt_msg("tilesize"));
1083 }
1084 let cols = div_ceil(width, TILE_SIZE);
1085 let rows = div_ceil(height, TILE_SIZE);
1086 let tile_count = u32le(16);
1087 if tile_count != cols * rows {
1088 return Err(Error::fmt_msg("tilecount"));
1089 }
1090 if tile_count > lim.max_tiles {
1091 return Err(Error::Limits {
1092 which: "max_tiles",
1093 limit: u64::from(lim.max_tiles),
1094 actual: u64::from(tile_count),
1095 });
1096 }
1097 let payload_len = u32le(20);
1098 if payload_len > (1 << 31) - 1 {
1099 return Err(Error::fmt_msg("payloadlen"));
1100 }
1101 if buf.len() != HEADER8 + payload_len as usize {
1102 return Err(Error::fmt_msg("filelen"));
1103 }
1104 let flags = u32le(24);
1105 if flags & FLAGS_RESERVED8 != 0 {
1108 return Err(Error::fmt_msg("reserved-flags"));
1109 }
1110 let token_wire = flags & FLAG_TOKENS != 0;
1111 if token_wire && flags & FLAG_PLANAR != 0 {
1114 return Err(Error::fmt_msg("tokens+planar are exclusive"));
1115 }
1116 let pred_count = u32::from(flags & FLAG_PRED_MED != 0)
1118 + u32::from(flags & FLAG_PRED_PLANE != 0)
1119 + u32::from(flags & FLAG_PRED_LEFT != 0);
1120 if pred_count > 1 {
1121 return Err(Error::fmt_msg("multiple predictors selected"));
1122 }
1123 if token_wire && flags & (FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0 {
1126 return Err(Error::fmt_msg("tokens+plane/left predictor exclusive"));
1127 }
1128 let backend = u32le(28);
1129 if !matches!(
1130 backend,
1131 BACKEND_ZLIB | BACKEND_ZSTD19 | BACKEND_RESERVED2 | BACKEND_RESERVED3 | BACKEND_MIX
1132 ) {
1133 return Err(Error::fmt_msg(format!("unknown backend {backend}")));
1134 }
1135 if matches!(backend, BACKEND_RESERVED2 | BACKEND_RESERVED3) {
1139 return Err(Error::fmt_msg(format!(
1140 "reserved backend {backend} (not shipped)"
1141 )));
1142 }
1143 let planar = flags & FLAG_PLANAR != 0;
1144
1145 let px_bytes_total: u64 = width as u64 * height as u64 * 3;
1160 let limit = if token_wire {
1161 (4 * width as u64)
1162 .checked_mul(height as u64)
1163 .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
1164 } else {
1165 let mode3_max: u64 = 1
1166 + (TILE_SIZE * TILE_SIZE) as u64
1167 + 2 * ((TILE_SIZE * TILE_SIZE) as u64 * 9 + 7) / 8
1168 + 2;
1169 px_bytes_total
1170 .checked_add(6 * tile_count as u64)
1171 .and_then(|l| l.checked_add(2 * mode3_max + 2))
1172 .ok_or_else(|| Error::fmt_msg("decompressed size overflow"))?
1173 };
1174
1175 let payload = &buf[HEADER8..];
1176 let raw = match backend {
1177 BACKEND_ZLIB | BACKEND_ZSTD19 => backend_decompress(backend, payload, limit)?,
1178 BACKEND_MIX => crate::mix_coder::mix_decode(payload, limit.min(i64::MAX as u64) as usize)?,
1179 other => return Err(Error::fmt_msg(format!("unsupported backend {other}"))),
1180 };
1181
1182 let reference = cd_reference(flags);
1187 let raw = if reference != 0 {
1188 channel_diff_unrelabel(&raw, planar, token_wire, width, height, reference)?
1189 } else {
1190 raw
1191 };
1192 let pixels = if token_wire {
1193 let res = tokens::decode_plane(&raw, width as usize, height as usize)?;
1197 pred_residual_invert(&res, width as usize, height as usize, PredKind::Med)
1198 } else {
1199 let kind = PredKind::from_flags(flags);
1200 match kind {
1204 PredKind::None => decode_record_walk(&raw, width, height, tile_count, planar, false)?,
1205 PredKind::Med => decode_record_walk(&raw, width, height, tile_count, planar, true)?,
1206 other => decode_pred_walk(&raw, width, height, tile_count, planar, other)?,
1207 }
1208 };
1209 Ok((Dims { width, height }, pixels, backend))
1210}
1211
1212fn pred_residual_invert(res: &[u8], w: usize, h: usize, kind: PredKind) -> Vec<u8> {
1217 let mut out = vec![0u8; w * h * 3];
1218 for y in 0..h {
1219 for x in 0..w {
1220 for c in 0..3usize {
1221 let i = (y * w + x) * 3 + c;
1222 let pred: u32 = if x == 0 && y == 0 {
1223 127
1224 } else if y == 0 {
1225 u32::from(out[i - 3])
1226 } else if x == 0 {
1227 u32::from(out[i - w * 3])
1228 } else {
1229 let a = u32::from(out[i - 3]);
1230 let b = u32::from(out[i - w * 3]);
1231 let cc = u32::from(out[i - w * 3 - 3]);
1232 kind.predict(a, b, cc)
1233 };
1234 out[i] = (pred + u32::from(res[i])).wrapping_rem_euclid(256) as u8;
1235 }
1236 }
1237 }
1238 out
1239}
1240
1241fn decode_pred_walk(
1248 raw: &[u8],
1249 width: u32,
1250 height: u32,
1251 tile_count: u32,
1252 planar: bool,
1253 kind: PredKind,
1254) -> Result<Vec<u8>> {
1255 let cols = div_ceil(width, TILE_SIZE);
1264 let mut pixels = vec![0u8; width as usize * height as usize * 3];
1265 let mut decoded: Vec<(u32, u32, Vec<u8>)> = Vec::with_capacity(tile_count as usize);
1266 let mut ip = 0usize;
1267 for i in 0..tile_count {
1268 let row = i / cols;
1269 let col = i % cols;
1270 let tw = TILE_SIZE.min(width - col * TILE_SIZE);
1271 let th = TILE_SIZE.min(height - row * TILE_SIZE);
1272 let n = (tw * th * 3) as usize;
1273 if ip >= raw.len() {
1274 return Err(Error::fmt_msg(format!("eof at tile {i}")));
1275 }
1276 let mode = raw[ip];
1277 ip += 1;
1278 let tile = match mode {
1279 0 => {
1280 if ip + n > raw.len() {
1281 return Err(Error::fmt_msg("body"));
1282 }
1283 let body = raw[ip..ip + n].to_vec();
1284 ip += n;
1285 let body = if planar {
1286 crate::mnf6::planar_unpack(&body)
1287 } else {
1288 body
1289 };
1290 let mut recon = vec![0u8; n];
1293 let gy0 = row as usize * TILE_SIZE as usize;
1294 let gx0 = col as usize * TILE_SIZE as usize;
1295 for y in 0..th as usize {
1296 for x in 0..tw as usize {
1297 for c in 0..3usize {
1298 let k = (y * tw as usize + x) * 3 + c;
1299 let gy = gy0 + y;
1300 let gx = gx0 + x;
1301 let tl = |ggy: usize, ggx: usize| -> u32 {
1302 if ggy >= gy0 && ggx >= gx0 {
1303 u32::from(
1304 recon[((ggy - gy0) * tw as usize + (ggx - gx0)) * 3 + c],
1305 )
1306 } else {
1307 u32::from(pixels[(ggy * width as usize + ggx) * 3 + c])
1308 }
1309 };
1310 let pred: u32 = if gy == 0 && gx == 0 {
1311 127
1312 } else if gy == 0 {
1313 if gx - 1 < gx0 {
1314 u32::from(pixels[(gy * width as usize + gx - 1) * 3 + c])
1315 } else {
1316 u32::from(recon[(y * tw as usize + x - 1) * 3 + c])
1317 }
1318 } else if gx == 0 {
1319 if gy - 1 < gy0 {
1320 u32::from(pixels[((gy - 1) * width as usize + gx) * 3 + c])
1321 } else {
1322 u32::from(recon[((y - 1) * tw as usize + x) * 3 + c])
1323 }
1324 } else {
1325 let a = tl(gy, gx - 1);
1326 let b = tl(gy - 1, gx);
1327 let cc = tl(gy - 1, gx - 1);
1328 kind.predict(a, b, cc)
1329 };
1330 recon[k] = (pred + u32::from(body[k])).wrapping_rem_euclid(256) as u8;
1331 }
1332 }
1333 }
1334 recon
1335 }
1336 _ => {
1337 return Err(Error::fmt_msg(format!(
1342 "tile {i}: mode {mode} under PRED_PLANE/LEFT — not yet admitted"
1343 )));
1344 }
1345 };
1346 decoded.push((tw, th, tile.clone()));
1347 for y_ in 0..th as usize {
1348 let py = row as usize * TILE_SIZE as usize + y_;
1349 for x_ in 0..tw as usize {
1350 let pxx = col as usize * TILE_SIZE as usize + x_;
1351 let d = (py * width as usize + pxx) * 3;
1352 let s = (y_ * tw as usize + x_) * 3;
1353 pixels[d..d + 3].copy_from_slice(&tile[s..s + 3]);
1354 }
1355 }
1356 }
1357 if ip != raw.len() {
1358 return Err(Error::fmt_msg("extra"));
1359 }
1360 Ok(pixels)
1361}
1362
1363#[cfg(test)]
1364mod gate30 {
1365 use super::*;
1366 use crate::mnf6::Coder;
1367 use std::path::Path;
1368 fn load(name: &str) -> (Dims, Vec<u8>) {
1369 let p = format!("{}/testdata/{}", env!("CARGO_MANIFEST_DIR"), name);
1370 let img = image::open(Path::new(&p))
1371 .unwrap_or_else(|e| panic!("{name}: {e}"))
1372 .to_rgb8();
1373 (
1374 Dims {
1375 width: img.width(),
1376 height: img.height(),
1377 },
1378 img.into_raw(),
1379 )
1380 }
1381 #[test]
1384 #[ignore]
1385 fn gate_all_arms_real_wire() {
1386 let files = [
1387 "gradient_320x240.png",
1388 "noise_256x256.png",
1389 "repeat_256x256.png",
1390 "screenshot_640x400.png",
1391 "solid_256x256.png",
1392 "heldout/blob_224x160.png",
1393 "heldout/blocks_192x128.png",
1394 "heldout/document_200x280.png",
1395 "heldout/plaid_192.png",
1396 "heldout/poster_208x144.png",
1397 "heldout-photos/autumn_160x120_s3100006.png",
1398 "heldout-photos/dusk_160x120_s3100003.png",
1399 "heldout-photos/dusk_224x160_s3100002.png",
1400 "heldout-photos/forest_192x144_s3100001.png",
1401 "heldout-photos/forest_224x160_s3100005.png",
1402 "heldout-photos/forest_224x160_s3100008.png",
1403 "heldout-photos/ocean_192x144_s3100007.png",
1404 "heldout-photos/sand_192x144_s3100004.png",
1405 "heldout-photos/slate_160x120_s3100009.png",
1406 "heldout-photos/terrain_160x120_s3100000.png",
1407 "photos/autumn_192x144_s900001.png",
1408 "photos/dusk_160x120_s900003.png",
1409 "photos/forest_224x160_s900002.png",
1410 "photos/ocean_192x144_s900007.png",
1411 "photos/sand_160x120_s900000.png",
1412 "photos/slate_192x144_s900004.png",
1413 "photos/slate_224x160_s900008.png",
1414 "photos/stone_160x120_s900006.png",
1415 "photos/terrain_160x120_s900009.png",
1416 "photos/terrain_224x160_s900005.png",
1417 ];
1418 let mut old_tot = 0usize;
1419 let mut new_tot = 0usize;
1420 let mut fires = 0usize;
1421 for name in files {
1422 let (d, px) = load(name);
1423 let mut old = usize::MAX;
1425 for kind in [PredKind::None, PredKind::Med] {
1426 for (backend, coder) in [
1427 (BACKEND_ZSTD19, Coder::Zstd(19)),
1428 (BACKEND_ZLIB, Coder::Zlib),
1429 ] {
1430 for planar in [false, true] {
1431 let med = kind == PredKind::Med;
1432 let (file, _) = encode_stream_coder(
1433 &px,
1434 d,
1435 true,
1436 true,
1437 kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 },
1438 planar,
1439 med,
1440 kind,
1441 &coder,
1442 backend,
1443 0,
1444 )
1445 .unwrap();
1446 let _ = &file;
1448 old = old.min(file.len());
1449 }
1450 }
1451 }
1452 for (backend, _coder) in [
1453 (BACKEND_ZSTD19, Coder::Zstd(19)),
1454 (BACKEND_ZLIB, Coder::Zlib),
1455 ] {
1456 let (tw, _) =
1457 encode_tokens_coder_cap(&px, d, backend, FLAG_PRED_MED | FLAG_TOKENS, 16, 0)
1458 .unwrap();
1459 old = old.min(tw.len());
1460 }
1461 let mut new_ = old;
1463 let mut new_win = String::new();
1464 for kind in [PredKind::Plane, PredKind::Left] {
1465 for (backend, coder) in [
1466 (BACKEND_ZSTD19, Coder::Zstd(19)),
1467 (BACKEND_ZLIB, Coder::Zlib),
1468 ] {
1469 let (file, _) = encode_stream_coder(
1470 &px,
1471 d,
1472 true,
1473 true,
1474 kind.flag_bit(),
1475 false,
1476 false,
1477 kind,
1478 &coder,
1479 backend,
1480 0,
1481 )
1482 .unwrap();
1483 let (dd, out, _) =
1485 decode_mnf8(&file).unwrap_or_else(|e| panic!("{name} {kind:?}: {e}"));
1486 if out != px {
1487 let pos = out
1488 .iter()
1489 .zip(&px)
1490 .position(|(a, b)| a != b)
1491 .expect("differs");
1492 panic!(
1493 "{name} {kind:?} NOT exact: first diff byte {pos} (pixel y={}, x={}, c={}): dec={} enc={}",
1494 pos / 3 / d.width as usize, pos / 3 % d.width as usize, pos % 3,
1495 out[pos], px[pos]
1496 );
1497 }
1498 let _ = dd;
1499 if file.len() < new_ {
1500 new_ = file.len();
1501 new_win = format!("{kind:?}-{backend}");
1502 }
1503 }
1504 }
1505 if new_ < old {
1506 fires += 1;
1507 }
1508 old_tot += old;
1509 new_tot += new_;
1510 println!(
1511 "{name:44} old={old:>8} new={new_:>8} {:+6} {}",
1512 new_ as i64 - old as i64,
1513 new_win
1514 );
1515 }
1516 println!(
1517 "TOTAL old={old_tot} new={new_tot} delta={:+} fires={fires}",
1518 new_tot as i64 - old_tot as i64
1519 );
1520 assert!(new_tot < old_tot, "aggregate must strictly improve");
1521 }
1522
1523 #[test]
1524 fn branch_arms_roundtrip_and_pick_fires() {
1525 let path = format!(
1529 "{}/testdata/heldout/poster_208x144.png",
1530 env!("CARGO_MANIFEST_DIR")
1531 );
1532 let img = image::open(std::path::Path::new(&path)).unwrap().to_rgb8();
1533 let d = Dims {
1534 width: img.width(),
1535 height: img.height(),
1536 };
1537 let px = img.into_raw();
1538 let (buf, st, _) = encode_mnf8(&px, d, true, Profile::Balanced);
1539 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1540 assert_eq!(out, px, "picked arm decodes exactly");
1541 assert!(
1542 st.chosen_flags & (FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1543 "poster must pick a branch arm (picked {:#x})",
1544 st.chosen_flags
1545 );
1546 let (bufold, _, _) = {
1548 let mut best: Option<(Vec<u8>, u32)> = None;
1550 for (backend, coder) in [(BACKEND_ZSTD19, Coder::Zstd(19))] {
1551 for kind in [PredKind::None, PredKind::Med] {
1552 for planar in [false, true] {
1553 let (f, _) = encode_stream_coder(
1554 &px,
1555 d,
1556 true,
1557 true,
1558 kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 },
1559 planar,
1560 kind == PredKind::Med,
1561 kind,
1562 &coder,
1563 backend,
1564 0,
1565 )
1566 .unwrap();
1567 if best.as_ref().is_none_or(|b| f.len() < b.0.len()) {
1568 best = Some((f, kind.flag_bit()));
1569 }
1570 }
1571 }
1572 let (t, _) =
1573 encode_tokens_coder_cap(&px, d, backend, FLAG_PRED_MED | FLAG_TOKENS, 16, 0)
1574 .unwrap();
1575 if best.as_ref().is_none_or(|b| t.len() < b.0.len()) {
1576 best = Some((t, FLAG_TOKENS));
1577 }
1578 }
1579 let (f, fl) = best.unwrap();
1580 (f, fl, 0u32)
1581 };
1582 assert!(
1583 buf.len() <= bufold.len(),
1584 "new pick ({}) must not exceed the old pick ({})",
1585 buf.len(),
1586 bufold.len()
1587 );
1588 }
1589
1590 #[test]
1591 fn rejects_multiple_predictors() {
1592 let mut f = vec![0u8; HEADER8];
1594 f[0..4].copy_from_slice(b"MNF8");
1595 f[4..8].copy_from_slice(&16u32.to_le_bytes());
1596 f[8..12].copy_from_slice(&16u32.to_le_bytes());
1597 f[12..16].copy_from_slice(&16u32.to_le_bytes());
1598 f[16..20].copy_from_slice(&1u32.to_le_bytes());
1599 f[20..24].copy_from_slice(&0u32.to_le_bytes());
1600 f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_PRED_PLANE).to_le_bytes());
1601 f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
1602 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1603 assert!(
1604 r.is_ok() && r.unwrap().is_err(),
1605 "two predictors must reject"
1606 );
1607 f[24..28].copy_from_slice(&(FLAG_TOKENS | FLAG_PRED_LEFT).to_le_bytes());
1608 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1609 assert!(r.is_ok() && r.unwrap().is_err(), "tokens+left must reject");
1610 }
1611}
1612
1613#[cfg(test)]
1614mod tests {
1615 use super::*;
1616
1617 fn roundtrip(px: &[u8], dims: Dims, profile: Profile) -> u32 {
1618 let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
1619 let (d, out, _) = decode_mnf8(&buf).expect("decode");
1620 assert_eq!(d, dims);
1621 assert_eq!(
1622 out, px,
1623 "profile {profile:?} backend {backend} flags {st:?}"
1624 );
1625 backend
1626 }
1627
1628 #[test]
1629 fn mnf8_balanced_roundtrip_all_classes() {
1630 for (w, h, seed) in [(16u32, 16u32, 1), (48, 33, 2), (31, 17, 4), (1, 1, 5)] {
1631 let mut s: u64 = (seed as u64).wrapping_mul(0x9E3779B97F4A7C15) ^ 0xDEADBEEF;
1632 let mut rnd = move || {
1633 s ^= s << 13;
1634 s ^= s >> 7;
1635 s ^= s << 17;
1636 s
1637 };
1638 let px: Vec<u8> = (0..w as usize * h as usize * 3)
1639 .map(|_| (rnd() & 0xFF) as u8)
1640 .collect();
1641 roundtrip(
1642 &px,
1643 Dims {
1644 width: w,
1645 height: h,
1646 },
1647 Profile::Balanced,
1648 );
1649 }
1650 }
1651
1652 #[test]
1653 fn mnf8_maximum_never_loses_to_mnf7() {
1654 for name in [
1657 "screenshot_640x400.png",
1658 "solid_256x256.png",
1659 "gradient_320x240.png",
1660 ] {
1661 let path = format!("{}/testdata/{name}", env!("CARGO_MANIFEST_DIR"));
1662 let img = image::open(&path)
1663 .unwrap_or_else(|e| panic!("{name}: {e}"))
1664 .to_rgb8();
1665 let d = Dims {
1666 width: img.width(),
1667 height: img.height(),
1668 };
1669 let px = img.into_raw();
1670 let (buf8, _, backend) = encode_mnf8(&px, d, true, Profile::Maximum);
1671 let (buf7, _) = crate::mnf6::encode_mnf7(&px, d, true);
1672 assert!(
1675 buf8.len() <= buf7.len() + 4,
1676 "{name}: MNF8({backend}) {} > MNF7(+4) {}",
1677 buf8.len(),
1678 buf7.len()
1679 );
1680 let (_, out, _) = decode_mnf8(&buf8).expect("decode");
1681 assert_eq!(out, px, "{name}: exact");
1682 }
1683 }
1684
1685 #[test]
1686 fn mnf8_rejects() {
1687 let mut f = vec![0u8; 32];
1688 f[0..4].copy_from_slice(b"MNF8");
1689 f[4..8].copy_from_slice(&16u32.to_le_bytes());
1690 f[8..12].copy_from_slice(&16u32.to_le_bytes());
1691 f[12..16].copy_from_slice(&16u32.to_le_bytes());
1692 f[16..20].copy_from_slice(&1u32.to_le_bytes());
1693 f[28..32].copy_from_slice(&7u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "unknown backend rejected");
1696 f[28..32].copy_from_slice(&3u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "reserved backend rejected");
1698 f[28..32].copy_from_slice(&2u32.to_le_bytes()); assert!(decode_mnf8(&f).is_err(), "reserved brotli backend rejected");
1700 f[28..32].copy_from_slice(&2u32.to_le_bytes());
1702 let r = std::panic::catch_unwind(|| decode_mnf8(&f));
1703 assert!(r.is_ok() && r.unwrap().is_err(), "must error, not panic");
1704 f[28..32].copy_from_slice(&0u32.to_le_bytes());
1705 f[24] = 0b100; assert!(decode_mnf8(&f).is_err(), "reserved flag rejected");
1707 f[0] = b'X';
1709 f[24] = 0;
1710 assert!(decode_mnf8(&f).is_err(), "magic");
1711 }
1712
1713 #[test]
1714 fn mnf8_balanced_picks_zstd_not_hardcoded() {
1715 let (w, h) = (64u32, 64u32);
1719 let mut px = vec![0u8; (w * h * 3) as usize];
1720 for y in 0..h {
1721 for x in 0..w {
1722 let i = ((y * w + x) * 3) as usize;
1723 px[i] = (x * 4) as u8;
1724 px[i + 1] = (y * 4) as u8;
1725 px[i + 2] = 128;
1726 }
1727 }
1728 let (buf, st, backend) = encode_mnf8(
1729 &px,
1730 Dims {
1731 width: w,
1732 height: h,
1733 },
1734 false,
1735 Profile::Balanced,
1736 );
1737 assert_eq!(backend, BACKEND_ZSTD19);
1738 assert!(
1745 st.chosen_flags & !0b1111 == 0,
1746 "balanced wrote a legal flags id, got {:#x}",
1747 st.chosen_flags
1748 );
1749 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1750 assert_eq!(out, px);
1751 }
1752
1753 #[test]
1754 fn mnf8_no_geom_no_flips() {
1755 let (w, h) = (32u32, 32u32); let mut px = vec![0u8; (w * h * 3) as usize];
1760 for y in 0..h {
1761 for x in 0..w {
1762 let i = ((y * w + x) * 3) as usize;
1763 px[i] = (x * 7 % 256) as u8;
1764 px[i + 1] = (y * 3 % 256) as u8;
1765 px[i + 2] = 5;
1766 }
1767 }
1768 let tile_row = 16usize;
1770 let tile_col = 0usize;
1771 let src_off = (tile_row * w as usize + tile_col) * 3;
1772 let tile = px[src_off..src_off + 16 * 16 * 3].to_vec();
1773 let flipped = apply_transform(1, 16, 16, &tile);
1774 px[src_off..src_off + 16 * 16 * 3].copy_from_slice(&flipped);
1775 let stacked = px.clone();
1776 let d = Dims {
1777 width: w,
1778 height: h,
1779 };
1780 let (buf, st, _) = encode_mnf8(&stacked, d, false, Profile::Balanced);
1781 assert_eq!(st.ref_geom, 0, "no flips allowed with use_geom=false");
1783 assert_eq!(st.geom_nontrivial, 0);
1784 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1785 assert_eq!(out, stacked);
1786 }
1787
1788 #[test]
1789 fn mnf8_no_atlas_arm() {
1790 let d = Dims {
1793 width: 32,
1794 height: 32,
1795 };
1796 let px: Vec<u8> = (0..d.width as usize * d.height as usize * 3)
1797 .map(|i| (i * 7 % 256) as u8)
1798 .collect();
1799 let (buf, st, _) = encode_mnf8_noclimb(&px, d);
1800 assert_eq!(
1801 st.ref_exact + st.ref_geom + st.ref_exact_color + st.ref_geom_color,
1802 0,
1803 "no-atlas arm must emit zero references"
1804 );
1805 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1806 assert_eq!(out, px);
1807 }
1808
1809 fn encode_mnf8_noclimb(px: &[u8], d: Dims) -> (Vec<u8>, MnF6Stats, u32) {
1811 let mut best: Option<(Vec<u8>, MnF6Stats, u32)> = None;
1813 for flags in [0u32, FLAG_PLANAR] {
1814 let planar = flags & FLAG_PLANAR != 0;
1815 let (file, st) = encode_stream_coder(
1816 px,
1817 d,
1818 false,
1819 false,
1820 flags,
1821 planar,
1822 false,
1823 PredKind::None,
1824 &Coder::Zstd(19),
1825 BACKEND_ZSTD19,
1826 0,
1827 )
1828 .expect("encode");
1829 if best.as_ref().is_none_or(|b| file.len() < b.0.len()) {
1830 best = Some((file, st, BACKEND_ZSTD19));
1831 }
1832 }
1833 let (f, s, b) = best.expect("variant");
1834 (f, s, b)
1835 }
1836
1837 #[test]
1838 fn mnf8_med_flag_roundtrip_photo_like() {
1839 let (w, h) = (64u32, 64u32);
1844 let mut px = vec![0u8; (w * h * 3) as usize];
1845 for y in 0..h {
1846 for x in 0..w {
1847 let i = ((y * w + x) * 3) as usize;
1848 px[i] = (x * 4) as u8;
1849 px[i + 1] = (y * 4) as u8;
1850 px[i + 2] = ((x + y) / 2) as u8;
1851 }
1852 }
1853 let d = Dims {
1854 width: w,
1855 height: h,
1856 };
1857 let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
1858 assert!(
1863 st.chosen_flags & !0b1111 == 0,
1864 "chosen flags must be a legal MNF8 id, got {:#x}",
1865 st.chosen_flags
1866 );
1867 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1868 if out != px {
1869 let first = out
1870 .iter()
1871 .zip(&px)
1872 .position(|(a, b)| a != b)
1873 .expect("differs");
1874 let (b_y, rem) = (first / 3 / (w as usize), (first / 3) % (w as usize));
1875 panic!(
1876 "not exact: first diff at byte {first} (pixel ({b_y},{rem}), ch {}): out={:?} px={:?}",
1877 first % 3,
1878 &out[first.saturating_sub(4)..first + 4],
1879 &px[first.saturating_sub(4)..first + 4]
1880 );
1881 }
1882 assert_eq!(out, px, "exact reconstruction under the measured flags");
1883 }
1884
1885 #[test]
1886 fn mnf8_med_flag_picked_on_photo_like_smoothing() {
1887 let (w, h) = (64u32, 64u32);
1891 let mut px = vec![0u8; (w * h * 3) as usize];
1892 for y in 0..h {
1893 for x in 0..w {
1894 let i = ((y * w + x) * 3) as usize;
1895 px[i] = (x * 4 + y * 2) as u8;
1897 px[i + 1] = (y * 6) as u8;
1898 px[i + 2] = ((x + y) * 3) as u8;
1899 }
1900 }
1901 let d = Dims {
1902 width: w,
1903 height: h,
1904 };
1905 let (_, st, _) = encode_mnf8(
1906 &px,
1907 d,
1908 false, Profile::Balanced,
1910 );
1911 assert!(
1916 st.chosen_flags & (FLAG_PRED_MED | FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
1917 "smooth no-repeat bodies must select a predictor (picked {:#x})",
1918 st.chosen_flags
1919 );
1920 let (_, out, _) =
1921 decode_mnf8(&encode_mnf8(&px, d, false, Profile::Balanced).0).expect("decode");
1922 assert_eq!(out, px, "exact under the measured predictor pick");
1923 }
1924
1925 #[test]
1926 fn mnf8_forced_mode4_mode5_records() {
1927 let mut s: u64 = 42;
1932 let mut rnd = move || {
1933 s ^= s << 13;
1934 s ^= s >> 7;
1935 s ^= s << 17;
1936 s
1937 };
1938 let a: Vec<u8> = (0..16 * 16 * 3).map(|_| (rnd() & 0xFF) as u8).collect();
1939 let ta_t = crate::mnf6::modcolor_forward(&a);
1940 let mut tb_t = ta_t.clone();
1941 let ramp = [0u8, 1, 2, 255];
1942 for i in (1..tb_t.len()).step_by(3) {
1943 tb_t[i] = tb_t[i].wrapping_add(ramp[(i / 3) % 4]);
1944 }
1945 let b = crate::mnf6::modcolor_inverse(&tb_t);
1946 let mut two = a.clone();
1947 two.extend_from_slice(&b);
1948 let d2 = Dims {
1949 width: 16,
1950 height: 32,
1951 };
1952 let (buf, st, _) = encode_mnf8(&two, d2, true, Profile::Balanced);
1953 assert!(
1954 st.ref_exact_color > 0,
1955 "expected mode 4 (or a color-geom below) selected, stats: exact={} geom={} ec={} gc={}",
1956 st.ref_exact,
1957 st.ref_geom,
1958 st.ref_exact_color,
1959 st.ref_geom_color
1960 );
1961 let (_, out, _) = decode_mnf8(&buf).expect("decode");
1962 assert_eq!(out, two, "mode-4 record reconstructs exactly through MNF8");
1963
1964 let flipped = apply_transform(1, 16, 16, &a);
1966 let tb_t2 = crate::mnf6::modcolor_forward(&flipped);
1967 let mut tc_t = tb_t2;
1968 for i in (1..tc_t.len()).step_by(3) {
1969 tc_t[i] = tc_t[i].wrapping_add(ramp[(i / 3) % 4]);
1970 }
1971 let c = crate::mnf6::modcolor_inverse(&tc_t);
1972 let mut three = flipped.clone();
1973 three.extend_from_slice(&c);
1974 let d3 = Dims {
1975 width: 16,
1976 height: 32,
1977 };
1978 let (buf3, st3, _) = encode_mnf8(&three, d3, true, Profile::Balanced);
1979 assert!(
1980 st3.ref_exact_color > 0 || st3.ref_geom_color > 0,
1981 "expected a color-domain reference (exact or geom), stats: ec={} gc={}",
1982 st3.ref_exact_color,
1983 st3.ref_geom_color
1984 );
1985 let (_, out3, _) = decode_mnf8(&buf3).expect("decode");
1986 assert_eq!(out3, three);
1987 }
1988
1989 fn roundtrip_tokens(px: &[u8], dims: Dims, profile: Profile) -> (u32, u32) {
1992 let (buf, st, backend) = encode_mnf8(px, dims, true, profile);
1993 let (d, out, _) = decode_mnf8(&buf).expect("decode");
1994 assert_eq!(d, dims);
1995 assert_eq!(
1996 out, px,
1997 "profile {profile:?} backend {backend} flags {:#x} literal {} matches {}",
1998 st.chosen_flags, st.literal, st.ref_exact
1999 );
2000 (st.literal, st.ref_exact)
2001 }
2002
2003 #[test]
2004 fn token_wire_unit_roundtrip_and_errors() {
2005 use crate::tokens::*;
2006 let (w, h) = (24usize, 16usize);
2008 let mut plane = vec![0u8; w * h * 3];
2009 for i in 0..w * h {
2010 plane[i * 3] = (i % 251) as u8;
2011 plane[i * 3 + 1] = ((i * 7) % 251) as u8;
2012 }
2013 for k in 300..384 {
2014 plane[k * 3..k * 3 + 3].copy_from_slice(&[9, 9, 9]);
2015 }
2016 let (wire, st) = encode_plane(&plane, w, h).expect("encode");
2017 assert!(st.matches >= 1, "planted structures must fire: {st:?}");
2018 let back = decode_plane(&wire, w, h).expect("decode");
2019 assert_eq!(back, plane);
2020 let mut bad = wire.clone();
2022 bad.push(2); assert!(decode_plane(&bad, w, h).is_err());
2024 assert!(decode_plane(&wire[..wire.len() - 1], w, h).is_err());
2025 let mut bad = wire.clone();
2026 bad.push(TAG_LITERAL);
2027 bad.extend_from_slice(&[1, 2, 3]);
2028 assert!(decode_plane(&bad, w, h).is_err()); }
2030
2031 #[test]
2032 fn mnf8_token_flag_picked_on_repeating_residual() {
2033 let (w, h) = (256u32, 256u32);
2038 let mut px = vec![0u8; (w * h * 3) as usize];
2039 for y in 0..h {
2040 for x in 0..w {
2041 let i = ((y * w + x) * 3) as usize;
2042 px[i] = (y * 3) as u8;
2044 px[i + 1] = (y / 4) as u8;
2045 px[i + 2] = 200;
2046 }
2047 }
2048 let d = Dims {
2049 width: w,
2050 height: h,
2051 };
2052 let (buf, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
2053 assert!(
2058 st.chosen_flags & (FLAG_TOKENS | FLAG_PRED_PLANE | FLAG_PRED_LEFT) != 0,
2059 "repeating-residual class must pick a measured-winning arm (picked {:#x})",
2060 st.chosen_flags
2061 );
2062 let (_, out, _) = decode_mnf8(&buf).expect("decode");
2063 assert_eq!(out, px, "wire reconstructs exactly under the pick");
2064 }
2065
2066 #[test]
2067 fn mnf8_token_flag_not_picked_on_noise() {
2068 let (w, h) = (64u32, 64u32);
2072 let mut s: u64 = 0x5EED;
2073 let mut rnd = move || {
2074 s ^= s << 13;
2075 s ^= s >> 7;
2076 s ^= s << 17;
2077 s
2078 };
2079 let px: Vec<u8> = (0..w as usize * h as usize * 3)
2080 .map(|_| (rnd() & 0xFF) as u8)
2081 .collect();
2082 let d = Dims {
2083 width: w,
2084 height: h,
2085 };
2086 let (_, st, _) = encode_mnf8(&px, d, false, Profile::Balanced);
2087 assert_eq!(
2088 st.chosen_flags & FLAG_TOKENS,
2089 0,
2090 "noise class must NOT pick tokens (picked {:#x})",
2091 st.chosen_flags
2092 );
2093 roundtrip_tokens(&px, d, Profile::Balanced);
2094 }
2095
2096 #[test]
2097 fn mnf8_tokens_decode_rejects_malformed_wire() {
2098 let f = vec![0u8; HEADER8 + 7];
2102 let mut f = f;
2103 f[0..4].copy_from_slice(b"MNF8");
2104 f[4..8].copy_from_slice(&16u32.to_le_bytes());
2105 f[8..12].copy_from_slice(&16u32.to_le_bytes());
2106 f[12..16].copy_from_slice(&16u32.to_le_bytes());
2107 f[16..20].copy_from_slice(&1u32.to_le_bytes());
2108 f[20..24].copy_from_slice(&7u32.to_le_bytes());
2109 f[24..28].copy_from_slice(&(FLAG_PRED_MED | FLAG_TOKENS).to_le_bytes());
2110 f[28..32].copy_from_slice(&BACKEND_ZSTD19.to_le_bytes());
2111 f[28..32].copy_from_slice(&BACKEND_ZLIB.to_le_bytes());
2113 let res: Vec<u8> = vec![0u8; 16 * 16 * 3];
2114 let (wire, _) = crate::tokens::encode_plane(&res, 16, 16).expect("wire");
2115 let mut wire_bad = wire.clone();
2117 wire_bad[0] = 7;
2118 let comp: Vec<u8>;
2119 {
2120 let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
2121 std::io::Write::write_all(&mut e, &wire_bad).unwrap();
2122 comp = e.finish().unwrap();
2123 }
2124 let mut file = Vec::with_capacity(HEADER8 + comp.len());
2125 file.extend_from_slice(&f[..HEADER8]);
2126 let pl = comp.len() as u32;
2127 file[20..24].copy_from_slice(&pl.to_le_bytes());
2128 file.extend_from_slice(&comp);
2129 let r = std::panic::catch_unwind(|| decode_mnf8(&file));
2130 assert!(
2131 r.is_ok() && r.unwrap().is_err(),
2132 "corrupt wire: Err not panic"
2133 );
2134 let comp2: Vec<u8>;
2136 {
2137 let mut e = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::best());
2138 std::io::Write::write_all(&mut e, &wire).unwrap();
2139 comp2 = e.finish().unwrap();
2140 }
2141 let mut file2 = Vec::with_capacity(HEADER8 + comp2.len());
2142 file2.extend_from_slice(&f[..HEADER8]);
2143 file2[20..24].copy_from_slice(&(comp2.len() as u32).to_le_bytes());
2144 file2.extend_from_slice(&comp2);
2145 let (_, out, _) = decode_mnf8(&file2).expect("valid token wire decodes");
2146 assert_eq!(out, pred_residual_invert(&res, 16, 16, PredKind::Med));
2147 }
2148}
2149
2150#[cfg(test)]
2151mod channel_diff {
2152 use super::*;
2153 use crate::mnf6::Coder;
2154 use std::path::Path;
2155
2156 fn load(name: &str) -> (Dims, Vec<u8>) {
2157 let p = format!("{}/testdata/{}", env!("CARGO_MANIFEST_DIR"), name);
2158 let img = image::open(Path::new(&p))
2159 .unwrap_or_else(|e| panic!("{name}: {e}"))
2160 .to_rgb8();
2161 (
2162 Dims {
2163 width: img.width(),
2164 height: img.height(),
2165 },
2166 img.into_raw(),
2167 )
2168 }
2169
2170 #[test]
2173 fn cd_roundtrip_all_predictor_shapes() {
2174 let (d, px) = load("gradient_320x240.png");
2175 for kind in [
2176 PredKind::None,
2177 PredKind::Med,
2178 PredKind::Plane,
2179 PredKind::Left,
2180 ] {
2181 for planar in [false, true] {
2182 for (backend, coder) in [
2183 (BACKEND_ZLIB, Coder::Zlib),
2184 (BACKEND_ZSTD19, Coder::Zstd(19)),
2185 ] {
2186 let flags =
2187 kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 } | FLAG_CHANNEL_DIFF;
2188 let (file, _) = encode_stream_coder(
2189 &px,
2190 d,
2191 true,
2192 false,
2193 flags,
2194 planar,
2195 kind == PredKind::Med,
2196 kind,
2197 &coder,
2198 backend,
2199 1,
2200 )
2201 .expect("encode");
2202 let (dd, out, _) = decode_mnf8(&file).expect("decode");
2203 assert_eq!(dd, d);
2204 assert_eq!(out, px, "cd roundtrip {kind:?} planar={planar}");
2205 }
2206 }
2207 }
2208 let flags = FLAG_PRED_MED | FLAG_TOKENS | FLAG_CHANNEL_DIFF;
2210 for cap in [16, tokens::MAX_CANDIDATES] {
2211 let (file, _) = encode_tokens_coder_cap(&px, d, BACKEND_ZSTD19, flags, cap, 1)
2212 .expect("token encode");
2213 let (dd, out, _) = decode_mnf8(&file).expect("decode");
2214 assert_eq!(dd, d);
2215 assert_eq!(out, px, "cd token roundtrip cap={cap}");
2216 }
2217 }
2218
2219 #[test]
2222 fn cd_pick_never_worse() {
2223 for name in ["photos/slate_224x160_s900008.png", "gradient_320x240.png"] {
2224 let (d, px) = load(name);
2225 let (cd_file, _, _) = encode_mnf8(&px, d, false, Profile::Maximum);
2226 let candidates: Vec<(u32, Coder)> = backend_candidates();
2229 let mut old = usize::MAX;
2230 for (backend, coder) in candidates {
2231 for kind in [
2232 PredKind::None,
2233 PredKind::Med,
2234 PredKind::Plane,
2235 PredKind::Left,
2236 ] {
2237 for planar in [false, true] {
2238 let flags = kind.flag_bit() | if planar { FLAG_PLANAR } else { 0 };
2239 let (file, _) = encode_stream_coder(
2240 &px,
2241 d,
2242 true,
2243 false,
2244 flags,
2245 planar,
2246 kind == PredKind::Med,
2247 kind,
2248 &coder,
2249 backend,
2250 0,
2251 )
2252 .expect("encode");
2253 old = old.min(file.len());
2254 }
2255 }
2256 for cap in [16, tokens::MAX_CANDIDATES] {
2257 let (file, _) = encode_tokens_coder_cap(
2258 &px,
2259 d,
2260 backend,
2261 FLAG_PRED_MED | FLAG_TOKENS,
2262 cap,
2263 0,
2264 )
2265 .expect("encode");
2266 old = old.min(file.len());
2267 }
2268 }
2269 assert!(
2270 cd_file.len() <= old,
2271 "{name}: pick {} above old {old}",
2272 cd_file.len()
2273 );
2274 }
2275 }
2276
2277 #[test]
2281 #[ignore]
2282 fn cd_corpus_gate() {
2283 let files = [
2284 "gradient_320x240.png",
2285 "noise_256x256.png",
2286 "repeat_256x256.png",
2287 "screenshot_640x400.png",
2288 "solid_256x256.png",
2289 "heldout/blob_224x160.png",
2290 "heldout/blocks_192x128.png",
2291 "heldout/document_200x280.png",
2292 "heldout/plaid_192.png",
2293 "heldout/poster_208x144.png",
2294 "heldout-photos/autumn_160x120_s3100006.png",
2295 "heldout-photos/dusk_160x120_s3100003.png",
2296 "heldout-photos/dusk_224x160_s3100002.png",
2297 "heldout-photos/forest_192x144_s3100001.png",
2298 "heldout-photos/forest_224x160_s3100005.png",
2299 "heldout-photos/forest_224x160_s3100008.png",
2300 "heldout-photos/ocean_192x144_s3100007.png",
2301 "heldout-photos/sand_192x144_s3100004.png",
2302 "heldout-photos/slate_160x120_s3100009.png",
2303 "heldout-photos/terrain_160x120_s3100000.png",
2304 "photos/autumn_192x144_s900001.png",
2305 "photos/dusk_160x120_s900003.png",
2306 "photos/forest_224x160_s900002.png",
2307 "photos/ocean_192x144_s900007.png",
2308 "photos/sand_160x120_s900000.png",
2309 "photos/slate_192x144_s900004.png",
2310 "photos/slate_224x160_s900008.png",
2311 "photos/stone_160x120_s900006.png",
2312 "photos/terrain_160x120_s900009.png",
2313 "photos/terrain_224x160_s900005.png",
2314 ];
2315 let mut tot = 0usize;
2316 let mut cd_fires = 0usize;
2317 for name in files {
2318 let (d, px) = load(name);
2319 let (file, st, _) = encode_mnf8(&px, d, false, crate::mnf8::Profile::Maximum);
2320 assert_eq!(decode_mnf8(&file).unwrap().1, px, "{name}");
2321 tot += file.len();
2322 let reference = cd_reference(st.chosen_flags);
2323 cd_fires += usize::from(reference != 0);
2324 if reference != 0 {
2325 println!("{name}: ref={reference}, {}", file.len());
2326 }
2327 }
2328 println!("corpus: total={tot} cd_fires={cd_fires}");
2329 assert!(cd_fires >= 10, "cd must fire on the photographic class");
2330 }
2331
2332 #[test]
2335 fn cd_malformed_rejects() {
2336 let (d, px) = load("gradient_320x240.png");
2337 let flags = FLAG_CHANNEL_DIFF;
2338 let (file, _) = encode_stream_coder(
2339 &px,
2340 d,
2341 true,
2342 false,
2343 flags,
2344 false,
2345 false,
2346 PredKind::None,
2347 &Coder::Zstd(19),
2348 BACKEND_ZSTD19,
2349 1,
2350 )
2351 .expect("encode");
2352 for cut in [40, 41, 60, file.len() / 2] {
2354 let mut t = file.clone();
2355 t.truncate(cut.min(file.len()));
2356 assert!(
2357 decode_mnf8(&t).is_err(),
2358 "truncated cd file must reject at {cut}"
2359 );
2360 }
2361 let mut t = file.clone();
2365 let last = t.len() - 1;
2366 t[last] = t[last].wrapping_add(1);
2367 let _ = decode_mnf8(&t); }
2370}