1#![cfg_attr(not(feature = "std"), no_std)]
22#![deny(unsafe_code)]
23
24#[cfg(not(feature = "std"))]
25extern crate alloc;
26
27#[cfg(not(feature = "std"))]
28use alloc::{vec, vec::Vec};
29#[cfg(feature = "std")]
30use std::{vec, vec::Vec};
31
32use djvu_pixmap::{GrayPixmap, Pixmap, PixmapError};
33use djvu_zp::ZpDecoder;
34
35#[cfg(feature = "std")]
41pub mod encode;
42
43#[derive(Debug, thiserror::Error, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum Iw44Error {
47 #[error("IW44 stream is truncated")]
49 Truncated,
50
51 #[error("IW44 stream contains invalid data")]
53 Invalid,
54
55 #[error("IW44 chunk is too short")]
57 ChunkTooShort,
58
59 #[error("IW44 first chunk header too short (need ≥ 9 bytes)")]
61 HeaderTooShort,
62
63 #[error("IW44 image has zero dimension")]
65 ZeroDimension,
66
67 #[error("IW44 image dimensions too large")]
69 ImageTooLarge,
70
71 #[error("IW44 subsequent chunk received before first chunk")]
73 MissingFirstChunk,
74
75 #[error("IW44 subsample must be >= 1")]
77 InvalidSubsample,
78
79 #[error("IW44 codec not yet initialized")]
81 MissingCodec,
82
83 #[error("IW44 ZP coder stream too short")]
85 ZpTooShort,
86
87 #[error("IW44 chunk does not bear expected serial number")]
95 UnexpectedSerial,
96}
97
98impl From<PixmapError> for Iw44Error {
104 fn from(_: PixmapError) -> Self {
105 Iw44Error::ImageTooLarge
106 }
107}
108
109pub(crate) const BAND_BUCKETS: [(usize, usize); 10] = [
117 (0, 0),
118 (1, 1),
119 (2, 2),
120 (3, 3),
121 (4, 7),
122 (8, 11),
123 (12, 15),
124 (16, 31),
125 (32, 47),
126 (48, 63),
127];
128
129pub(crate) const QUANT_LO_INIT: [u32; 16] = [
131 0x004000, 0x008000, 0x008000, 0x010000, 0x010000, 0x010000, 0x010000, 0x010000, 0x010000,
132 0x010000, 0x010000, 0x010000, 0x020000, 0x020000, 0x020000, 0x020000,
133];
134
135pub(crate) const QUANT_HI_INIT: [u32; 10] = [
137 0, 0x020000, 0x020000, 0x040000, 0x040000, 0x040000, 0x080000, 0x040000, 0x040000, 0x080000,
138];
139
140pub(crate) const ZERO: u8 = 1;
143pub(crate) const ACTIVE: u8 = 2;
144pub(crate) const NEW: u8 = 4;
145pub(crate) const UNK: u8 = 8;
146
147pub(crate) const fn zigzag_row(i: usize) -> u8 {
153 let b1 = ((i >> 1) & 1) as u8;
154 let b3 = ((i >> 3) & 1) as u8;
155 let b5 = ((i >> 5) & 1) as u8;
156 let b7 = ((i >> 7) & 1) as u8;
157 let b9 = ((i >> 9) & 1) as u8;
158 b1 * 16 + b3 * 8 + b5 * 4 + b7 * 2 + b9
159}
160
161pub(crate) const fn zigzag_col(i: usize) -> u8 {
162 let b0 = (i & 1) as u8;
163 let b2 = ((i >> 2) & 1) as u8;
164 let b4 = ((i >> 4) & 1) as u8;
165 let b6 = ((i >> 6) & 1) as u8;
166 let b8 = ((i >> 8) & 1) as u8;
167 b0 * 16 + b2 * 8 + b4 * 4 + b6 * 2 + b8
168}
169
170static ZIGZAG_INV: [u16; 1024] = {
176 let mut table = [0u16; 1024];
177 let mut i = 0usize;
178 while i < 1024 {
179 let r = zigzag_row(i) as usize;
180 let c = zigzag_col(i) as usize;
181 table[r * 32 + c] = i as u16;
182 i += 1;
183 }
184 table
185};
186
187static ZIGZAG_INV_SUB2: [u8; 256] = {
191 let mut table = [0u8; 256];
192 let mut i = 0usize;
193 while i < 256 {
194 let r = (zigzag_row(i) >> 1) as usize;
195 let c = (zigzag_col(i) >> 1) as usize;
196 table[r * 16 + c] = i as u8;
197 i += 1;
198 }
199 table
200};
201
202static ZIGZAG_INV_SUB4: [u8; 64] = {
205 let mut table = [0u8; 64];
206 let mut i = 0usize;
207 while i < 64 {
208 let r = (zigzag_row(i) >> 2) as usize;
209 let c = (zigzag_col(i) >> 2) as usize;
210 table[r * 8 + c] = i as u8;
211 i += 1;
212 }
213 table
214};
215
216static ZIGZAG_INV_SUB8: [u8; 16] = {
219 let mut table = [0u8; 16];
220 let mut i = 0usize;
221 while i < 16 {
222 let r = (zigzag_row(i) >> 3) as usize;
223 let c = (zigzag_col(i) >> 3) as usize;
224 table[r * 4 + c] = i as u8;
225 i += 1;
226 }
227 table
228};
229
230#[inline]
234fn normalize(val: i16) -> i32 {
235 let v = ((val as i32) + 32) >> 6;
236 v.clamp(-128, 127)
237}
238
239pub(crate) fn ycbcr_row_to_rgba(y_row: &[i32], cb_row: &[i32], cr_row: &[i32], out: &mut [u8]) {
258 debug_assert_eq!(y_row.len(), cb_row.len());
259 debug_assert_eq!(y_row.len(), cr_row.len());
260 debug_assert_eq!(out.len(), y_row.len() * 4);
261
262 let w = y_row.len();
263
264 #[cfg(target_arch = "aarch64")]
265 {
266 #[allow(unsafe_code)]
267 unsafe {
268 ycbcr_neon(
269 y_row.as_ptr(),
270 cb_row.as_ptr(),
271 cr_row.as_ptr(),
272 out.as_mut_ptr(),
273 w,
274 )
275 };
276 return;
277 }
278
279 #[allow(unreachable_code)]
281 ycbcr_portable(y_row, cb_row, cr_row, out, w);
282}
283
284#[inline]
291fn ycbcr_row_from_i16(y: &[i16], cb: &[i16], cr: &[i16], out: &mut [u8]) {
292 let w = y.len();
293 debug_assert_eq!(cb.len(), w);
294 debug_assert_eq!(cr.len(), w);
295 debug_assert_eq!(out.len(), w * 4);
296 #[cfg(target_arch = "aarch64")]
297 {
298 #[allow(unsafe_code)]
299 unsafe {
300 ycbcr_neon_raw(y.as_ptr(), cb.as_ptr(), cr.as_ptr(), out.as_mut_ptr(), w);
301 }
302 return;
303 }
304 #[cfg(all(target_arch = "x86_64", feature = "std"))]
306 {
307 if std::is_x86_feature_detected!("avx2") {
308 #[allow(unsafe_code)]
309 unsafe {
310 ycbcr_avx2_raw(y.as_ptr(), cb.as_ptr(), cr.as_ptr(), out.as_mut_ptr(), w);
311 }
312 return;
313 }
314 }
315 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
317 {
318 #[allow(unsafe_code)]
319 unsafe {
320 ycbcr_simd128_raw(y.as_ptr(), cb.as_ptr(), cr.as_ptr(), out.as_mut_ptr(), w);
321 }
322 return;
323 }
324 #[allow(unreachable_code)]
325 {
326 let mut y_norm = vec![0i32; w];
327 let mut cb_norm = vec![0i32; w];
328 let mut cr_norm = vec![0i32; w];
329 for (col, v) in y_norm.iter_mut().enumerate() {
330 *v = normalize(y[col]);
331 }
332 for col in 0..w {
333 cb_norm[col] = normalize(cb[col]);
334 cr_norm[col] = normalize(cr[col]);
335 }
336 ycbcr_row_to_rgba(&y_norm, &cb_norm, &cr_norm, out);
337 }
338}
339
340#[inline]
350#[allow(dead_code)]
351fn ycbcr_row_from_i16_half(y: &[i16], cb_half: &[i16], cr_half: &[i16], out: &mut [u8], w: usize) {
352 debug_assert!(y.len() >= w);
353 debug_assert_eq!(out.len(), w * 4);
354 #[cfg(target_arch = "aarch64")]
355 {
356 #[allow(unsafe_code)]
357 unsafe {
358 ycbcr_neon_raw_half(
359 y.as_ptr(),
360 cb_half.as_ptr(),
361 cr_half.as_ptr(),
362 out.as_mut_ptr(),
363 w,
364 );
365 }
366 return;
367 }
368 #[cfg(all(target_arch = "x86_64", feature = "std"))]
369 {
370 if std::is_x86_feature_detected!("avx2") {
371 #[allow(unsafe_code)]
372 unsafe {
373 ycbcr_avx2_raw_half(
374 y.as_ptr(),
375 cb_half.as_ptr(),
376 cr_half.as_ptr(),
377 out.as_mut_ptr(),
378 w,
379 );
380 }
381 return;
382 }
383 }
384 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
385 {
386 #[allow(unsafe_code)]
387 unsafe {
388 ycbcr_simd128_raw_half(
389 y.as_ptr(),
390 cb_half.as_ptr(),
391 cr_half.as_ptr(),
392 out.as_mut_ptr(),
393 w,
394 );
395 }
396 return;
397 }
398 #[allow(unreachable_code)]
399 {
400 let mut y_norm = vec![0i32; w];
401 let mut cb_norm = vec![0i32; w];
402 let mut cr_norm = vec![0i32; w];
403 for (col, v) in y_norm.iter_mut().enumerate() {
404 *v = normalize(y[col]);
405 }
406 for col in 0..w {
407 cb_norm[col] = normalize(cb_half[col / 2]);
408 cr_norm[col] = normalize(cr_half[col / 2]);
409 }
410 ycbcr_row_to_rgba(&y_norm, &cb_norm, &cr_norm, out);
411 }
412}
413
414fn upsample_chroma_row_bilinear(
425 half0: &[i16],
426 half1: &[i16],
427 v_blend: bool,
428 out: &mut [i16],
429 cw: usize,
430) {
431 let vsamp = |hc: usize| -> i32 {
432 if v_blend {
433 (half0[hc] as i32 + half1[hc] as i32 + 1) >> 1
434 } else {
435 half0[hc] as i32
436 }
437 };
438 for (c, o) in out.iter_mut().enumerate() {
439 let hc = c >> 1;
440 let v0 = vsamp(hc);
441 *o = if c & 1 == 0 {
442 v0 as i16
443 } else {
444 let v1 = vsamp((hc + 1).min(cw - 1));
445 ((v0 + v1 + 1) >> 1) as i16
446 };
447 }
448}
449
450#[inline(always)]
452fn ycbcr_portable(y_row: &[i32], cb_row: &[i32], cr_row: &[i32], out: &mut [u8], w: usize) {
453 use wide::i32x8;
454 let c128 = i32x8::splat(128);
455 let c0 = i32x8::splat(0);
456 let c255 = i32x8::splat(255);
457
458 let full8 = w / 8;
459 for (((yc, cbc), crc), outc) in y_row[..full8 * 8]
460 .as_chunks::<8>()
461 .0
462 .iter()
463 .zip(cb_row[..full8 * 8].as_chunks::<8>().0)
464 .zip(cr_row[..full8 * 8].as_chunks::<8>().0)
465 .zip(out[..full8 * 32].as_chunks_mut::<32>().0)
466 {
467 let ys = i32x8::from([yc[0], yc[1], yc[2], yc[3], yc[4], yc[5], yc[6], yc[7]]);
468 let bs = i32x8::from([
469 cbc[0], cbc[1], cbc[2], cbc[3], cbc[4], cbc[5], cbc[6], cbc[7],
470 ]);
471 let rs = i32x8::from([
472 crc[0], crc[1], crc[2], crc[3], crc[4], crc[5], crc[6], crc[7],
473 ]);
474 let t2 = rs + (rs >> 1_i32);
475 let t3 = ys + c128 - (bs >> 2_i32);
476 let red = (ys + c128 + t2).max(c0).min(c255).to_array();
477 let grn = (t3 - (t2 >> 1_i32)).max(c0).min(c255).to_array();
478 let blu = (t3 + (bs << 1_i32)).max(c0).min(c255).to_array();
479 for i in 0..8 {
480 outc[i * 4] = red[i] as u8;
481 outc[i * 4 + 1] = grn[i] as u8;
482 outc[i * 4 + 2] = blu[i] as u8;
483 outc[i * 4 + 3] = 255;
484 }
485 }
486 for col in (full8 * 8)..w {
487 let y = y_row[col];
488 let b = cb_row[col];
489 let r = cr_row[col];
490 let t2 = r + (r >> 1);
491 let t3 = y + 128 - (b >> 2);
492 out[col * 4] = (y + 128 + t2).clamp(0, 255) as u8;
493 out[col * 4 + 1] = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
494 out[col * 4 + 2] = (t3 + (b << 1)).clamp(0, 255) as u8;
495 out[col * 4 + 3] = 255;
496 }
497}
498
499#[cfg(target_arch = "aarch64")]
508#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
509#[target_feature(enable = "neon")]
510unsafe fn ycbcr_neon_raw(
511 yp: *const i16,
512 cbp: *const i16,
513 crp: *const i16,
514 outp: *mut u8,
515 w: usize,
516) {
517 use core::arch::aarch64::*;
518 let n_min = vdupq_n_s16(-128);
524 let n_max = vdupq_n_s16(127);
525 let c128 = vdupq_n_s16(128);
526 let alpha = vdup_n_u8(255);
527
528 let full8 = w / 8;
529 for i in 0..full8 {
530 let off = i * 8;
531 let yc = vmaxq_s16(
533 vminq_s16(vrshrq_n_s16::<6>(vld1q_s16(yp.add(off))), n_max),
534 n_min,
535 );
536 let cbc = vmaxq_s16(
537 vminq_s16(vrshrq_n_s16::<6>(vld1q_s16(cbp.add(off))), n_max),
538 n_min,
539 );
540 let crc = vmaxq_s16(
541 vminq_s16(vrshrq_n_s16::<6>(vld1q_s16(crp.add(off))), n_max),
542 n_min,
543 );
544 let y128 = vaddq_s16(yc, c128);
547 let t2 = vaddq_s16(crc, vshrq_n_s16::<1>(crc));
549 let t3 = vsubq_s16(y128, vshrq_n_s16::<2>(cbc));
551 let r16 = vaddq_s16(y128, t2);
553 let g16 = vsubq_s16(t3, vshrq_n_s16::<1>(t2));
555 let b16 = vaddq_s16(t3, vshlq_n_s16::<1>(cbc));
557 let r8 = vqmovun_s16(r16);
559 let g8 = vqmovun_s16(g16);
560 let b8 = vqmovun_s16(b16);
561 vst4_u8(outp.add(off * 4), uint8x8x4_t(r8, g8, b8, alpha));
562 }
563 for col in (full8 * 8)..w {
565 let y = normalize(*yp.add(col));
566 let b = normalize(*cbp.add(col));
567 let r = normalize(*crp.add(col));
568 let t2 = r + (r >> 1);
569 let t3 = y + 128 - (b >> 2);
570 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
571 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
572 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
573 *outp.add(col * 4 + 3) = 255;
574 }
575}
576
577#[cfg(target_arch = "aarch64")]
583#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
584#[target_feature(enable = "neon")]
585unsafe fn ycbcr_neon_raw_half(
586 yp: *const i16,
587 cbp: *const i16,
588 crp: *const i16,
589 outp: *mut u8,
590 w: usize,
591) {
592 use core::arch::aarch64::*;
593 let n_min = vdupq_n_s16(-128);
595 let n_max = vdupq_n_s16(127);
596 let c128 = vdupq_n_s16(128);
597 let alpha = vdup_n_u8(255);
598
599 let full8 = w / 8;
600 for i in 0..full8 {
601 let off = i * 8;
602 let c_off = i * 4;
603 let yc = vmaxq_s16(
605 vminq_s16(vrshrq_n_s16::<6>(vld1q_s16(yp.add(off))), n_max),
606 n_min,
607 );
608 let cb4 = vmaxq_s16(
611 vminq_s16(
612 vrshrq_n_s16::<6>(vcombine_s16(vld1_s16(cbp.add(c_off)), vdup_n_s16(0))),
613 n_max,
614 ),
615 n_min,
616 );
617 let cr4 = vmaxq_s16(
618 vminq_s16(
619 vrshrq_n_s16::<6>(vcombine_s16(vld1_s16(crp.add(c_off)), vdup_n_s16(0))),
620 n_max,
621 ),
622 n_min,
623 );
624 let cbc = vzip1q_s16(cb4, cb4);
626 let crc = vzip1q_s16(cr4, cr4);
627 let y128 = vaddq_s16(yc, c128);
629 let t2 = vaddq_s16(crc, vshrq_n_s16::<1>(crc));
630 let t3 = vsubq_s16(y128, vshrq_n_s16::<2>(cbc));
631 let r16 = vaddq_s16(y128, t2);
632 let g16 = vsubq_s16(t3, vshrq_n_s16::<1>(t2));
633 let b16 = vaddq_s16(t3, vshlq_n_s16::<1>(cbc));
634 let r8 = vqmovun_s16(r16);
635 let g8 = vqmovun_s16(g16);
636 let b8 = vqmovun_s16(b16);
637 vst4_u8(outp.add(off * 4), uint8x8x4_t(r8, g8, b8, alpha));
638 }
639 for col in (full8 * 8)..w {
641 let y = normalize(*yp.add(col));
642 let b = normalize(*cbp.add(col / 2));
643 let r = normalize(*crp.add(col / 2));
644 let t2 = r + (r >> 1);
645 let t3 = y + 128 - (b >> 2);
646 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
647 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
648 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
649 *outp.add(col * 4 + 3) = 255;
650 }
651}
652
653#[cfg(all(target_arch = "x86_64", feature = "std"))]
661#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
662#[target_feature(enable = "avx2")]
663unsafe fn ycbcr_avx2_raw(
664 yp: *const i16,
665 cbp: *const i16,
666 crp: *const i16,
667 outp: *mut u8,
668 w: usize,
669) {
670 use core::arch::x86_64::*;
671 let n_min = _mm256_set1_epi16(-128);
672 let n_max = _mm256_set1_epi16(127);
673 let c128 = _mm256_set1_epi16(128);
674 let one = _mm256_set1_epi16(1);
675
676 let full16 = w / 16;
677 for i in 0..full16 {
678 let off = i * 16;
679 let load_norm_clamp = |p: *const i16| -> __m256i {
686 let v = _mm256_loadu_si256(p as *const __m256i);
687 let high = _mm256_srai_epi16::<6>(v);
688 let bit5 = _mm256_and_si256(_mm256_srli_epi16::<5>(v), one);
689 let n = _mm256_add_epi16(high, bit5);
690 _mm256_max_epi16(_mm256_min_epi16(n, n_max), n_min)
691 };
692 let yc = load_norm_clamp(yp.add(off));
693 let cbc = load_norm_clamp(cbp.add(off));
694 let crc = load_norm_clamp(crp.add(off));
695
696 let y128 = _mm256_add_epi16(yc, c128);
698 let t2 = _mm256_add_epi16(crc, _mm256_srai_epi16::<1>(crc));
699 let t3 = _mm256_sub_epi16(y128, _mm256_srai_epi16::<2>(cbc));
700 let r16 = _mm256_add_epi16(y128, t2);
701 let g16 = _mm256_sub_epi16(t3, _mm256_srai_epi16::<1>(t2));
702 let b16 = _mm256_add_epi16(t3, _mm256_slli_epi16::<1>(cbc));
703
704 let r_pack = _mm_packus_epi16(
706 _mm256_castsi256_si128(r16),
707 _mm256_extracti128_si256::<1>(r16),
708 );
709 let g_pack = _mm_packus_epi16(
710 _mm256_castsi256_si128(g16),
711 _mm256_extracti128_si256::<1>(g16),
712 );
713 let b_pack = _mm_packus_epi16(
714 _mm256_castsi256_si128(b16),
715 _mm256_extracti128_si256::<1>(b16),
716 );
717 let a_pack = _mm_set1_epi8(-1i8);
718
719 let rg_lo = _mm_unpacklo_epi8(r_pack, g_pack);
721 let rg_hi = _mm_unpackhi_epi8(r_pack, g_pack);
722 let ba_lo = _mm_unpacklo_epi8(b_pack, a_pack);
723 let ba_hi = _mm_unpackhi_epi8(b_pack, a_pack);
724
725 let rgba0 = _mm_unpacklo_epi16(rg_lo, ba_lo);
726 let rgba1 = _mm_unpackhi_epi16(rg_lo, ba_lo);
727 let rgba2 = _mm_unpacklo_epi16(rg_hi, ba_hi);
728 let rgba3 = _mm_unpackhi_epi16(rg_hi, ba_hi);
729
730 let dst = outp.add(off * 4) as *mut __m128i;
731 _mm_storeu_si128(dst, rgba0);
732 _mm_storeu_si128(dst.add(1), rgba1);
733 _mm_storeu_si128(dst.add(2), rgba2);
734 _mm_storeu_si128(dst.add(3), rgba3);
735 }
736 for col in (full16 * 16)..w {
738 let y = normalize(*yp.add(col));
739 let b = normalize(*cbp.add(col));
740 let r = normalize(*crp.add(col));
741 let t2 = r + (r >> 1);
742 let t3 = y + 128 - (b >> 2);
743 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
744 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
745 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
746 *outp.add(col * 4 + 3) = 255;
747 }
748}
749
750#[cfg(all(target_arch = "x86_64", feature = "std"))]
757#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
758#[target_feature(enable = "avx2")]
759unsafe fn ycbcr_avx2_raw_half(
760 yp: *const i16,
761 cbp: *const i16,
762 crp: *const i16,
763 outp: *mut u8,
764 w: usize,
765) {
766 use core::arch::x86_64::*;
767 let n_min = _mm256_set1_epi16(-128);
768 let n_max = _mm256_set1_epi16(127);
769 let c128 = _mm256_set1_epi16(128);
770 let one = _mm256_set1_epi16(1);
771
772 let norm_clamp = |v: __m256i| -> __m256i {
775 let high = _mm256_srai_epi16::<6>(v);
776 let bit5 = _mm256_and_si256(_mm256_srli_epi16::<5>(v), one);
777 let n = _mm256_add_epi16(high, bit5);
778 _mm256_max_epi16(_mm256_min_epi16(n, n_max), n_min)
779 };
780
781 let full16 = w / 16;
782 for i in 0..full16 {
783 let off = i * 16;
784 let c_off = i * 8;
785
786 let yv = _mm256_loadu_si256(yp.add(off) as *const __m256i);
788 let yc = norm_clamp(yv);
789
790 let upsample = |p: *const i16| -> __m256i {
792 let v8 = _mm_loadu_si128(p as *const __m128i);
793 let spread = _mm256_permute4x64_epi64::<0b00_01_00_00>(_mm256_castsi128_si256(v8));
796 _mm256_unpacklo_epi16(spread, spread)
798 };
799 let cbc = norm_clamp(upsample(cbp.add(c_off)));
800 let crc = norm_clamp(upsample(crp.add(c_off)));
801
802 let y128 = _mm256_add_epi16(yc, c128);
803 let t2 = _mm256_add_epi16(crc, _mm256_srai_epi16::<1>(crc));
804 let t3 = _mm256_sub_epi16(y128, _mm256_srai_epi16::<2>(cbc));
805 let r16 = _mm256_add_epi16(y128, t2);
806 let g16 = _mm256_sub_epi16(t3, _mm256_srai_epi16::<1>(t2));
807 let b16 = _mm256_add_epi16(t3, _mm256_slli_epi16::<1>(cbc));
808
809 let r_pack = _mm_packus_epi16(
810 _mm256_castsi256_si128(r16),
811 _mm256_extracti128_si256::<1>(r16),
812 );
813 let g_pack = _mm_packus_epi16(
814 _mm256_castsi256_si128(g16),
815 _mm256_extracti128_si256::<1>(g16),
816 );
817 let b_pack = _mm_packus_epi16(
818 _mm256_castsi256_si128(b16),
819 _mm256_extracti128_si256::<1>(b16),
820 );
821 let a_pack = _mm_set1_epi8(-1i8);
822
823 let rg_lo = _mm_unpacklo_epi8(r_pack, g_pack);
824 let rg_hi = _mm_unpackhi_epi8(r_pack, g_pack);
825 let ba_lo = _mm_unpacklo_epi8(b_pack, a_pack);
826 let ba_hi = _mm_unpackhi_epi8(b_pack, a_pack);
827
828 let rgba0 = _mm_unpacklo_epi16(rg_lo, ba_lo);
829 let rgba1 = _mm_unpackhi_epi16(rg_lo, ba_lo);
830 let rgba2 = _mm_unpacklo_epi16(rg_hi, ba_hi);
831 let rgba3 = _mm_unpackhi_epi16(rg_hi, ba_hi);
832
833 let dst = outp.add(off * 4) as *mut __m128i;
834 _mm_storeu_si128(dst, rgba0);
835 _mm_storeu_si128(dst.add(1), rgba1);
836 _mm_storeu_si128(dst.add(2), rgba2);
837 _mm_storeu_si128(dst.add(3), rgba3);
838 }
839 for col in (full16 * 16)..w {
841 let y = normalize(*yp.add(col));
842 let b = normalize(*cbp.add(col / 2));
843 let r = normalize(*crp.add(col / 2));
844 let t2 = r + (r >> 1);
845 let t3 = y + 128 - (b >> 2);
846 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
847 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
848 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
849 *outp.add(col * 4 + 3) = 255;
850 }
851}
852
853#[cfg(target_arch = "wasm32")]
866#[allow(unsafe_code, unsafe_op_in_unsafe_fn, dead_code)]
867#[target_feature(enable = "simd128")]
868unsafe fn ycbcr_simd128_raw(
869 yp: *const i16,
870 cbp: *const i16,
871 crp: *const i16,
872 outp: *mut u8,
873 w: usize,
874) {
875 use core::arch::wasm32::*;
876 let n_min = i16x8_splat(-128);
877 let n_max = i16x8_splat(127);
878 let c128 = i16x8_splat(128);
879 let one = i16x8_splat(1);
880 let alpha_src = i16x8_splat(255);
883
884 let full8 = w / 8;
885 for i in 0..full8 {
886 let off = i * 8;
887 let load_norm_clamp = |p: *const i16| -> v128 {
892 let v = v128_load(p as *const v128);
893 let high = i16x8_shr(v, 6);
894 let bit5 = v128_and(u16x8_shr(v, 5), one);
895 let n = i16x8_add(high, bit5);
896 i16x8_max(i16x8_min(n, n_max), n_min)
897 };
898 let yc = load_norm_clamp(yp.add(off));
899 let cbc = load_norm_clamp(cbp.add(off));
900 let crc = load_norm_clamp(crp.add(off));
901
902 let y128 = i16x8_add(yc, c128);
904 let t2 = i16x8_add(crc, i16x8_shr(crc, 1));
905 let t3 = i16x8_sub(y128, i16x8_shr(cbc, 2));
906 let r16 = i16x8_add(y128, t2);
907 let g16 = i16x8_sub(t3, i16x8_shr(t2, 1));
908 let b16 = i16x8_add(t3, i16x8_shl(cbc, 1));
909
910 let v_rg = u8x16_narrow_i16x8(r16, g16);
914 let v_ba = u8x16_narrow_i16x8(b16, alpha_src);
915
916 let out0 =
920 i8x16_shuffle::<0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27>(v_rg, v_ba);
921 let out1 =
922 i8x16_shuffle::<4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31>(v_rg, v_ba);
923
924 v128_store(outp.add(off * 4) as *mut v128, out0);
925 v128_store(outp.add(off * 4 + 16) as *mut v128, out1);
926 }
927 for col in (full8 * 8)..w {
929 let y = normalize(*yp.add(col));
930 let b = normalize(*cbp.add(col));
931 let r = normalize(*crp.add(col));
932 let t2 = r + (r >> 1);
933 let t3 = y + 128 - (b >> 2);
934 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
935 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
936 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
937 *outp.add(col * 4 + 3) = 255;
938 }
939}
940
941#[cfg(target_arch = "wasm32")]
949#[allow(unsafe_code, unsafe_op_in_unsafe_fn, dead_code)]
950#[target_feature(enable = "simd128")]
951unsafe fn ycbcr_simd128_raw_half(
952 yp: *const i16,
953 cbp: *const i16,
954 crp: *const i16,
955 outp: *mut u8,
956 w: usize,
957) {
958 use core::arch::wasm32::*;
959 let n_min = i16x8_splat(-128);
960 let n_max = i16x8_splat(127);
961 let c128 = i16x8_splat(128);
962 let one = i16x8_splat(1);
963 let alpha_src = i16x8_splat(255);
964
965 let full8 = w / 8;
966 for i in 0..full8 {
967 let off = i * 8;
968 let c_off = i * 4;
969 let load_norm_clamp = |p: *const i16| -> v128 {
971 let v = v128_load(p as *const v128);
972 let high = i16x8_shr(v, 6);
973 let bit5 = v128_and(u16x8_shr(v, 5), one);
974 let n = i16x8_add(high, bit5);
975 i16x8_max(i16x8_min(n, n_max), n_min)
976 };
977 let yc = load_norm_clamp(yp.add(off));
978
979 let load_norm_chroma_4 = |p: *const i16| -> v128 {
982 let v = v128_load64_zero(p as *const u64);
983 let high = i16x8_shr(v, 6);
984 let bit5 = v128_and(u16x8_shr(v, 5), one);
985 let n = i16x8_add(high, bit5);
986 i16x8_max(i16x8_min(n, n_max), n_min)
987 };
988 let cb4 = load_norm_chroma_4(cbp.add(c_off));
989 let cr4 = load_norm_chroma_4(crp.add(c_off));
990
991 let cbc = i8x16_shuffle::<0, 1, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7, 6, 7>(cb4, cb4);
994 let crc = i8x16_shuffle::<0, 1, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7, 6, 7>(cr4, cr4);
995
996 let y128 = i16x8_add(yc, c128);
997 let t2 = i16x8_add(crc, i16x8_shr(crc, 1));
998 let t3 = i16x8_sub(y128, i16x8_shr(cbc, 2));
999 let r16 = i16x8_add(y128, t2);
1000 let g16 = i16x8_sub(t3, i16x8_shr(t2, 1));
1001 let b16 = i16x8_add(t3, i16x8_shl(cbc, 1));
1002
1003 let v_rg = u8x16_narrow_i16x8(r16, g16);
1004 let v_ba = u8x16_narrow_i16x8(b16, alpha_src);
1005 let out0 =
1006 i8x16_shuffle::<0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27>(v_rg, v_ba);
1007 let out1 =
1008 i8x16_shuffle::<4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31>(v_rg, v_ba);
1009 v128_store(outp.add(off * 4) as *mut v128, out0);
1010 v128_store(outp.add(off * 4 + 16) as *mut v128, out1);
1011 }
1012 for col in (full8 * 8)..w {
1013 let y = normalize(*yp.add(col));
1014 let b = normalize(*cbp.add(col / 2));
1015 let r = normalize(*crp.add(col / 2));
1016 let t2 = r + (r >> 1);
1017 let t3 = y + 128 - (b >> 2);
1018 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
1019 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
1020 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
1021 *outp.add(col * 4 + 3) = 255;
1022 }
1023}
1024
1025#[cfg(target_arch = "aarch64")]
1028#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
1029#[target_feature(enable = "neon")]
1030unsafe fn ycbcr_neon(yp: *const i32, cbp: *const i32, crp: *const i32, outp: *mut u8, w: usize) {
1031 use core::arch::aarch64::*;
1032 let c128 = vdupq_n_s32(128);
1033 let c0 = vdupq_n_s32(0);
1034 let c255 = vdupq_n_s32(255);
1035 let alpha = vdup_n_u8(255);
1036
1037 let full8 = w / 8;
1038 for i in 0..full8 {
1039 let off = i * 8;
1040 let y_lo = vld1q_s32(yp.add(off));
1042 let y_hi = vld1q_s32(yp.add(off + 4));
1043 let cb_lo = vld1q_s32(cbp.add(off));
1044 let cb_hi = vld1q_s32(cbp.add(off + 4));
1045 let cr_lo = vld1q_s32(crp.add(off));
1046 let cr_hi = vld1q_s32(crp.add(off + 4));
1047
1048 let t2_lo = vaddq_s32(cr_lo, vshrq_n_s32::<1>(cr_lo));
1050 let t2_hi = vaddq_s32(cr_hi, vshrq_n_s32::<1>(cr_hi));
1051 let t3_lo = vsubq_s32(vaddq_s32(y_lo, c128), vshrq_n_s32::<2>(cb_lo));
1053 let t3_hi = vsubq_s32(vaddq_s32(y_hi, c128), vshrq_n_s32::<2>(cb_hi));
1054
1055 let r_lo = vminq_s32(vmaxq_s32(vaddq_s32(vaddq_s32(y_lo, c128), t2_lo), c0), c255);
1057 let r_hi = vminq_s32(vmaxq_s32(vaddq_s32(vaddq_s32(y_hi, c128), t2_hi), c0), c255);
1058 let g_lo = vminq_s32(
1060 vmaxq_s32(vsubq_s32(t3_lo, vshrq_n_s32::<1>(t2_lo)), c0),
1061 c255,
1062 );
1063 let g_hi = vminq_s32(
1064 vmaxq_s32(vsubq_s32(t3_hi, vshrq_n_s32::<1>(t2_hi)), c0),
1065 c255,
1066 );
1067 let b_lo = vminq_s32(
1069 vmaxq_s32(vaddq_s32(t3_lo, vshlq_n_s32::<1>(cb_lo)), c0),
1070 c255,
1071 );
1072 let b_hi = vminq_s32(
1073 vmaxq_s32(vaddq_s32(t3_hi, vshlq_n_s32::<1>(cb_hi)), c0),
1074 c255,
1075 );
1076
1077 let r8 = vqmovun_s16(vcombine_s16(vmovn_s32(r_lo), vmovn_s32(r_hi)));
1079 let g8 = vqmovun_s16(vcombine_s16(vmovn_s32(g_lo), vmovn_s32(g_hi)));
1080 let b8 = vqmovun_s16(vcombine_s16(vmovn_s32(b_lo), vmovn_s32(b_hi)));
1081
1082 vst4_u8(outp.add(off * 4), uint8x8x4_t(r8, g8, b8, alpha));
1084 }
1085
1086 for col in (full8 * 8)..w {
1088 let y = *yp.add(col);
1089 let b = *cbp.add(col);
1090 let r = *crp.add(col);
1091 let t2 = r + (r >> 1);
1092 let t3 = y + 128 - (b >> 2);
1093 *outp.add(col * 4) = (y + 128 + t2).clamp(0, 255) as u8;
1094 *outp.add(col * 4 + 1) = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
1095 *outp.add(col * 4 + 2) = (t3 + (b << 1)).clamp(0, 255) as u8;
1096 *outp.add(col * 4 + 3) = 255;
1097 }
1098}
1099
1100#[derive(Clone, Debug)]
1107struct PlaneDecoder {
1108 width: usize,
1109 height: usize,
1110 block_cols: usize,
1111 blocks: Vec<CoefBlock>,
1115 hi_len: usize,
1118 quant_lo: [u32; 16],
1119 quant_hi: [u32; 10],
1120 curband: usize,
1122 ctx_decode_bucket: [u8; 1],
1124 ctx_decode_coef: [u8; 80],
1125 ctx_activate_coef: [u8; 16],
1126 ctx_increase_coef: [u8; 1],
1127 coeffstate: [[u8; 16]; 16],
1129 bucketstate: [u8; 16],
1130 bbstate: u8,
1131}
1132
1133#[allow(unsafe_code)]
1138#[inline(always)]
1139fn prelim_flags_bucket(coefs: &[i16; 16], bucket: &mut [u8; 16]) -> u8 {
1140 #[cfg(target_arch = "aarch64")]
1141 return unsafe { prelim_flags_bucket_neon(coefs, bucket) };
1143
1144 #[cfg(all(target_arch = "x86_64", feature = "std"))]
1145 {
1146 if std::is_x86_feature_detected!("avx2") {
1147 return unsafe { prelim_flags_bucket_avx2(coefs, bucket) };
1149 }
1150 }
1151
1152 #[cfg_attr(target_arch = "aarch64", allow(unreachable_code))]
1153 {
1154 let mut bstate = 0u8;
1155 for k in 0..16 {
1156 let f = if coefs[k] == 0 { UNK } else { ACTIVE };
1157 bucket[k] = f;
1158 bstate |= f;
1159 }
1160 bstate
1161 }
1162}
1163
1164#[cfg(target_arch = "aarch64")]
1169#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
1170#[target_feature(enable = "neon")]
1171unsafe fn prelim_flags_bucket_neon(coefs: &[i16; 16], bucket: &mut [u8; 16]) -> u8 {
1172 use core::arch::aarch64::*;
1173 let ptr = coefs.as_ptr();
1174 let c0 = vreinterpretq_u16_s16(vld1q_s16(ptr));
1176 let c1 = vreinterpretq_u16_s16(vld1q_s16(ptr.add(8)));
1177 let zero = vdupq_n_u16(0);
1179 let nz0 = vmvnq_u16(vceqq_u16(c0, zero));
1180 let nz1 = vmvnq_u16(vceqq_u16(c1, zero));
1181 let xv = vdupq_n_u16(10);
1184 let uv = vdupq_n_u16(8);
1185 let r0 = veorq_u16(uv, vandq_u16(xv, nz0));
1186 let r1 = veorq_u16(uv, vandq_u16(xv, nz1));
1187 let out = vcombine_u8(vmovn_u16(r0), vmovn_u16(r1));
1189 vst1q_u8(bucket.as_mut_ptr(), out);
1190 let lo = vget_low_u8(out);
1192 let hi = vget_high_u8(out);
1193 let v4 = vorr_u8(lo, hi);
1194 let v2 = vorr_u8(v4, vext_u8::<4>(v4, v4));
1195 let v1 = vorr_u8(v2, vext_u8::<2>(v2, v2));
1196 let v0 = vorr_u8(v1, vext_u8::<1>(v1, v1));
1197 vget_lane_u8::<0>(v0)
1198}
1199
1200#[cfg(all(target_arch = "x86_64", feature = "std"))]
1210#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
1211#[target_feature(enable = "avx2")]
1212unsafe fn prelim_flags_bucket_avx2(coefs: &[i16; 16], bucket: &mut [u8; 16]) -> u8 {
1213 use core::arch::x86_64::*;
1214 let coefs = _mm256_loadu_si256(coefs.as_ptr() as *const __m256i);
1216 let zero = _mm256_setzero_si256();
1218 let eq = _mm256_cmpeq_epi16(coefs, zero);
1219 let all_ones = _mm256_cmpeq_epi16(zero, zero);
1221 let nz = _mm256_xor_si256(eq, all_ones);
1222 let xv = _mm256_set1_epi16(10);
1224 let uv = _mm256_set1_epi16(8);
1225 let r16 = _mm256_xor_si256(uv, _mm256_and_si256(xv, nz));
1226 let r_lo = _mm256_castsi256_si128(r16);
1229 let r_hi = _mm256_extracti128_si256::<1>(r16);
1230 let packed = _mm_packus_epi16(r_lo, r_hi);
1231 _mm_storeu_si128(bucket.as_mut_ptr() as *mut __m128i, packed);
1232 let or64 = _mm_or_si128(packed, _mm_unpackhi_epi64(packed, packed));
1234 let or32 = _mm_or_si128(or64, _mm_srli_si128::<4>(or64));
1235 let or16_red = _mm_or_si128(or32, _mm_srli_si128::<2>(or32));
1236 let or8 = _mm_or_si128(or16_red, _mm_srli_si128::<1>(or16_red));
1237 _mm_extract_epi8::<0>(or8) as u8
1238}
1239
1240#[cfg(target_arch = "aarch64")]
1245#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
1246#[target_feature(enable = "neon")]
1247unsafe fn prelim_flags_band0_neon(block: &[i16; 16], old_flags: &mut [u8; 16]) -> u8 {
1248 use core::arch::aarch64::*;
1249 let old_u8 = vld1q_u8(old_flags.as_ptr());
1251 let one_u8 = vdupq_n_u8(1);
1253 let is_zero_state = vceqq_u8(old_u8, one_u8); let should_update = vmvnq_u8(is_zero_state); let ptr = block.as_ptr();
1257 let c0 = vreinterpretq_u16_s16(vld1q_s16(ptr));
1258 let c1 = vreinterpretq_u16_s16(vld1q_s16(ptr.add(8)));
1259 let zero16 = vdupq_n_u16(0);
1260 let nz0 = vmvnq_u16(vceqq_u16(c0, zero16));
1261 let nz1 = vmvnq_u16(vceqq_u16(c1, zero16));
1262 let xv = vdupq_n_u16(10); let uv = vdupq_n_u16(8); let r0 = veorq_u16(uv, vandq_u16(xv, nz0));
1265 let r1 = veorq_u16(uv, vandq_u16(xv, nz1));
1266 let new_flags = vcombine_u8(vmovn_u16(r0), vmovn_u16(r1));
1267 let result = vbslq_u8(should_update, new_flags, old_u8);
1269 vst1q_u8(old_flags.as_mut_ptr(), result);
1270 let lo = vget_low_u8(result);
1272 let hi = vget_high_u8(result);
1273 let v4 = vorr_u8(lo, hi);
1274 let v2 = vorr_u8(v4, vext_u8::<4>(v4, v4));
1275 let v1 = vorr_u8(v2, vext_u8::<2>(v2, v2));
1276 let v0 = vorr_u8(v1, vext_u8::<1>(v1, v1));
1277 vget_lane_u8::<0>(v0)
1278}
1279
1280#[cfg(all(target_arch = "x86_64", feature = "std"))]
1286#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
1287#[target_feature(enable = "avx2")]
1288unsafe fn prelim_flags_band0_avx2(block: &[i16; 16], old_flags: &mut [u8; 16]) -> u8 {
1289 use core::arch::x86_64::*;
1290 let old_u8 = _mm_loadu_si128(old_flags.as_ptr() as *const __m128i);
1292 let one_u8 = _mm_set1_epi8(1);
1294 let is_zero_state = _mm_cmpeq_epi8(old_u8, one_u8);
1295 let all_ones_128 = _mm_cmpeq_epi8(old_u8, old_u8);
1296 let should_update = _mm_xor_si128(is_zero_state, all_ones_128);
1297
1298 let coefs = _mm256_loadu_si256(block.as_ptr() as *const __m256i);
1300 let zero = _mm256_setzero_si256();
1301 let eq = _mm256_cmpeq_epi16(coefs, zero);
1302 let all_ones_256 = _mm256_cmpeq_epi16(zero, zero);
1303 let nz = _mm256_xor_si256(eq, all_ones_256);
1304 let xv = _mm256_set1_epi16(10);
1305 let uv = _mm256_set1_epi16(8);
1306 let r16 = _mm256_xor_si256(uv, _mm256_and_si256(xv, nz));
1307 let r_lo = _mm256_castsi256_si128(r16);
1308 let r_hi = _mm256_extracti128_si256::<1>(r16);
1309 let new_flags = _mm_packus_epi16(r_lo, r_hi);
1310
1311 let blended = _mm_or_si128(
1313 _mm_and_si128(should_update, new_flags),
1314 _mm_andnot_si128(should_update, old_u8),
1315 );
1316 _mm_storeu_si128(old_flags.as_mut_ptr() as *mut __m128i, blended);
1317
1318 let or64 = _mm_or_si128(blended, _mm_unpackhi_epi64(blended, blended));
1320 let or32 = _mm_or_si128(or64, _mm_srli_si128::<4>(or64));
1321 let or16_red = _mm_or_si128(or32, _mm_srli_si128::<2>(or32));
1322 let or8 = _mm_or_si128(or16_red, _mm_srli_si128::<1>(or16_red));
1323 _mm_extract_epi8::<0>(or8) as u8
1324}
1325
1326#[allow(unsafe_code)]
1330#[inline(always)]
1331fn band0_dispatch(block: &[i16; 16], old_flags: &mut [u8; 16]) -> u8 {
1332 #[cfg(target_arch = "aarch64")]
1333 return unsafe { prelim_flags_band0_neon(block, old_flags) };
1335
1336 #[cfg(all(target_arch = "x86_64", feature = "std"))]
1337 {
1338 if std::is_x86_feature_detected!("avx2") {
1339 return unsafe { prelim_flags_band0_avx2(block, old_flags) };
1341 }
1342 }
1343
1344 #[cfg_attr(target_arch = "aarch64", allow(unreachable_code))]
1345 {
1346 let mut b = 0u8;
1347 for k in 0..16 {
1348 if old_flags[k] != ZERO {
1349 old_flags[k] = if block[k] == 0 { UNK } else { ACTIVE };
1350 }
1351 b |= old_flags[k];
1352 }
1353 b
1354 }
1355}
1356
1357const ZERO_BUCKET: [i16; 16] = [0; 16];
1360
1361#[derive(Clone, Debug, Default)]
1379pub(crate) struct CoefBlock {
1380 lo: [i16; 16],
1382 hi: Vec<i16>,
1385}
1386
1387impl CoefBlock {
1388 #[inline(always)]
1390 pub(crate) fn coef(&self, i: usize) -> i16 {
1391 if i < 16 {
1392 self.lo[i]
1393 } else {
1394 self.hi.get(i - 16).copied().unwrap_or(0)
1395 }
1396 }
1397
1398 #[inline(always)]
1400 pub(crate) fn bucket(&self, b: usize) -> &[i16; 16] {
1401 if b == 0 {
1402 return &self.lo;
1403 }
1404 let off = (b - 1) * 16;
1405 match self.hi.get(off..off + 16) {
1406 Some(s) => s.try_into().expect("16-wide bucket slice"),
1408 None => &ZERO_BUCKET,
1409 }
1410 }
1411
1412 #[inline]
1418 pub(crate) fn grow_through(&mut self, top: usize) -> usize {
1419 let need = top * 16;
1420 if self.hi.len() >= need {
1421 return 0;
1422 }
1423 let grow = need - self.hi.len();
1424 self.hi.reserve_exact(grow);
1425 self.hi.resize(need, 0);
1426 grow
1427 }
1428
1429 #[inline]
1431 pub(crate) fn bucket_mut(&mut self, b: usize) -> &mut [i16; 16] {
1432 if b == 0 {
1433 return &mut self.lo;
1434 }
1435 let off = (b - 1) * 16;
1436 <&mut [i16; 16]>::try_from(&mut self.hi[off..off + 16]).expect("16-wide bucket slice")
1438 }
1439
1440 #[inline]
1443 pub(crate) fn bucket_mut_if_present(&mut self, b: usize) -> Option<&mut [i16; 16]> {
1444 if b == 0 {
1445 return Some(&mut self.lo);
1446 }
1447 let off = (b - 1) * 16;
1448 let s = self.hi.get_mut(off..off + 16)?;
1449 Some(<&mut [i16; 16]>::try_from(s).expect("16-wide bucket slice"))
1450 }
1451
1452 #[inline]
1458 fn materialize(&self, out: &mut [i16]) {
1459 let n = out.len();
1460 let lo = n.min(16);
1461 out[..lo].copy_from_slice(&self.lo[..lo]);
1462 if n > 16 {
1463 let hi = self.hi.len().min(n - 16);
1464 out[16..16 + hi].copy_from_slice(&self.hi[..hi]);
1465 out[16 + hi..].fill(0);
1466 }
1467 }
1468}
1469
1470impl PlaneDecoder {
1471 fn heap_bytes(&self) -> usize {
1474 self.blocks.capacity() * core::mem::size_of::<CoefBlock>()
1475 + self.hi_len * core::mem::size_of::<i16>()
1476 }
1477
1478 #[inline]
1482 fn bucket_mut(&mut self, block_idx: usize, b: usize) -> &mut [i16; 16] {
1483 let band_top = BAND_BUCKETS[self.curband].1;
1489 debug_assert!(
1490 b >= BAND_BUCKETS[self.curband].0 && b <= band_top,
1491 "bucket {b} is outside band {}",
1492 self.curband
1493 );
1494 let block = &mut self.blocks[block_idx];
1495 self.hi_len += block.grow_through(band_top);
1496 block.bucket_mut(b)
1497 }
1498
1499 fn new(width: usize, height: usize) -> Self {
1500 let block_cols = width.div_ceil(32);
1501 let block_rows = height.div_ceil(32);
1502 let block_count = block_cols * block_rows;
1503 PlaneDecoder {
1504 width,
1505 height,
1506 block_cols,
1507 blocks: vec![CoefBlock::default(); block_count],
1508 hi_len: 0,
1509 quant_lo: QUANT_LO_INIT,
1510 quant_hi: QUANT_HI_INIT,
1511 curband: 0,
1512 ctx_decode_bucket: [0; 1],
1513 ctx_decode_coef: [0; 80],
1514 ctx_activate_coef: [0; 16],
1515 ctx_increase_coef: [0; 1],
1516 coeffstate: [[0; 16]; 16],
1517 bucketstate: [0; 16],
1518 bbstate: 0,
1519 }
1520 }
1521
1522 fn decode_slice(&mut self, zp: &mut ZpDecoder<'_>) {
1524 if !self.is_null_slice() {
1525 for block_idx in 0..self.blocks.len() {
1526 self.preliminary_flag_computation(block_idx);
1527 if self.block_band_decoding_pass(zp) && self.bucket_decoding_pass(zp, block_idx) {
1528 self.newly_active_coefficient_decoding_pass(zp, block_idx);
1529 }
1530 if (self.bbstate & ACTIVE) != 0 {
1533 self.previously_active_coefficient_decoding_pass(zp, block_idx);
1534 }
1535 }
1536 }
1537 self.finish_slice();
1538 }
1539
1540 fn is_null_slice(&mut self) -> bool {
1541 if self.curband == 0 {
1542 let mut is_null = true;
1543 for i in 0..16 {
1544 let threshold = self.quant_lo[i];
1545 self.coeffstate[0][i] = ZERO;
1546 if threshold > 0 && threshold < 0x8000 {
1547 self.coeffstate[0][i] = UNK;
1548 is_null = false;
1549 }
1550 }
1551 is_null
1552 } else {
1553 let threshold = self.quant_hi[self.curband];
1554 !(threshold > 0 && threshold < 0x8000)
1555 }
1556 }
1557
1558 fn preliminary_flag_computation(&mut self, block_idx: usize) {
1559 self.bbstate = 0;
1560 let (from, to) = BAND_BUCKETS[self.curband];
1561
1562 if self.curband != 0 {
1563 let hi = &self.blocks[block_idx].hi[..];
1568 for (boff, j) in (from..=to).enumerate() {
1569 let off = (j - 1) * 16;
1570 let coefs = match hi.get(off..off + 16) {
1571 Some(s) => <&[i16; 16]>::try_from(s).expect("16-wide bucket slice"),
1572 None => &ZERO_BUCKET,
1573 };
1574 let bstatetmp = prelim_flags_bucket(coefs, &mut self.coeffstate[boff]);
1575 self.bucketstate[boff] = bstatetmp;
1576 self.bbstate |= bstatetmp;
1577 }
1578 } else {
1579 let bstatetmp =
1580 band0_dispatch(self.blocks[block_idx].bucket(0), &mut self.coeffstate[0]);
1581 self.bucketstate[0] = bstatetmp;
1582 self.bbstate |= bstatetmp;
1583 }
1584 }
1585
1586 fn block_band_decoding_pass(&mut self, zp: &mut ZpDecoder<'_>) -> bool {
1587 let (from, to) = BAND_BUCKETS[self.curband];
1588 let bcount = to - from + 1;
1589 let should_mark_new = bcount < 16
1590 || (self.bbstate & ACTIVE) != 0
1591 || ((self.bbstate & UNK) != 0 && zp.decode_bit(&mut self.ctx_decode_bucket[0]));
1592 if should_mark_new {
1593 self.bbstate |= NEW;
1594 }
1595 (self.bbstate & NEW) != 0
1596 }
1597
1598 fn bucket_decoding_pass(&mut self, zp: &mut ZpDecoder<'_>, block_idx: usize) -> bool {
1600 let (from, to) = BAND_BUCKETS[self.curband];
1601 let mut any_new = false;
1602 for (boff, i) in (from..=to).enumerate() {
1603 if (self.bucketstate[boff] & UNK) == 0 {
1604 continue;
1605 }
1606 let mut n: usize = 0;
1607 if self.curband != 0 {
1608 let t = 4 * i;
1609 for j in t..t + 4 {
1610 if self.blocks[block_idx].coef(j) != 0 {
1611 n += 1;
1612 }
1613 }
1614 if n == 4 {
1615 n = 3;
1616 }
1617 }
1618 if (self.bbstate & ACTIVE) != 0 {
1619 n |= 4;
1620 }
1621 if zp.decode_bit(&mut self.ctx_decode_coef[n + self.curband * 8]) {
1622 self.bucketstate[boff] |= NEW;
1623 any_new = true;
1624 }
1625 }
1626 any_new
1627 }
1628
1629 fn newly_active_coefficient_decoding_pass(&mut self, zp: &mut ZpDecoder<'_>, block_idx: usize) {
1630 let (from, to) = BAND_BUCKETS[self.curband];
1631 let mut step = self.quant_hi[self.curband];
1632 for (boff, i) in (from..=to).enumerate() {
1633 if (self.bucketstate[boff] & NEW) != 0 {
1634 let shift: usize = if (self.bucketstate[boff] & ACTIVE) != 0 {
1635 8
1636 } else {
1637 0
1638 };
1639 let mut np: usize = 0;
1640 for j in 0..16 {
1641 if (self.coeffstate[boff][j] & UNK) != 0 {
1642 np += 1;
1643 }
1644 }
1645 for j in 0..16 {
1646 if (self.coeffstate[boff][j] & UNK) != 0 {
1647 let ip = np.min(7);
1648 if zp.decode_bit(&mut self.ctx_activate_coef[shift + ip]) {
1649 let sign = if zp.decode_passthrough_iw44() {
1650 -1i32
1651 } else {
1652 1i32
1653 };
1654 np = 0;
1655 if self.curband == 0 {
1656 step = self.quant_lo[j];
1657 }
1658 let s = step as i32;
1659 let val = sign * (s + (s >> 1) - (s >> 3));
1660 self.bucket_mut(block_idx, i)[j] = val as i16;
1661 }
1662 np = np.saturating_sub(1);
1663 }
1664 }
1665 }
1666 }
1667 }
1668
1669 #[inline(never)]
1675 fn previously_active_coefficient_decoding_pass(
1676 &mut self,
1677 zp: &mut ZpDecoder<'_>,
1678 block_idx: usize,
1679 ) {
1680 use djvu_zp::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
1681
1682 let mut a = zp.a;
1684 let mut c = zp.c;
1685 let mut fence = zp.fence;
1686 let mut bit_buf = zp.bit_buf;
1687 let mut bit_count = zp.bit_count;
1688 let data = zp.data;
1689 let mut pos = zp.pos;
1690
1691 macro_rules! read_byte {
1692 () => {{
1693 let b = if pos < data.len() { data[pos] } else { 0xff };
1694 pos = pos.wrapping_add(1);
1695 b as u32
1696 }};
1697 }
1698 macro_rules! refill {
1699 () => {
1700 while bit_count <= 24 {
1701 bit_buf = (bit_buf << 8) | read_byte!();
1702 bit_count += 8;
1703 }
1704 };
1705 }
1706 macro_rules! renorm {
1707 () => {{
1708 let shift = (a as u16).leading_ones();
1709 bit_count -= shift as i32;
1710 a = (a << shift) & 0xffff;
1711 let mask = (1u32 << (shift & 31)).wrapping_sub(1);
1712 c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
1713 if bit_count < 16 {
1714 refill!();
1715 }
1716 fence = c.min(0x7fff);
1717 }};
1718 }
1719 macro_rules! decode_bit_ctx {
1721 ($ctx:expr) => {{
1722 let state = ($ctx) as usize;
1723 let mps_bit = state & 1;
1724 let z = a + PROB[state] as u32;
1725 if z <= fence {
1726 a = z;
1727 mps_bit != 0
1728 } else {
1729 let boundary = 0x6000u32 + ((a + z) >> 2);
1730 let z_clamped = z.min(boundary);
1731 if z_clamped > c {
1732 let complement = 0x10000u32 - z_clamped;
1733 a = (a + complement) & 0xffff;
1734 c = (c + complement) & 0xffff;
1735 $ctx = LPS_NEXT[state];
1736 renorm!();
1737 (1 - mps_bit) != 0
1738 } else {
1739 if a >= THRESHOLD[state] as u32 {
1740 $ctx = MPS_NEXT[state];
1741 }
1742 bit_count -= 1;
1743 a = (z_clamped << 1) & 0xffff;
1744 c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
1745 if bit_count < 16 {
1746 refill!();
1747 }
1748 fence = c.min(0x7fff);
1749 mps_bit != 0
1750 }
1751 }
1752 }};
1753 }
1754 macro_rules! decode_passthrough_iw44 {
1756 () => {{
1757 let z = (0x8000u32 + (3u32 * a) / 8) as u16;
1758 if z as u32 > c {
1759 let complement = 0x10000u32 - z as u32;
1760 a = (a + complement) & 0xffff;
1761 c = (c + complement) & 0xffff;
1762 renorm!();
1763 true
1764 } else {
1765 bit_count -= 1;
1766 a = (z as u32 * 2) & 0xffff;
1767 c = (c << 1 | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
1768 if bit_count < 16 {
1769 refill!();
1770 }
1771 fence = c.min(0x7fff);
1772 false
1773 }
1774 }};
1775 }
1776
1777 let (from, to) = BAND_BUCKETS[self.curband];
1778 let mut step = self.quant_hi[self.curband];
1779 for (boff, i) in (from..=to).enumerate() {
1780 let bucket = match self.blocks[block_idx].bucket_mut_if_present(i) {
1786 Some(b) => b,
1787 None => continue,
1788 };
1789 let flags = &self.coeffstate[boff];
1790 for (j, slot) in bucket.iter_mut().enumerate() {
1791 if (flags[j] & ACTIVE) != 0 {
1792 if self.curband == 0 {
1793 step = self.quant_lo[j];
1794 }
1795 let coef = *slot;
1796 let mut abs_coef = coef.unsigned_abs() as i32;
1797 let s = step as i32;
1798 let des = if abs_coef <= 3 * s {
1799 let d = decode_bit_ctx!(self.ctx_increase_coef[0]);
1800 abs_coef += s >> 2;
1801 d
1802 } else {
1803 decode_passthrough_iw44!()
1804 };
1805 if des {
1806 abs_coef += s >> 1;
1807 } else {
1808 abs_coef += -s + (s >> 1);
1809 }
1810 *slot = if coef < 0 {
1811 -abs_coef as i16
1812 } else {
1813 abs_coef as i16
1814 };
1815 }
1816 }
1817 }
1818
1819 zp.a = a;
1821 zp.c = c;
1822 zp.fence = fence;
1823 zp.bit_buf = bit_buf;
1824 zp.bit_count = bit_count;
1825 zp.pos = pos;
1826 }
1827
1828 fn finish_slice(&mut self) {
1830 self.quant_hi[self.curband] >>= 1;
1831 if self.curband == 0 {
1832 for i in 0..16 {
1833 self.quant_lo[i] >>= 1;
1834 }
1835 }
1836 self.curband += 1;
1837 if self.curband == 10 {
1838 self.curband = 0;
1839 }
1840 }
1841
1842 fn reconstruct(&self, subsample: usize) -> FlatPlane {
1847 if (2..=8).contains(&subsample) && subsample.is_power_of_two() {
1864 let sub = subsample;
1865
1866 let block_rows = self.height.div_ceil(32);
1869 let sub_block = 32 / sub; let compact_stride = self.block_cols * sub_block;
1873 let compact_rows = block_rows * sub_block;
1874 let compact_w = self.width.div_ceil(sub);
1876 let compact_h = self.height.div_ceil(sub);
1877
1878 #[allow(unsafe_code)]
1883 let mut plane = FlatPlane {
1884 data: unsafe { uninit_i16_vec(compact_stride * compact_rows) },
1885 stride: compact_stride,
1886 };
1887
1888 let compact_inv: &[u8] = match sub {
1897 2 => &ZIGZAG_INV_SUB2,
1898 4 => &ZIGZAG_INV_SUB4,
1899 _ => &ZIGZAG_INV_SUB8, };
1901 let mut prefix = [0i16; 256];
1906 #[allow(unsafe_code)]
1907 for r in 0..block_rows {
1908 for c in 0..self.block_cols {
1909 let block = &self.blocks[r * self.block_cols + c];
1910 block.materialize(&mut prefix[..sub_block * sub_block]);
1911 let base_row = r * sub_block;
1912 let base_col = c * sub_block;
1913 for row in 0..sub_block {
1914 let dst_base = (base_row + row) * compact_stride + base_col;
1915 let inv_base = row * sub_block;
1916 for col in 0..sub_block {
1917 let i = unsafe { *compact_inv.get_unchecked(inv_base + col) } as usize;
1919 unsafe {
1920 *plane.data.get_unchecked_mut(dst_base + col) =
1921 *prefix.get_unchecked(i);
1922 }
1923 }
1924 }
1925 }
1926 }
1927
1928 let start_scale = 16 / sub;
1933 inverse_wavelet_transform_from(&mut plane, compact_w, compact_h, 1, start_scale);
1934 return plane;
1935 }
1936
1937 let full_width = self.width.div_ceil(32) * 32;
1939 let full_height = self.height.div_ceil(32) * 32;
1940 let block_rows = self.height.div_ceil(32);
1941 #[allow(unsafe_code)]
1945 let mut plane = FlatPlane {
1946 data: unsafe { uninit_i16_vec(full_width * full_height) },
1947 stride: full_width,
1948 };
1949
1950 let mut full = [0i16; 1024];
1955 for r in 0..block_rows {
1956 for c in 0..self.block_cols {
1957 self.blocks[r * self.block_cols + c].materialize(&mut full);
1958 let row_base = r << 5;
1959 let col_base = c << 5;
1960 for row in 0..32usize {
1961 let dst_base = (row_base + row) * full_width + col_base;
1962 let inv_base = row * 32;
1963 for col in 0..32usize {
1964 let i = ZIGZAG_INV[inv_base + col] as usize;
1965 plane.data[dst_base + col] = full[i];
1966 }
1967 }
1968 }
1969 }
1970
1971 inverse_wavelet_transform(&mut plane, self.width, self.height, subsample);
1972 plane
1973 }
1974
1975 fn reconstruct_band(&self, first_block: usize, last_block: usize) -> FlatPlane {
1993 debug_assert!(first_block < last_block);
1994 let block_rows = self.height.div_ceil(32);
1995 let last_block = last_block.min(block_rows);
1996 let full_width = self.width.div_ceil(32) * 32;
1997 let band_rows = (last_block - first_block) * 32;
1998
1999 #[allow(unsafe_code)]
2002 let mut plane = FlatPlane {
2003 data: unsafe { uninit_i16_vec(full_width * band_rows) },
2004 stride: full_width,
2005 };
2006
2007 let mut full = [0i16; 1024];
2008 for r in first_block..last_block {
2009 for c in 0..self.block_cols {
2010 self.blocks[r * self.block_cols + c].materialize(&mut full);
2011 let row_base = (r - first_block) << 5;
2012 let col_base = c << 5;
2013 for row in 0..32usize {
2014 let dst_base = (row_base + row) * full_width + col_base;
2015 let inv_base = row * 32;
2016 for col in 0..32usize {
2017 let i = ZIGZAG_INV[inv_base + col] as usize;
2018 plane.data[dst_base + col] = full[i];
2019 }
2020 }
2021 }
2022 }
2023
2024 let logical = if last_block == block_rows {
2028 self.height - first_block * 32
2029 } else {
2030 band_rows
2031 };
2032 inverse_wavelet_transform(&mut plane, self.width, logical, 1);
2033 plane
2034 }
2035}
2036
2037fn reconstruct_planes(
2044 y_dec: &PlaneDecoder,
2045 cb_dec: &PlaneDecoder,
2046 cr_dec: &PlaneDecoder,
2047 sub: usize,
2048 chroma_sub: usize,
2049) -> (FlatPlane, FlatPlane, FlatPlane) {
2050 #[cfg(feature = "parallel")]
2051 {
2052 let (y, (cb, cr)) = rayon::join(
2053 || y_dec.reconstruct(sub),
2054 || {
2055 rayon::join(
2056 || cb_dec.reconstruct(chroma_sub),
2057 || cr_dec.reconstruct(chroma_sub),
2058 )
2059 },
2060 );
2061 (y, cb, cr)
2062 }
2063 #[cfg(not(feature = "parallel"))]
2064 {
2065 (
2066 y_dec.reconstruct(sub),
2067 cb_dec.reconstruct(chroma_sub),
2068 cr_dec.reconstruct(chroma_sub),
2069 )
2070 }
2071}
2072
2073#[allow(clippy::too_many_arguments)]
2081fn convert_rgb_rows(
2082 chroma_half: bool,
2083 y: &FlatPlane,
2084 y_row0: usize,
2085 cb: &FlatPlane,
2086 cr: &FlatPlane,
2087 c_row0: usize,
2088 rows: core::ops::Range<usize>,
2089 pw: usize,
2090 ph: usize,
2091 out: &mut [u8],
2092) {
2093 let cw = pw.div_ceil(2);
2095 let ch = ph.div_ceil(2);
2096 let out_lo = ph - rows.end;
2097 debug_assert_eq!(out.len(), rows.len() * pw * 4);
2098
2099 #[cfg(feature = "parallel")]
2100 {
2101 use rayon::prelude::*;
2102 out.par_chunks_mut(pw * 4)
2103 .enumerate()
2104 .for_each(|(i, row_data)| {
2105 let row = ph - 1 - (out_lo + i); let y_off = (row - y_row0) * y.stride;
2107 if chroma_half {
2108 let c0 = row / 2;
2109 let c1 = (c0 + 1).min(ch - 1);
2110 let v_blend = row & 1 == 1;
2111 let mut cb_full = vec![0i16; pw];
2112 let mut cr_full = vec![0i16; pw];
2113 upsample_chroma_row_bilinear(
2114 &cb.data[(c0 - c_row0) * cb.stride..],
2115 &cb.data[(c1 - c_row0) * cb.stride..],
2116 v_blend,
2117 &mut cb_full,
2118 cw,
2119 );
2120 upsample_chroma_row_bilinear(
2121 &cr.data[(c0 - c_row0) * cr.stride..],
2122 &cr.data[(c1 - c_row0) * cr.stride..],
2123 v_blend,
2124 &mut cr_full,
2125 cw,
2126 );
2127 ycbcr_row_from_i16(&y.data[y_off..y_off + pw], &cb_full, &cr_full, row_data);
2128 } else {
2129 let c_off = (row - c_row0) * cb.stride;
2130 ycbcr_row_from_i16(
2131 &y.data[y_off..y_off + pw],
2132 &cb.data[c_off..c_off + pw],
2133 &cr.data[c_off..c_off + pw],
2134 row_data,
2135 );
2136 }
2137 });
2138 }
2139 #[cfg(not(feature = "parallel"))]
2140 {
2141 let mut cb_full = vec![0i16; pw];
2142 let mut cr_full = vec![0i16; pw];
2143 for (i, row_data) in out.chunks_mut(pw * 4).enumerate() {
2144 let row = ph - 1 - (out_lo + i); let y_off = (row - y_row0) * y.stride;
2146 if chroma_half {
2147 let c0 = row / 2;
2148 let c1 = (c0 + 1).min(ch - 1);
2149 let v_blend = row & 1 == 1;
2150 upsample_chroma_row_bilinear(
2151 &cb.data[(c0 - c_row0) * cb.stride..],
2152 &cb.data[(c1 - c_row0) * cb.stride..],
2153 v_blend,
2154 &mut cb_full,
2155 cw,
2156 );
2157 upsample_chroma_row_bilinear(
2158 &cr.data[(c0 - c_row0) * cr.stride..],
2159 &cr.data[(c1 - c_row0) * cr.stride..],
2160 v_blend,
2161 &mut cr_full,
2162 cw,
2163 );
2164 ycbcr_row_from_i16(&y.data[y_off..y_off + pw], &cb_full, &cr_full, row_data);
2165 } else {
2166 let c_off = (row - c_row0) * cb.stride;
2167 ycbcr_row_from_i16(
2168 &y.data[y_off..y_off + pw],
2169 &cb.data[c_off..c_off + pw],
2170 &cr.data[c_off..c_off + pw],
2171 row_data,
2172 );
2173 }
2174 }
2175 }
2176}
2177
2178const BAND_MIN_PLANE_BYTES: usize = 128 * 1024 * 1024;
2181
2182const BAND_BUDGET_BYTES: usize = 128 * 1024 * 1024;
2185
2186const BAND_MIN_KEEP_BLOCKS: usize = 4 * BAND_HALO_BLOCKS;
2189
2190fn band_keep_blocks(
2203 y_dec: &PlaneDecoder,
2204 chroma_half: bool,
2205 out_bytes_per_px: usize,
2206) -> Option<usize> {
2207 let stride = y_dec.width.div_ceil(32) * 32;
2208 let per_row = if chroma_half { stride * 3 } else { stride * 6 };
2212 let block_rows = y_dec.height.div_ceil(32);
2213 if per_row.saturating_mul(block_rows * 32) <= BAND_MIN_PLANE_BYTES {
2214 return None;
2215 }
2216 let per_block_row = per_row * 32;
2217 let halo_bytes = 2 * BAND_HALO_BLOCKS * per_block_row;
2220 let per_kept_block_row = per_block_row + stride * 32 * out_bytes_per_px;
2221 let affordable = BAND_BUDGET_BYTES.saturating_sub(halo_bytes) / per_kept_block_row;
2222 let keep = affordable.max(BAND_MIN_KEEP_BLOCKS);
2223 (keep * 2 <= block_rows).then_some(keep)
2228}
2229
2230const BAND_HALO_BLOCKS: usize = 8;
2235
2236#[allow(unsafe_code)]
2246unsafe fn uninit_i16_vec(n: usize) -> Vec<i16> {
2247 use core::mem::MaybeUninit;
2248 let mut v: Vec<MaybeUninit<i16>> = Vec::with_capacity(n);
2249 unsafe { v.set_len(n) };
2251 let mut md = core::mem::ManuallyDrop::new(v);
2252 unsafe { Vec::from_raw_parts(md.as_mut_ptr().cast::<i16>(), md.len(), md.capacity()) }
2254}
2255
2256struct FlatPlane {
2257 data: Vec<i16>,
2258 stride: usize,
2259}
2260
2261use wide::i32x8;
2273
2274#[inline(always)]
2281fn load8s(slice: &[i16], phys_off: usize, s: usize) -> i32x8 {
2282 if s == 1 {
2286 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
2291 {
2292 #[allow(unsafe_code)]
2293 return unsafe { load8s_s1_avx2(slice, phys_off) };
2294 }
2295 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
2300 {
2301 #[allow(unsafe_code)]
2302 return unsafe { load8s_s1_simd128(slice, phys_off) };
2303 }
2304 #[allow(unsafe_code, unreachable_code)]
2305 return unsafe {
2306 let arr: [i16; 8] = core::ptr::read(slice.as_ptr().add(phys_off) as *const [i16; 8]);
2308 i32x8::from([
2309 arr[0] as i32,
2310 arr[1] as i32,
2311 arr[2] as i32,
2312 arr[3] as i32,
2313 arr[4] as i32,
2314 arr[5] as i32,
2315 arr[6] as i32,
2316 arr[7] as i32,
2317 ])
2318 };
2319 }
2320 #[cfg(target_arch = "aarch64")]
2321 if s == 2 || s == 4 {
2322 #[allow(unsafe_code)]
2323 return unsafe { load8s_neon(slice, phys_off, s) };
2324 }
2325 i32x8::from([
2326 slice[phys_off] as i32,
2327 slice[phys_off + s] as i32,
2328 slice[phys_off + 2 * s] as i32,
2329 slice[phys_off + 3 * s] as i32,
2330 slice[phys_off + 4 * s] as i32,
2331 slice[phys_off + 5 * s] as i32,
2332 slice[phys_off + 6 * s] as i32,
2333 slice[phys_off + 7 * s] as i32,
2334 ])
2335}
2336
2337#[inline(always)]
2342fn store8s(slice: &mut [i16], phys_off: usize, s: usize, v: i32x8) {
2343 if s == 1 {
2345 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
2346 {
2347 #[allow(unsafe_code)]
2348 return unsafe { store8s_s1_avx2(slice, phys_off, v) };
2349 }
2350 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
2356 {
2357 #[allow(unsafe_code)]
2358 return unsafe { store8s_s1_simd128(slice, phys_off, v) };
2359 }
2360 #[allow(unsafe_code, unreachable_code)]
2361 return unsafe {
2362 let a = v.to_array();
2364 let narrow: [i16; 8] = [
2365 a[0] as i16,
2366 a[1] as i16,
2367 a[2] as i16,
2368 a[3] as i16,
2369 a[4] as i16,
2370 a[5] as i16,
2371 a[6] as i16,
2372 a[7] as i16,
2373 ];
2374 core::ptr::write(slice.as_mut_ptr().add(phys_off) as *mut [i16; 8], narrow);
2375 };
2376 }
2377 #[cfg(target_arch = "aarch64")]
2378 if s == 2 || s == 4 {
2379 #[allow(unsafe_code)]
2380 return unsafe { store8s_neon(slice, phys_off, s, v) };
2381 }
2382 let a = v.to_array();
2383 for j in 0..8 {
2384 slice[phys_off + j * s] = a[j] as i16;
2385 }
2386}
2387
2388#[cfg(target_arch = "aarch64")]
2397#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
2400#[target_feature(enable = "neon")]
2401unsafe fn load8s_neon(slice: &[i16], phys_off: usize, s: usize) -> i32x8 {
2402 use core::arch::aarch64::*;
2403 let ptr = slice.as_ptr().add(phys_off);
2404 let target: int16x8_t = if s == 2 {
2405 vld2q_s16(ptr).0
2406 } else {
2407 vld4q_s16(ptr).0
2409 };
2410 let lo = vmovl_s16(vget_low_s16(target));
2412 let hi = vmovl_high_s16(target);
2413 let arr = core::mem::transmute::<[int32x4_t; 2], [i32; 8]>([lo, hi]);
2414 i32x8::from(arr)
2415}
2416
2417#[cfg(all(target_arch = "x86_64", feature = "std"))]
2430#[allow(unsafe_code, unsafe_op_in_unsafe_fn, dead_code)]
2431#[target_feature(enable = "avx2")]
2432#[inline]
2433unsafe fn load8s_s1_avx2(slice: &[i16], phys_off: usize) -> i32x8 {
2434 use core::arch::x86_64::*;
2435 let ptr = slice.as_ptr().add(phys_off) as *const __m128i;
2436 let v16 = _mm_loadu_si128(ptr);
2437 let v32 = _mm256_cvtepi16_epi32(v16);
2438 let arr: [i32; 8] = core::mem::transmute(v32);
2439 i32x8::from(arr)
2440}
2441
2442#[cfg(all(target_arch = "x86_64", feature = "std"))]
2443#[allow(unsafe_code, unsafe_op_in_unsafe_fn, dead_code)]
2444#[target_feature(enable = "avx2")]
2445#[inline]
2446unsafe fn store8s_s1_avx2(slice: &mut [i16], phys_off: usize, v: i32x8) {
2447 use core::arch::x86_64::*;
2448 let arr: [i32; 8] = v.to_array();
2449 let v32: __m256i = core::mem::transmute(arr);
2450 let shuf = _mm256_setr_epi8(
2454 0, 1, 4, 5, 8, 9, 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 4, 5, 8, 9, 12, 13, -1, -1,
2455 -1, -1, -1, -1, -1, -1,
2456 );
2457 let shuffled = _mm256_shuffle_epi8(v32, shuf);
2458 let permuted = _mm256_permute4x64_epi64::<0b00_00_10_00>(shuffled);
2462 let result = _mm256_castsi256_si128(permuted);
2463 let ptr = slice.as_mut_ptr().add(phys_off) as *mut __m128i;
2464 _mm_storeu_si128(ptr, result);
2465}
2466
2467#[cfg(target_arch = "aarch64")]
2468#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
2469#[target_feature(enable = "neon")]
2470unsafe fn store8s_neon(slice: &mut [i16], phys_off: usize, s: usize, v: i32x8) {
2471 use core::arch::aarch64::*;
2472 let ptr = slice.as_mut_ptr().add(phys_off);
2473 let v_arr = core::mem::transmute::<[i32; 8], [int32x4_t; 2]>(v.to_array());
2475 let new_vals = vcombine_s16(vmovn_s32(v_arr[0]), vmovn_s32(v_arr[1]));
2476 let a: [i16; 8] = core::mem::transmute(new_vals);
2481 for (j, &val) in a.iter().enumerate() {
2482 *ptr.add(j * s) = val;
2483 }
2484}
2485
2486#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
2504#[allow(unsafe_code, unsafe_op_in_unsafe_fn, dead_code)]
2505#[target_feature(enable = "simd128")]
2506#[inline]
2507unsafe fn load8s_s1_simd128(slice: &[i16], phys_off: usize) -> i32x8 {
2508 use core::arch::wasm32::*;
2509 let v16 = v128_load(slice.as_ptr().add(phys_off) as *const v128);
2511 let lo = i32x4_extend_low_i16x8(v16);
2513 let hi = i32x4_extend_high_i16x8(v16);
2514 core::mem::transmute::<[v128; 2], i32x8>([lo, hi])
2517}
2518
2519#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
2520#[allow(unsafe_code, unsafe_op_in_unsafe_fn, dead_code)]
2521#[target_feature(enable = "simd128")]
2522#[inline]
2523unsafe fn store8s_s1_simd128(slice: &mut [i16], phys_off: usize, v: i32x8) {
2524 use core::arch::wasm32::*;
2525 let [lo, hi]: [v128; 2] = core::mem::transmute(v);
2527 let out = i8x16_shuffle::<0, 1, 4, 5, 8, 9, 12, 13, 16, 17, 20, 21, 24, 25, 28, 29>(lo, hi);
2532 v128_store(slice.as_mut_ptr().add(phys_off) as *mut v128, out);
2533}
2534
2535#[inline(always)]
2540#[allow(unsafe_code)]
2541fn load8_i32(slice: &[i32], off: usize) -> i32x8 {
2542 unsafe {
2544 i32x8::from([
2545 *slice.get_unchecked(off),
2546 *slice.get_unchecked(off + 1),
2547 *slice.get_unchecked(off + 2),
2548 *slice.get_unchecked(off + 3),
2549 *slice.get_unchecked(off + 4),
2550 *slice.get_unchecked(off + 5),
2551 *slice.get_unchecked(off + 6),
2552 *slice.get_unchecked(off + 7),
2553 ])
2554 }
2555}
2556
2557#[inline(always)]
2562#[allow(unsafe_code)]
2563fn store8_i32(slice: &mut [i32], off: usize, v: i32x8) {
2564 let a = v.to_array();
2565 unsafe {
2567 *slice.get_unchecked_mut(off) = a[0];
2568 *slice.get_unchecked_mut(off + 1) = a[1];
2569 *slice.get_unchecked_mut(off + 2) = a[2];
2570 *slice.get_unchecked_mut(off + 3) = a[3];
2571 *slice.get_unchecked_mut(off + 4) = a[4];
2572 *slice.get_unchecked_mut(off + 5) = a[5];
2573 *slice.get_unchecked_mut(off + 6) = a[6];
2574 *slice.get_unchecked_mut(off + 7) = a[7];
2575 }
2576}
2577
2578#[inline(always)]
2585#[allow(unsafe_code)]
2586fn load_rows8(data: &[i16], offs: &[usize; 8], k: usize) -> i32x8 {
2587 unsafe {
2589 i32x8::from([
2590 *data.get_unchecked(offs[0] + k) as i32,
2591 *data.get_unchecked(offs[1] + k) as i32,
2592 *data.get_unchecked(offs[2] + k) as i32,
2593 *data.get_unchecked(offs[3] + k) as i32,
2594 *data.get_unchecked(offs[4] + k) as i32,
2595 *data.get_unchecked(offs[5] + k) as i32,
2596 *data.get_unchecked(offs[6] + k) as i32,
2597 *data.get_unchecked(offs[7] + k) as i32,
2598 ])
2599 }
2600}
2601
2602#[inline(always)]
2607#[allow(unsafe_code)]
2608fn store_rows8(data: &mut [i16], offs: &[usize; 8], k: usize, v: i32x8) {
2609 let a = v.to_array();
2610 unsafe {
2612 *data.get_unchecked_mut(offs[0] + k) = a[0] as i16;
2613 *data.get_unchecked_mut(offs[1] + k) = a[1] as i16;
2614 *data.get_unchecked_mut(offs[2] + k) = a[2] as i16;
2615 *data.get_unchecked_mut(offs[3] + k) = a[3] as i16;
2616 *data.get_unchecked_mut(offs[4] + k) = a[4] as i16;
2617 *data.get_unchecked_mut(offs[5] + k) = a[5] as i16;
2618 *data.get_unchecked_mut(offs[6] + k) = a[6] as i16;
2619 *data.get_unchecked_mut(offs[7] + k) = a[7] as i16;
2620 }
2621}
2622
2623#[allow(unsafe_code)]
2628const C16: i32x8 = unsafe { core::mem::transmute([16i32; 8]) };
2629#[allow(unsafe_code)]
2630const C8: i32x8 = unsafe { core::mem::transmute([8i32; 8]) };
2631#[allow(unsafe_code)]
2632const C1: i32x8 = unsafe { core::mem::transmute([1i32; 8]) };
2633
2634#[inline(always)]
2636fn lifting_even(cur: i32x8, p1: i32x8, n1: i32x8, p3: i32x8, n3: i32x8) -> i32x8 {
2637 let a = p1 + n1;
2638 let c = p3 + n3;
2639 cur - (((a << 3) + a - c + C16) >> 5)
2640}
2641
2642#[inline(always)]
2644fn predict_inner(cur: i32x8, p1: i32x8, n1: i32x8, p3: i32x8, n3: i32x8) -> i32x8 {
2645 let a = p1 + n1;
2646 cur + (((a << 3) + a - (p3 + n3) + C8) >> 4)
2647}
2648
2649#[inline(always)]
2651fn predict_avg(cur: i32x8, p: i32x8, n: i32x8) -> i32x8 {
2652 cur + ((p + n + C1) >> 1)
2653}
2654
2655#[cfg(target_arch = "aarch64")]
2686#[allow(unsafe_code, unsafe_op_in_unsafe_fn)]
2687#[target_feature(enable = "neon")]
2688unsafe fn row_pass_neon_s1_row(data: &mut [i16], row_off: usize, width: usize) {
2689 use core::arch::aarch64::*;
2690
2691 let kmax = width - 1;
2692 let border = kmax.saturating_sub(3);
2693 let ptr = data.as_mut_ptr().add(row_off);
2694
2695 let even_chunks = if width >= 32 { (width - 31) / 16 } else { 0 };
2698
2699 let mut prev_odd = vdupq_n_s16(0i16);
2702
2703 for chunk in 0..even_chunks {
2704 let curr_pair = vld2q_s16(ptr.add(chunk * 16) as *const i16);
2705 let next_pair = vld2q_s16(ptr.add((chunk + 1) * 16) as *const i16);
2706 let curr_even = curr_pair.0;
2707 let curr_odd = curr_pair.1;
2708 let next_odd = next_pair.1;
2709
2710 let p1 = vextq_s16::<7>(prev_odd, curr_odd);
2711 let n1 = curr_odd;
2712 let p3 = vextq_s16::<6>(prev_odd, curr_odd);
2713 let n3 = vextq_s16::<1>(curr_odd, next_odd);
2714
2715 macro_rules! lift {
2717 ($ce:expr, $p1:expr, $n1:expr, $p3:expr, $n3:expr) => {{
2718 let a = vaddq_s32($p1, $n1);
2719 let c = vaddq_s32($p3, $n3);
2720 let nine_a = vaddq_s32(vshlq_n_s32::<3>(a), a);
2721 let delta = vshrq_n_s32::<5>(vsubq_s32(vaddq_s32(nine_a, vdupq_n_s32(16i32)), c));
2722 vsubq_s32($ce, delta)
2723 }};
2724 }
2725
2726 let new_lo = lift!(
2727 vmovl_s16(vget_low_s16(curr_even)),
2728 vmovl_s16(vget_low_s16(p1)),
2729 vmovl_s16(vget_low_s16(n1)),
2730 vmovl_s16(vget_low_s16(p3)),
2731 vmovl_s16(vget_low_s16(n3))
2732 );
2733 let new_hi = lift!(
2734 vmovl_high_s16(curr_even),
2735 vmovl_high_s16(p1),
2736 vmovl_high_s16(n1),
2737 vmovl_high_s16(p3),
2738 vmovl_high_s16(n3)
2739 );
2740 let new_evens = vcombine_s16(vmovn_s32(new_lo), vmovn_s32(new_hi));
2741
2742 vst2q_s16(ptr.add(chunk * 16), int16x8x2_t(new_evens, curr_odd));
2743
2744 prev_odd = curr_odd;
2745 }
2746
2747 {
2750 let k_start = even_chunks * 16;
2751 let mut prev1 = if even_chunks > 0 {
2752 vgetq_lane_s16::<6>(prev_odd) as i32
2753 } else {
2754 0
2755 };
2756 let mut next1 = if even_chunks > 0 {
2757 vgetq_lane_s16::<7>(prev_odd) as i32
2758 } else {
2759 0
2760 };
2761 let mut next3 = if k_start < kmax {
2762 *data.get_unchecked(row_off + k_start + 1) as i32
2763 } else {
2764 0
2765 };
2766 let mut k = k_start;
2767 while k <= kmax {
2768 let prev3 = prev1;
2769 prev1 = next1;
2770 next1 = next3;
2771 next3 = if k + 3 <= kmax {
2772 *data.get_unchecked(row_off + k + 3) as i32
2773 } else {
2774 0
2775 };
2776 let a = prev1 + next1;
2777 let c = prev3 + next3;
2778 let idx = row_off + k;
2779 *data.get_unchecked_mut(idx) =
2780 (*data.get_unchecked(idx) as i32 - (((a << 3) + a - c + 16) >> 5)) as i16;
2781 k += 2;
2782 }
2783 }
2784
2785 if kmax < 1 {
2788 return;
2789 }
2790
2791 {
2793 let p1 = *data.get_unchecked(row_off) as i32;
2794 let idx1 = row_off + 1;
2795 if 1 < kmax {
2796 let n1 = *data.get_unchecked(row_off + 2) as i32;
2797 *data.get_unchecked_mut(idx1) =
2798 (*data.get_unchecked(idx1) as i32 + ((p1 + n1 + 1) >> 1)) as i16;
2799 } else {
2800 *data.get_unchecked_mut(idx1) = (*data.get_unchecked(idx1) as i32 + p1) as i16;
2801 }
2802 }
2803
2804 let odd_chunks = if kmax >= 20 {
2809 even_chunks.min((kmax - 20) / 16 + 1)
2810 } else {
2811 0
2812 };
2813
2814 for chunk in 0..odd_chunks {
2815 let pair1 = vld2q_s16(ptr.add(chunk * 16) as *const i16);
2817 let pair2 = vld2q_s16(ptr.add((chunk + 1) * 16) as *const i16);
2819
2820 let curr_odds = vextq_s16::<1>(pair1.1, pair2.1);
2822
2823 let p3_e = pair1.0;
2829 let p1_e = vextq_s16::<1>(pair1.0, pair2.0); let n1_e = vextq_s16::<2>(pair1.0, pair2.0);
2831 let n3_e = vextq_s16::<3>(pair1.0, pair2.0);
2832
2833 macro_rules! predict {
2835 ($co:expr, $p1:expr, $n1:expr, $p3:expr, $n3:expr) => {{
2836 let a = vaddq_s32($p1, $n1);
2837 let c = vaddq_s32($p3, $n3);
2838 let nine_a = vaddq_s32(vshlq_n_s32::<3>(a), a);
2839 let delta = vshrq_n_s32::<4>(vsubq_s32(vaddq_s32(nine_a, vdupq_n_s32(8i32)), c));
2840 vaddq_s32($co, delta)
2841 }};
2842 }
2843
2844 let new_lo = predict!(
2845 vmovl_s16(vget_low_s16(curr_odds)),
2846 vmovl_s16(vget_low_s16(p1_e)),
2847 vmovl_s16(vget_low_s16(n1_e)),
2848 vmovl_s16(vget_low_s16(p3_e)),
2849 vmovl_s16(vget_low_s16(n3_e))
2850 );
2851 let new_hi = predict!(
2852 vmovl_high_s16(curr_odds),
2853 vmovl_high_s16(p1_e),
2854 vmovl_high_s16(n1_e),
2855 vmovl_high_s16(p3_e),
2856 vmovl_high_s16(n3_e)
2857 );
2858 let new_odds = vcombine_s16(vmovn_s32(new_lo), vmovn_s32(new_hi));
2859
2860 vst2q_s16(ptr.add(chunk * 16 + 2), int16x8x2_t(p1_e, new_odds));
2862 }
2863
2864 if kmax >= 3 {
2867 let k_scalar = 3 + odd_chunks * 16;
2868 let mut prev1 = *data.get_unchecked(row_off + k_scalar - 3) as i32;
2869 let mut next1 = *data.get_unchecked(row_off + k_scalar - 1) as i32;
2870 let mut next3 = if k_scalar < kmax {
2871 *data.get_unchecked(row_off + k_scalar + 1) as i32
2872 } else {
2873 0
2874 };
2875 let mut k = k_scalar;
2876 while k <= kmax {
2877 let prev3 = prev1;
2878 prev1 = next1;
2879 next1 = next3;
2880 next3 = if k + 3 <= kmax {
2881 *data.get_unchecked(row_off + k + 3) as i32
2882 } else {
2883 0
2884 };
2885 let idx = row_off + k;
2886 if k <= border {
2887 let a = prev1 + next1;
2888 let c = prev3 + next3;
2889 *data.get_unchecked_mut(idx) =
2890 (*data.get_unchecked(idx) as i32 + (((a << 3) + a - c + 8) >> 4)) as i16;
2891 } else if k < kmax {
2892 *data.get_unchecked_mut(idx) =
2893 (*data.get_unchecked(idx) as i32 + ((prev1 + next1 + 1) >> 1)) as i16;
2894 } else {
2895 *data.get_unchecked_mut(idx) = (*data.get_unchecked(idx) as i32 + prev1) as i16;
2896 }
2897 k += 2;
2898 }
2899 }
2900}
2901
2902pub(crate) fn row_pass_inner(
2913 data: &mut [i16],
2914 width: usize,
2915 height: usize,
2916 stride: usize,
2917 s: usize,
2918 sd: usize,
2919 use_simd: bool,
2920) {
2921 #[cfg(target_arch = "aarch64")]
2924 if use_simd && s == 1 {
2925 for row in (0..height).step_by(s) {
2926 #[allow(unsafe_code)]
2927 unsafe {
2928 row_pass_neon_s1_row(data, row * stride, width);
2929 }
2930 }
2931 return;
2932 }
2933
2934 let kmax = (width - 1) >> sd;
2935 let border = kmax.saturating_sub(3);
2936
2937 let simd_active = if use_simd { height / s / 8 * 8 } else { 0 };
2943 let simd_rows = simd_active * s;
2944
2945 for group in 0..simd_active / 8 {
2946 let row_base = group * 8 * s;
2947 let o: [usize; 8] = core::array::from_fn(|i| (row_base + i * s) * stride);
2948
2949 let mut prev1v = i32x8::splat(0);
2951 let mut next1v = i32x8::splat(0);
2952 let mut next3v = if kmax >= 1 {
2953 load_rows8(data, &o, 1 << sd)
2954 } else {
2955 i32x8::splat(0)
2956 };
2957 let mut prev3v: i32x8;
2958 let mut k = 0usize;
2959 while k <= kmax {
2960 prev3v = prev1v;
2961 prev1v = next1v;
2962 next1v = next3v;
2963 next3v = if k + 3 <= kmax {
2964 load_rows8(data, &o, (k + 3) << sd)
2965 } else {
2966 i32x8::splat(0)
2967 };
2968 let cur = load_rows8(data, &o, k << sd);
2969 store_rows8(
2970 data,
2971 &o,
2972 k << sd,
2973 lifting_even(cur, prev1v, next1v, prev3v, next3v),
2974 );
2975 k += 2;
2976 }
2977
2978 if kmax >= 1 {
2980 let mut k = 1usize;
2981 prev1v = load_rows8(data, &o, (k - 1) << sd);
2982 if k < kmax {
2983 next1v = load_rows8(data, &o, (k + 1) << sd);
2984 let cur = load_rows8(data, &o, k << sd);
2985 store_rows8(data, &o, k << sd, predict_avg(cur, prev1v, next1v));
2986 } else {
2987 let cur = load_rows8(data, &o, k << sd);
2989 store_rows8(data, &o, k << sd, cur + prev1v);
2990 next1v = i32x8::splat(0);
2991 }
2992
2993 next3v = if border >= 3 {
2994 load_rows8(data, &o, (k + 3) << sd)
2995 } else {
2996 i32x8::splat(0)
2997 };
2998
2999 k = 3;
3000 while k <= border {
3001 prev3v = prev1v;
3002 prev1v = next1v;
3003 next1v = next3v;
3004 next3v = load_rows8(data, &o, (k + 3) << sd);
3005 let cur = load_rows8(data, &o, k << sd);
3006 store_rows8(
3007 data,
3008 &o,
3009 k << sd,
3010 predict_inner(cur, prev1v, next1v, prev3v, next3v),
3011 );
3012 k += 2;
3013 }
3014
3015 while k <= kmax {
3016 prev1v = next1v;
3017 next1v = next3v;
3018 next3v = i32x8::splat(0);
3019 let cur = load_rows8(data, &o, k << sd);
3020 if k < kmax {
3021 store_rows8(data, &o, k << sd, predict_avg(cur, prev1v, next1v));
3022 } else {
3023 store_rows8(data, &o, k << sd, cur + prev1v);
3024 }
3025 k += 2;
3026 }
3027 }
3028 }
3029
3030 let scalar_start = simd_rows;
3032 for row in (scalar_start..height).step_by(s) {
3033 let off = row * stride;
3034
3035 let mut prev1: i32 = 0;
3037 let mut next1: i32 = 0;
3038 let mut next3: i32 = if kmax >= 1 {
3039 data[off + (1 << sd)] as i32
3040 } else {
3041 0
3042 };
3043 let mut prev3: i32;
3044 let mut k = 0usize;
3045 while k <= kmax {
3046 prev3 = prev1;
3047 prev1 = next1;
3048 next1 = next3;
3049 next3 = if k + 3 <= kmax {
3050 data[off + ((k + 3) << sd)] as i32
3051 } else {
3052 0
3053 };
3054 let a = prev1 + next1;
3055 let c = prev3 + next3;
3056 let idx = off + (k << sd);
3057 data[idx] = (data[idx] as i32 - (((a << 3) + a - c + 16) >> 5)) as i16;
3058 k += 2;
3059 }
3060
3061 if kmax >= 1 {
3063 let mut k = 1usize;
3064 prev1 = data[off + ((k - 1) << sd)] as i32;
3065 if k < kmax {
3066 next1 = data[off + ((k + 1) << sd)] as i32;
3067 let idx = off + (k << sd);
3068 data[idx] = (data[idx] as i32 + ((prev1 + next1 + 1) >> 1)) as i16;
3069 } else {
3070 let idx = off + (k << sd);
3071 data[idx] = (data[idx] as i32 + prev1) as i16;
3072 }
3073
3074 next3 = if border >= 3 {
3075 data[off + ((k + 3) << sd)] as i32
3076 } else {
3077 0
3078 };
3079
3080 k = 3;
3081 while k <= border {
3082 prev3 = prev1;
3083 prev1 = next1;
3084 next1 = next3;
3085 next3 = data[off + ((k + 3) << sd)] as i32;
3086 let a = prev1 + next1;
3087 let idx = off + (k << sd);
3088 data[idx] = (data[idx] as i32 + (((a << 3) + a - (prev3 + next3) + 8) >> 4)) as i16;
3089 k += 2;
3090 }
3091
3092 while k <= kmax {
3093 prev1 = next1;
3094 next1 = next3;
3095 next3 = 0;
3096 let idx = off + (k << sd);
3097 if k < kmax {
3098 data[idx] = (data[idx] as i32 + ((prev1 + next1 + 1) >> 1)) as i16;
3099 } else {
3100 data[idx] = (data[idx] as i32 + prev1) as i16;
3101 }
3102 k += 2;
3103 }
3104 }
3105 }
3106}
3107
3108fn inverse_wavelet_transform(plane: &mut FlatPlane, width: usize, height: usize, subsample: usize) {
3109 inverse_wavelet_transform_from(plane, width, height, subsample, 16);
3110}
3111
3112fn inverse_wavelet_transform_from(
3119 plane: &mut FlatPlane,
3120 width: usize,
3121 height: usize,
3122 subsample: usize,
3123 start_scale: usize,
3124) {
3125 let stride = plane.stride;
3126 let data = plane.data.as_mut_slice();
3127 let mut s = start_scale;
3128 let mut s_degree: u32 = start_scale.trailing_zeros();
3129
3130 let mut st0 = vec![0i32; width];
3131 let mut st1 = vec![0i32; width];
3132 let mut st2 = vec![0i32; width];
3133
3134 while s >= subsample {
3135 let sd = s_degree as usize;
3136
3137 let use_simd = s <= 4;
3141
3142 {
3144 let kmax = (height - 1) >> sd;
3145 let border = kmax.saturating_sub(3);
3146 let num_cols = width.div_ceil(s);
3147 let simd_cols = if use_simd { num_cols / 8 * 8 } else { 0 };
3148
3149 for v in &mut st0[..num_cols] {
3151 *v = 0;
3152 }
3153 for v in &mut st1[..num_cols] {
3154 *v = 0;
3155 }
3156 if kmax >= 1 {
3157 let off = (1 << sd) * stride;
3158 if use_simd {
3159 for ci in (0..simd_cols).step_by(8) {
3160 store8_i32(&mut st2, ci, load8s(data, off + ci * s, s));
3161 }
3162 for ci in simd_cols..num_cols {
3163 st2[ci] = data[off + ci * s] as i32;
3164 }
3165 } else {
3166 for (ci, col) in (0..width).step_by(s).enumerate() {
3167 st2[ci] = data[off + col] as i32;
3168 }
3169 }
3170 } else {
3171 for v in &mut st2[..num_cols] {
3172 *v = 0;
3173 }
3174 }
3175
3176 let mut k = 0usize;
3181 while k + 3 <= kmax {
3183 let k_off = (k << sd) * stride;
3184 let n3_off = ((k + 3) << sd) * stride;
3185 if use_simd {
3186 let mut ci = 0usize;
3187 while ci < simd_cols {
3188 let vp3 = load8_i32(&st0, ci);
3189 let vp1 = load8_i32(&st1, ci);
3190 let vn1 = load8_i32(&st2, ci);
3191 let vn3 = load8s(data, n3_off + ci * s, s);
3192 let cur = load8s(data, k_off + ci * s, s);
3193 store8s(
3194 data,
3195 k_off + ci * s,
3196 s,
3197 lifting_even(cur, vp1, vn1, vp3, vn3),
3198 );
3199 store8_i32(&mut st0, ci, vp1);
3200 store8_i32(&mut st1, ci, vn1);
3201 store8_i32(&mut st2, ci, vn3);
3202 ci += 8;
3203 }
3204 while ci < num_cols {
3205 let p3 = st0[ci];
3206 let p1 = st1[ci];
3207 let n1 = st2[ci];
3208 let n3 = data[n3_off + ci * s] as i32;
3209 let a = p1 + n1;
3210 let idx = k_off + ci * s;
3211 data[idx] =
3212 (data[idx] as i32 - (((a << 3) + a - (p3 + n3) + 16) >> 5)) as i16;
3213 st0[ci] = p1;
3214 st1[ci] = n1;
3215 st2[ci] = n3;
3216 ci += 1;
3217 }
3218 } else {
3219 for (ci, col) in (0..width).step_by(s).enumerate() {
3220 let p3 = st0[ci];
3221 let p1 = st1[ci];
3222 let n1 = st2[ci];
3223 let n3 = data[n3_off + col] as i32;
3224 let a = p1 + n1;
3225 let c = p3 + n3;
3226 let idx = k_off + col;
3227 data[idx] = (data[idx] as i32 - (((a << 3) + a - c + 16) >> 5)) as i16;
3228 st0[ci] = p1;
3229 st1[ci] = n1;
3230 st2[ci] = n3;
3231 }
3232 }
3233 k += 2;
3234 }
3235 while k <= kmax {
3237 let k_off = (k << sd) * stride;
3238 if use_simd {
3239 let zero8 = i32x8::splat(0);
3240 let mut ci = 0usize;
3241 while ci < simd_cols {
3242 let vp3 = load8_i32(&st0, ci);
3243 let vp1 = load8_i32(&st1, ci);
3244 let vn1 = load8_i32(&st2, ci);
3245 let cur = load8s(data, k_off + ci * s, s);
3246 store8s(
3247 data,
3248 k_off + ci * s,
3249 s,
3250 lifting_even(cur, vp1, vn1, vp3, zero8),
3251 );
3252 store8_i32(&mut st0, ci, vp1);
3253 store8_i32(&mut st1, ci, vn1);
3254 store8_i32(&mut st2, ci, zero8);
3255 ci += 8;
3256 }
3257 while ci < num_cols {
3258 let p3 = st0[ci];
3259 let p1 = st1[ci];
3260 let n1 = st2[ci];
3261 let a = p1 + n1;
3262 let idx = k_off + ci * s;
3263 data[idx] = (data[idx] as i32 - (((a << 3) + a - p3 + 16) >> 5)) as i16;
3264 st0[ci] = p1;
3265 st1[ci] = n1;
3266 st2[ci] = 0;
3267 ci += 1;
3268 }
3269 } else {
3270 for (ci, col) in (0..width).step_by(s).enumerate() {
3271 let p3 = st0[ci];
3272 let p1 = st1[ci];
3273 let n1 = st2[ci];
3274 let a = p1 + n1;
3275 let idx = k_off + col;
3276 data[idx] = (data[idx] as i32 - (((a << 3) + a - p3 + 16) >> 5)) as i16;
3277 st0[ci] = p1;
3278 st1[ci] = n1;
3279 st2[ci] = 0;
3280 }
3281 }
3282 k += 2;
3283 }
3284
3285 if kmax >= 1 {
3287 let km1_off = 0;
3289 let k_off = (1 << sd) * stride;
3290
3291 if 2 <= kmax {
3292 let kp1_off = (2 << sd) * stride;
3293 if use_simd {
3294 let mut ci = 0usize;
3295 while ci < simd_cols {
3296 let vp = load8s(data, km1_off + ci * s, s);
3297 let vn = load8s(data, kp1_off + ci * s, s);
3298 let cur = load8s(data, k_off + ci * s, s);
3299 store8s(data, k_off + ci * s, s, predict_avg(cur, vp, vn));
3300 store8_i32(&mut st0, ci, vp);
3301 store8_i32(&mut st1, ci, vn);
3302 ci += 8;
3303 }
3304 while ci < num_cols {
3305 let p = data[km1_off + ci * s] as i32;
3306 let n = data[kp1_off + ci * s] as i32;
3307 let idx = k_off + ci * s;
3308 data[idx] = (data[idx] as i32 + ((p + n + 1) >> 1)) as i16;
3309 st0[ci] = p;
3310 st1[ci] = n;
3311 ci += 1;
3312 }
3313 } else {
3314 for (ci, col) in (0..width).step_by(s).enumerate() {
3315 let p = data[km1_off + col] as i32;
3316 let n = data[kp1_off + col] as i32;
3317 let idx = k_off + col;
3318 data[idx] = (data[idx] as i32 + ((p + n + 1) >> 1)) as i16;
3319 st0[ci] = p;
3320 st1[ci] = n;
3321 }
3322 }
3323 } else if use_simd {
3324 let mut ci = 0usize;
3325 while ci < simd_cols {
3326 let vp = load8s(data, km1_off + ci * s, s);
3327 let cur = load8s(data, k_off + ci * s, s);
3328 store8s(data, k_off + ci * s, s, cur + vp);
3329 store8_i32(&mut st0, ci, vp);
3330 ci += 8;
3331 }
3332 for v in &mut st1[..num_cols] {
3333 *v = 0;
3334 }
3335 while ci < num_cols {
3336 let p = data[km1_off + ci * s] as i32;
3337 let idx = k_off + ci * s;
3338 data[idx] = (data[idx] as i32 + p) as i16;
3339 st0[ci] = p;
3340 st1[ci] = 0;
3341 ci += 1;
3342 }
3343 } else {
3344 for (ci, col) in (0..width).step_by(s).enumerate() {
3345 let p = data[km1_off + col] as i32;
3346 let idx = k_off + col;
3347 data[idx] = (data[idx] as i32 + p) as i16;
3348 st0[ci] = p;
3349 st1[ci] = 0;
3350 }
3351 }
3352
3353 if border >= 3 {
3354 let off = (4 << sd) * stride;
3355 if use_simd {
3356 let mut ci = 0usize;
3357 while ci < simd_cols {
3358 store8_i32(&mut st2, ci, load8s(data, off + ci * s, s));
3359 ci += 8;
3360 }
3361 while ci < num_cols {
3362 st2[ci] = data[off + ci * s] as i32;
3363 ci += 1;
3364 }
3365 } else {
3366 for (ci, col) in (0..width).step_by(s).enumerate() {
3367 st2[ci] = data[off + col] as i32;
3368 }
3369 }
3370 }
3371
3372 let mut k = 3usize;
3374 while k <= border {
3375 let k_off = (k << sd) * stride;
3376 let n3_off = ((k + 3) << sd) * stride;
3377
3378 if use_simd {
3379 let mut ci = 0usize;
3380 while ci < simd_cols {
3381 let vp3 = load8_i32(&st0, ci);
3382 let vp1 = load8_i32(&st1, ci);
3383 let vn1 = load8_i32(&st2, ci);
3384 let vn3 = load8s(data, n3_off + ci * s, s);
3385 let cur = load8s(data, k_off + ci * s, s);
3386 store8s(
3387 data,
3388 k_off + ci * s,
3389 s,
3390 predict_inner(cur, vp1, vn1, vp3, vn3),
3391 );
3392 store8_i32(&mut st0, ci, vp1);
3393 store8_i32(&mut st1, ci, vn1);
3394 store8_i32(&mut st2, ci, vn3);
3395 ci += 8;
3396 }
3397 while ci < num_cols {
3398 let p3 = st0[ci];
3399 let p1 = st1[ci];
3400 let n1 = st2[ci];
3401 let n3 = data[n3_off + ci * s] as i32;
3402 let a = p1 + n1;
3403 let idx = k_off + ci * s;
3404 data[idx] =
3405 (data[idx] as i32 + (((a << 3) + a - (p3 + n3) + 8) >> 4)) as i16;
3406 st0[ci] = p1;
3407 st1[ci] = n1;
3408 st2[ci] = n3;
3409 ci += 1;
3410 }
3411 } else {
3412 for (ci, col) in (0..width).step_by(s).enumerate() {
3413 let p3 = st0[ci];
3414 let p1 = st1[ci];
3415 let n1 = st2[ci];
3416 let n3 = data[n3_off + col] as i32;
3417
3418 let a = p1 + n1;
3419 let idx = k_off + col;
3420 data[idx] =
3421 (data[idx] as i32 + (((a << 3) + a - (p3 + n3) + 8) >> 4)) as i16;
3422
3423 st0[ci] = p1;
3424 st1[ci] = n1;
3425 st2[ci] = n3;
3426 }
3427 }
3428 k += 2;
3429 }
3430
3431 while k <= kmax {
3433 let k_off = (k << sd) * stride;
3434
3435 if k < kmax {
3436 if use_simd {
3437 let mut ci = 0usize;
3438 while ci < simd_cols {
3439 let vp = load8_i32(&st1, ci);
3440 let vn = load8_i32(&st2, ci);
3441 let cur = load8s(data, k_off + ci * s, s);
3442 store8s(data, k_off + ci * s, s, predict_avg(cur, vp, vn));
3443 store8_i32(&mut st1, ci, vn);
3444 store8_i32(&mut st2, ci, i32x8::splat(0));
3445 ci += 8;
3446 }
3447 while ci < num_cols {
3448 let p = st1[ci];
3449 let n = st2[ci];
3450 let idx = k_off + ci * s;
3451 data[idx] = (data[idx] as i32 + ((p + n + 1) >> 1)) as i16;
3452 st1[ci] = n;
3453 st2[ci] = 0;
3454 ci += 1;
3455 }
3456 } else {
3457 for (ci, col) in (0..width).step_by(s).enumerate() {
3458 let p = st1[ci];
3459 let n = st2[ci];
3460 let idx = k_off + col;
3461 data[idx] = (data[idx] as i32 + ((p + n + 1) >> 1)) as i16;
3462 st1[ci] = n;
3463 st2[ci] = 0;
3464 }
3465 }
3466 } else if use_simd {
3467 let mut ci = 0usize;
3468 while ci < simd_cols {
3469 let vp = load8_i32(&st1, ci);
3470 let cur = load8s(data, k_off + ci * s, s);
3471 store8s(data, k_off + ci * s, s, cur + vp);
3472 store8_i32(&mut st1, ci, load8_i32(&st2, ci));
3473 store8_i32(&mut st2, ci, i32x8::splat(0));
3474 ci += 8;
3475 }
3476 while ci < num_cols {
3477 let p = st1[ci];
3478 let idx = k_off + ci * s;
3479 data[idx] = (data[idx] as i32 + p) as i16;
3480 st1[ci] = st2[ci];
3481 st2[ci] = 0;
3482 ci += 1;
3483 }
3484 } else {
3485 for (ci, col) in (0..width).step_by(s).enumerate() {
3486 let p = st1[ci];
3487 let idx = k_off + col;
3488 data[idx] = (data[idx] as i32 + p) as i16;
3489 st1[ci] = st2[ci];
3490 st2[ci] = 0;
3491 }
3492 }
3493 k += 2;
3494 }
3495 }
3496 }
3497
3498 row_pass_inner(data, width, height, stride, s, sd, true);
3501
3502 s >>= 1;
3503 s_degree = s_degree.saturating_sub(1);
3504 }
3505}
3506
3507#[derive(Clone, Debug)]
3528pub struct Iw44Image {
3529 pub width: u32,
3531 pub height: u32,
3533 is_color: bool,
3535 delay: u8,
3537 chroma_half: bool,
3539 y: Option<PlaneDecoder>,
3541 cb: Option<PlaneDecoder>,
3543 cr: Option<PlaneDecoder>,
3545 cslice: usize,
3547 next_serial: u32,
3552}
3553
3554impl Default for Iw44Image {
3555 fn default() -> Self {
3556 Self::new()
3557 }
3558}
3559
3560impl Iw44Image {
3561 pub fn heap_bytes(&self) -> usize {
3579 [self.y.as_ref(), self.cb.as_ref(), self.cr.as_ref()]
3580 .into_iter()
3581 .flatten()
3582 .map(PlaneDecoder::heap_bytes)
3583 .sum()
3584 }
3585
3586 pub fn new() -> Self {
3588 Iw44Image {
3589 width: 0,
3590 height: 0,
3591 is_color: false,
3592 delay: 0,
3593 chroma_half: false,
3594 y: None,
3595 cb: None,
3596 cr: None,
3597 cslice: 0,
3598 next_serial: 0,
3599 }
3600 }
3601
3602 #[cfg(test)]
3607 pub fn chroma_plane_dims(&self) -> Option<(usize, usize)> {
3608 self.cb.as_ref().map(|p| (p.width, p.height))
3609 }
3610
3611 #[cfg(test)]
3613 pub fn is_color(&self) -> bool {
3614 self.is_color
3615 }
3616
3617 #[cfg(test)]
3619 pub fn chroma_half(&self) -> bool {
3620 self.chroma_half
3621 }
3622
3623 pub fn decode_chunk(&mut self, data: &[u8]) -> Result<(), Iw44Error> {
3633 if data.len() < 2 {
3634 return Err(Iw44Error::ChunkTooShort);
3635 }
3636 let serial = data[0];
3637 let slices = data[1];
3638
3639 if self.y.is_some() && serial as u32 != self.next_serial {
3655 return Err(Iw44Error::UnexpectedSerial);
3656 }
3657
3658 let payload_start = if serial == 0 {
3659 if data.len() < 9 {
3661 return Err(Iw44Error::HeaderTooShort);
3662 }
3663 let majver = data[2];
3664 let minor = data[3];
3665 let is_grayscale = (majver >> 7) != 0;
3666 let w = u16::from_be_bytes([data[4], data[5]]);
3667 let h = u16::from_be_bytes([data[6], data[7]]);
3668 let delay_byte = data[8];
3669 let delay = if minor >= 2 { delay_byte & 127 } else { 0 };
3670 let chroma_half = false;
3676
3677 if w == 0 || h == 0 {
3678 return Err(Iw44Error::ZeroDimension);
3679 }
3680 let pixels = w as u64 * h as u64;
3690 if pixels > 64 * 1024 * 1024 {
3691 return Err(Iw44Error::ImageTooLarge);
3692 }
3693
3694 self.width = w as u32;
3695 self.height = h as u32;
3696 self.is_color = !is_grayscale;
3697 self.delay = delay;
3698 self.chroma_half = self.is_color && chroma_half;
3699 self.cslice = 0;
3700 self.y = Some(PlaneDecoder::new(w as usize, h as usize));
3701 if self.is_color {
3702 let (cw, ch) = if self.chroma_half {
3703 ((w as usize).div_ceil(2), (h as usize).div_ceil(2))
3704 } else {
3705 (w as usize, h as usize)
3706 };
3707 self.cb = Some(PlaneDecoder::new(cw, ch));
3708 self.cr = Some(PlaneDecoder::new(cw, ch));
3709 }
3710 9
3711 } else {
3712 if self.y.is_none() {
3713 return Err(Iw44Error::MissingFirstChunk);
3714 }
3715 2
3716 };
3717
3718 let raw_zp_data = &data[payload_start..];
3729 let padded_zp_data;
3730 let zp_data: &[u8] = if raw_zp_data.len() >= 2 {
3731 raw_zp_data
3732 } else {
3733 padded_zp_data = [raw_zp_data.first().copied().unwrap_or(0xff), 0xff];
3734 &padded_zp_data
3735 };
3736 let mut zp = ZpDecoder::new(zp_data).map_err(|_| Iw44Error::ZpTooShort)?;
3737
3738 for _ in 0..slices {
3739 self.cslice += 1;
3740 if let Some(ref mut y) = self.y {
3741 y.decode_slice(&mut zp);
3742 }
3743 if self.is_color && self.cslice > self.delay as usize {
3744 if let Some(ref mut cb) = self.cb {
3745 cb.decode_slice(&mut zp);
3746 }
3747 if let Some(ref mut cr) = self.cr {
3748 cr.decode_slice(&mut zp);
3749 }
3750 }
3751 }
3763
3764 self.next_serial = serial as u32 + 1;
3765 Ok(())
3766 }
3767
3768 pub fn to_rgb(&self) -> Result<Pixmap, Iw44Error> {
3776 self.to_rgb_subsample(1)
3777 }
3778
3779 pub fn to_rgb_subsample(&self, subsample: u32) -> Result<Pixmap, Iw44Error> {
3784 if subsample == 0 {
3785 return Err(Iw44Error::InvalidSubsample);
3786 }
3787 let y_dec = self.y.as_ref().ok_or(Iw44Error::MissingCodec)?;
3788 let sub = subsample as usize;
3789 let w = (self.width as usize).div_ceil(sub) as u32;
3790 let h = (self.height as usize).div_ceil(sub) as u32;
3791
3792 if self.is_color {
3793 let chroma_sub = if self.chroma_half {
3798 sub.div_ceil(2)
3799 } else {
3800 sub
3801 };
3802 let cb_dec = self.cb.as_ref().ok_or(Iw44Error::MissingCodec)?;
3803 let cr_dec = self.cr.as_ref().ok_or(Iw44Error::MissingCodec)?;
3804
3805 let pw = w as usize;
3806 let ph = h as usize;
3807
3808 if sub == 1 {
3812 let mut pm = Pixmap::try_new(w, h, 0, 0, 0, 255)?;
3813 if let Some(keep) = band_keep_blocks(y_dec, self.chroma_half, 0) {
3818 self.rgb_sub1_banded(y_dec, cb_dec, cr_dec, keep, pw, ph, &mut pm);
3819 return Ok(pm);
3820 }
3821 let (y_plane, cb_plane, cr_plane) =
3822 reconstruct_planes(y_dec, cb_dec, cr_dec, sub, chroma_sub);
3823 convert_rgb_rows(
3824 self.chroma_half,
3825 &y_plane,
3826 0,
3827 &cb_plane,
3828 &cr_plane,
3829 0,
3830 0..ph,
3831 pw,
3832 ph,
3833 &mut pm.data,
3834 );
3835 return Ok(pm);
3836 }
3837
3838 let (y_plane, cb_plane, cr_plane) =
3839 reconstruct_planes(y_dec, cb_dec, cr_dec, sub, chroma_sub);
3840 let mut pm = Pixmap::try_new(w, h, 0, 0, 0, 255)?;
3841
3842 if (2..=8).contains(&sub) && sub.is_power_of_two() {
3852 for row in 0..ph {
3853 let out_row = ph - 1 - row; let y_off = row * y_plane.stride;
3855 let c_off = row * cb_plane.stride;
3856 let row_start = out_row * pw * 4;
3857 ycbcr_row_from_i16(
3858 &y_plane.data[y_off..y_off + pw],
3859 &cb_plane.data[c_off..c_off + pw],
3860 &cr_plane.data[c_off..c_off + pw],
3861 &mut pm.data[row_start..row_start + pw * 4],
3862 );
3863 }
3864 return Ok(pm);
3865 }
3866
3867 for row in 0..h {
3869 let out_row = h - 1 - row;
3870 for col in 0..w {
3871 let src_row = row as usize * sub;
3872 let src_col = col as usize * sub;
3873 let y_idx = src_row * y_plane.stride + src_col;
3874 let chroma_row = if self.chroma_half {
3875 src_row / 2
3876 } else {
3877 src_row
3878 };
3879 let chroma_col = if self.chroma_half {
3880 src_col / 2
3881 } else {
3882 src_col
3883 };
3884 let c_idx = chroma_row * cb_plane.stride + chroma_col;
3885
3886 let y = normalize(y_plane.data[y_idx]);
3887 let b = normalize(cb_plane.data[c_idx]);
3888 let r = normalize(cr_plane.data[c_idx]);
3889
3890 let t2 = r + (r >> 1);
3891 let t3 = y + 128 - (b >> 2);
3892
3893 let red = (y + 128 + t2).clamp(0, 255) as u8;
3894 let green = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
3895 let blue = (t3 + (b << 1)).clamp(0, 255) as u8;
3896 pm.set_rgb(col, out_row, red, green, blue);
3897 }
3898 }
3899 Ok(pm)
3900 } else {
3901 let y_plane = y_dec.reconstruct(sub);
3905 let is_compact = (2..=8).contains(&sub) && sub.is_power_of_two();
3906 let mut pm = Pixmap::try_new(w, h, 0, 0, 0, 255)?;
3907 for row in 0..h {
3908 let out_row = h - 1 - row;
3909 for col in 0..w {
3910 let (src_row, src_col) = if is_compact {
3911 (row as usize, col as usize)
3912 } else {
3913 (row as usize * sub, col as usize * sub)
3914 };
3915 let idx = src_row * y_plane.stride + src_col;
3916 let val = normalize(y_plane.data[idx]);
3917 let gray = (127 - val) as u8;
3919 pm.set_rgb(col, out_row, gray, gray, gray);
3920 }
3921 }
3922 Ok(pm)
3923 }
3924 }
3925
3926 #[allow(clippy::too_many_arguments)]
3933 fn rgb_sub1_banded(
3934 &self,
3935 y_dec: &PlaneDecoder,
3936 cb_dec: &PlaneDecoder,
3937 cr_dec: &PlaneDecoder,
3938 keep: usize,
3939 pw: usize,
3940 ph: usize,
3941 pm: &mut Pixmap,
3942 ) {
3943 let block_rows = (self.height as usize).div_ceil(32);
3944 let mut first = 0usize;
3945 while first < block_rows {
3946 let last = (first + keep).min(block_rows);
3947 let (r0, r1) = (first * 32, (last * 32).min(ph));
3948 if r0 >= r1 {
3949 break;
3950 }
3951 let out = &mut pm.data[(ph - r1) * pw * 4..(ph - r0) * pw * 4];
3952 self.rgb_sub1_band(y_dec, cb_dec, cr_dec, r0, r1, pw, ph, out);
3953 first = last;
3954 }
3955 }
3956
3957 #[allow(clippy::too_many_arguments)]
3964 fn rgb_sub1_band(
3965 &self,
3966 y_dec: &PlaneDecoder,
3967 cb_dec: &PlaneDecoder,
3968 cr_dec: &PlaneDecoder,
3969 r0: usize,
3970 r1: usize,
3971 pw: usize,
3972 ph: usize,
3973 out: &mut [u8],
3974 ) {
3975 let block_rows = (self.height as usize).div_ceil(32);
3976 let c_block_rows = cb_dec.height.div_ceil(32);
3977 let ch = ph.div_ceil(2);
3978 let (first, last) = (r0 / 32, r1.div_ceil(32));
3979 let lo = first.saturating_sub(BAND_HALO_BLOCKS);
3980 let hi = (last + BAND_HALO_BLOCKS).min(block_rows);
3981
3982 let (c_r0, c_r1) = if self.chroma_half {
3987 (r0 / 2, (r1.div_ceil(2) + 1).min(ch))
3988 } else {
3989 (r0, r1)
3990 };
3991 let c_lo = (c_r0 / 32).saturating_sub(BAND_HALO_BLOCKS);
3992 let c_hi = (c_r1.div_ceil(32) + BAND_HALO_BLOCKS).min(c_block_rows);
3993
3994 #[cfg(feature = "parallel")]
3995 let (y_band, cb_band, cr_band) = {
3996 let (y, (cb, cr)) = rayon::join(
3997 || y_dec.reconstruct_band(lo, hi),
3998 || {
3999 rayon::join(
4000 || cb_dec.reconstruct_band(c_lo, c_hi),
4001 || cr_dec.reconstruct_band(c_lo, c_hi),
4002 )
4003 },
4004 );
4005 (y, cb, cr)
4006 };
4007 #[cfg(not(feature = "parallel"))]
4008 let (y_band, cb_band, cr_band) = (
4009 y_dec.reconstruct_band(lo, hi),
4010 cb_dec.reconstruct_band(c_lo, c_hi),
4011 cr_dec.reconstruct_band(c_lo, c_hi),
4012 );
4013
4014 convert_rgb_rows(
4015 self.chroma_half,
4016 &y_band,
4017 lo * 32,
4018 &cb_band,
4019 &cr_band,
4020 c_lo * 32,
4021 r0..r1,
4022 pw,
4023 ph,
4024 out,
4025 );
4026 }
4027
4028 pub fn rgb_band_rows(&self) -> Option<u32> {
4037 if !self.is_color {
4038 return None;
4039 }
4040 let y_dec = self.y.as_ref()?;
4041 band_keep_blocks(y_dec, self.chroma_half, 4).map(|keep| (keep * 32) as u32)
4042 }
4043
4044 pub fn rgb_rows(&self, rows: core::ops::Range<u32>) -> Result<Pixmap, Iw44Error> {
4060 if !self.is_color {
4061 return Err(Iw44Error::MissingCodec);
4062 }
4063 let y_dec = self.y.as_ref().ok_or(Iw44Error::MissingCodec)?;
4064 let cb_dec = self.cb.as_ref().ok_or(Iw44Error::MissingCodec)?;
4065 let cr_dec = self.cr.as_ref().ok_or(Iw44Error::MissingCodec)?;
4066 let (pw, ph) = (self.width as usize, self.height as usize);
4067 let (o0, o1) = (rows.start as usize, rows.end as usize);
4068 if o0 > o1 || o1 > ph {
4069 return Err(Iw44Error::Invalid);
4070 }
4071 let mut pm = Pixmap::try_new(self.width, (o1 - o0) as u32, 0, 0, 0, 255)?;
4072 if o0 == o1 {
4073 return Ok(pm);
4074 }
4075 self.rgb_sub1_band(
4078 y_dec,
4079 cb_dec,
4080 cr_dec,
4081 ph - o1,
4082 ph - o0,
4083 pw,
4084 ph,
4085 &mut pm.data,
4086 );
4087 Ok(pm)
4088 }
4089
4090 pub fn to_gray8(&self) -> Result<GrayPixmap, Iw44Error> {
4094 self.to_gray8_subsample(1)
4095 }
4096
4097 pub fn to_gray8_subsample(&self, subsample: u32) -> Result<GrayPixmap, Iw44Error> {
4117 if subsample == 0 {
4118 return Err(Iw44Error::InvalidSubsample);
4119 }
4120 let y_dec = self.y.as_ref().ok_or(Iw44Error::MissingCodec)?;
4121 let sub = subsample as usize;
4122 let w = (self.width as usize).div_ceil(sub) as u32;
4123 let h = (self.height as usize).div_ceil(sub) as u32;
4124
4125 let y_plane = y_dec.reconstruct(sub);
4128 let is_compact = (2..=8).contains(&sub) && sub.is_power_of_two();
4129 let is_color = self.is_color;
4130
4131 let pw = w as usize;
4132 let ph = h as usize;
4133 let mut data = vec![0u8; pw * ph];
4134 for row in 0..ph {
4135 let out_row = ph - 1 - row; let src_row = if is_compact { row } else { row * sub };
4137 let y_off = src_row * y_plane.stride;
4138 let dst = &mut data[out_row * pw..out_row * pw + pw];
4139 if is_color {
4140 for (col, d) in dst.iter_mut().enumerate() {
4142 let src_col = if is_compact { col } else { col * sub };
4143 let val = normalize(y_plane.data[y_off + src_col]);
4144 *d = (val + 128).clamp(0, 255) as u8;
4145 }
4146 } else {
4147 for (col, d) in dst.iter_mut().enumerate() {
4149 let src_col = if is_compact { col } else { col * sub };
4150 let val = normalize(y_plane.data[y_off + src_col]);
4151 *d = (127 - val) as u8;
4152 }
4153 }
4154 }
4155 Ok(GrayPixmap {
4156 width: w,
4157 height: h,
4158 data,
4159 })
4160 }
4161}
4162
4163#[cfg(test)]
4166mod tests {
4167 use super::*;
4168
4169 #[test]
4171 fn pixmap_error_maps_to_image_too_large() {
4172 let e = PixmapError::TooLarge {
4173 width: 10000,
4174 height: 10000,
4175 pixels: 100_000_000,
4176 max: Pixmap::MAX_PIXELS,
4177 };
4178 assert_eq!(Iw44Error::from(e), Iw44Error::ImageTooLarge);
4179 }
4180
4181 fn assets_path() -> std::path::PathBuf {
4182 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
4183 .join("../../references/djvujs/library/assets")
4184 }
4185
4186 fn golden_path() -> std::path::PathBuf {
4187 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/golden/iw44")
4188 }
4189
4190 fn extract_bg44_chunks(file: &djvu_iff::DjvuFile) -> Vec<&[u8]> {
4192 fn collect(chunk: &djvu_iff::Chunk) -> Option<Vec<&[u8]>> {
4193 match chunk {
4194 djvu_iff::Chunk::Form {
4195 secondary_id,
4196 children,
4197 ..
4198 } => {
4199 if secondary_id == b"DJVU" {
4200 let v = children
4201 .iter()
4202 .filter_map(|c| match c {
4203 djvu_iff::Chunk::Leaf {
4204 id: [b'B', b'G', b'4', b'4'],
4205 data,
4206 } => Some(data.as_slice()),
4207 _ => None,
4208 })
4209 .collect::<Vec<_>>();
4210 return Some(v);
4211 }
4212 for c in children {
4213 if let Some(v) = collect(c) {
4214 return Some(v);
4215 }
4216 }
4217 None
4218 }
4219 _ => None,
4220 }
4221 }
4222 collect(&file.root).unwrap_or_default()
4223 }
4224
4225 fn find_ppm_data_start(ppm: &[u8]) -> usize {
4226 let mut newlines = 0;
4227 for (i, &b) in ppm.iter().enumerate() {
4228 if b == b'\n' {
4229 newlines += 1;
4230 if newlines == 3 {
4231 return i + 1;
4232 }
4233 }
4234 }
4235 0
4236 }
4237
4238 fn assert_or_create_golden(actual_ppm: &[u8], golden_file: &str) {
4244 let path = golden_path().join(golden_file);
4245 if !path.exists() {
4246 std::fs::write(&path, actual_ppm)
4247 .unwrap_or_else(|e| panic!("failed to write golden {golden_file}: {e}"));
4248 return; }
4250 assert_ppm_match(actual_ppm, golden_file);
4251 }
4252
4253 fn assert_ppm_match(actual_ppm: &[u8], golden_file: &str) {
4254 let expected_ppm = std::fs::read(golden_path().join(golden_file))
4255 .unwrap_or_else(|_| panic!("golden file not found: {}", golden_file));
4256 assert_eq!(
4257 actual_ppm.len(),
4258 expected_ppm.len(),
4259 "PPM size mismatch for {}: got {} expected {}",
4260 golden_file,
4261 actual_ppm.len(),
4262 expected_ppm.len()
4263 );
4264 if actual_ppm != expected_ppm {
4265 let header_end = find_ppm_data_start(actual_ppm);
4266 let actual_pixels = &actual_ppm[header_end..];
4267 let expected_pixels = &expected_ppm[header_end..];
4268 let total_pixels = actual_pixels.len() / 3;
4269 let diff_pixels = actual_pixels
4270 .chunks(3)
4271 .zip(expected_pixels.chunks(3))
4272 .filter(|(a, b)| a != b)
4273 .count();
4274 panic!(
4275 "{} pixel mismatch: {}/{} pixels differ ({:.1}%)",
4276 golden_file,
4277 diff_pixels,
4278 total_pixels,
4279 diff_pixels as f64 / total_pixels as f64 * 100.0
4280 );
4281 }
4282 }
4283
4284 #[test]
4287 fn upsample_chroma_row_bilinear_values() {
4288 let half0 = [10i16, 20, 30];
4289 let mut out = [0i16; 6];
4292 super::upsample_chroma_row_bilinear(&half0, &half0, false, &mut out, 3);
4293 assert_eq!(out, [10, 15, 20, 25, 30, 30]);
4294
4295 let half1 = [20i16, 40, 60];
4297 super::upsample_chroma_row_bilinear(&half0, &half1, true, &mut out, 3);
4298 assert_eq!(out, [15, 23, 30, 38, 45, 45]);
4299
4300 let flat = [50i16, 50, 50];
4302 super::upsample_chroma_row_bilinear(&flat, &flat, false, &mut out, 3);
4303 assert_eq!(out, [50, 50, 50, 50, 50, 50]);
4304 }
4305
4306 #[test]
4310 fn iw44_new_rejects_empty_chunk() {
4311 let mut img = Iw44Image::new();
4312 assert!(matches!(
4313 img.decode_chunk(&[]),
4314 Err(Iw44Error::ChunkTooShort)
4315 ));
4316 }
4317
4318 #[test]
4320 fn iw44_new_rejects_truncated_header() {
4321 let mut img = Iw44Image::new();
4322 assert!(matches!(
4324 img.decode_chunk(&[0x00, 0x01, 0x00, 0x02, 0x00]),
4325 Err(Iw44Error::HeaderTooShort)
4326 ));
4327 }
4328
4329 #[test]
4331 fn iw44_new_rejects_zero_dimension() {
4332 let mut img = Iw44Image::new();
4333 let header = [0x00u8, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x64, 0x00];
4335 assert!(matches!(
4336 img.decode_chunk(&header),
4337 Err(Iw44Error::ZeroDimension)
4338 ));
4339 }
4340
4341 #[test]
4344 fn iw44_new_rejects_oversized_image() {
4345 let mut img = Iw44Image::new();
4346 let header = [0x00u8, 0x01, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x00];
4349 assert!(matches!(
4350 img.decode_chunk(&header),
4351 Err(Iw44Error::ImageTooLarge)
4352 ));
4353 }
4354
4355 #[test]
4357 fn iw44_new_rejects_subsequent_before_first() {
4358 let mut img = Iw44Image::new();
4359 assert!(matches!(
4361 img.decode_chunk(&[0x01, 0x01]),
4362 Err(Iw44Error::MissingFirstChunk)
4363 ));
4364 }
4365
4366 #[test]
4374 fn iw44_decode_chunk_tolerates_empty_refinement_payload() {
4375 let mut img = Iw44Image::new();
4376 let header = [0x00u8, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00];
4378 img.decode_chunk(&header).expect("first chunk must decode");
4379
4380 assert!(img.decode_chunk(&[0x01, 0x04]).is_ok());
4382
4383 assert!(img.decode_chunk(&[0x02, 0x04, 0xab]).is_ok());
4386
4387 assert!(img.to_rgb().is_ok());
4389 }
4390
4391 #[test]
4402 fn iw44_decode_chunk_rejects_serial_skip() {
4403 let mut img = Iw44Image::new();
4404 let header = [0x00u8, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00];
4405 img.decode_chunk(&header).expect("first chunk must decode");
4406
4407 assert!(matches!(
4410 img.decode_chunk(&[0x03, 0x04]),
4411 Err(Iw44Error::UnexpectedSerial)
4412 ));
4413 }
4414
4415 #[test]
4419 fn iw44_decode_chunk_rejects_serial_repeat() {
4420 let mut img = Iw44Image::new();
4421 let header = [0x00u8, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00];
4422 img.decode_chunk(&header).expect("first chunk must decode");
4423 img.decode_chunk(&[0x01, 0x04])
4424 .expect("serial=1 refinement must decode");
4425
4426 assert!(matches!(
4429 img.decode_chunk(&[0x01, 0x04]),
4430 Err(Iw44Error::UnexpectedSerial)
4431 ));
4432 }
4433
4434 #[test]
4438 fn iw44_decode_chunk_serial_check_does_not_regress_zpshort_tolerance() {
4439 let mut img = Iw44Image::new();
4440 let header = [0x00u8, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00];
4441 img.decode_chunk(&header).expect("first chunk must decode");
4442 assert!(img.decode_chunk(&[0x01, 0x04]).is_ok());
4445 assert!(img.decode_chunk(&[0x02, 0x04]).is_ok());
4446 assert!(img.to_rgb().is_ok());
4447 }
4448
4449 #[test]
4451 fn iw44_new_to_rgb_without_data_returns_error() {
4452 let img = Iw44Image::new();
4453 assert!(matches!(img.to_rgb(), Err(Iw44Error::MissingCodec)));
4454 }
4455
4456 #[test]
4458 fn iw44_new_subsample_zero_rejected() {
4459 let img = Iw44Image::new();
4460 assert!(matches!(
4461 img.to_rgb_subsample(0),
4462 Err(Iw44Error::InvalidSubsample)
4463 ));
4464 }
4465
4466 #[test]
4477 fn reconstruct_band_matches_the_whole_plane() {
4478 let data = std::fs::read(assets_path().join("carte.djvu")).expect("carte.djvu");
4479 let file = djvu_iff::parse(&data).expect("parse");
4480 let mut img = Iw44Image::new();
4481 for c in &extract_bg44_chunks(&file) {
4482 img.decode_chunk(c).expect("decode_chunk");
4483 }
4484 let y = img.y.as_ref().expect("luma plane");
4485 let block_rows = y.height.div_ceil(32);
4486 assert!(
4487 block_rows >= 2 * BAND_HALO_BLOCKS + 2,
4488 "the fixture must be tall enough to hold an interior band"
4489 );
4490 let full = y.reconstruct(1);
4491
4492 for keep in [1usize, 2, 4] {
4493 let mut first = 0;
4494 while first < block_rows {
4495 let last = (first + keep).min(block_rows);
4496 let lo = first.saturating_sub(BAND_HALO_BLOCKS);
4497 let hi = (last + BAND_HALO_BLOCKS).min(block_rows);
4498 let band = y.reconstruct_band(lo, hi);
4499 for r in first * 32..(last * 32).min(y.height) {
4500 let a = &full.data[r * full.stride..r * full.stride + y.width];
4501 let b = &band.data[(r - lo * 32) * band.stride..][..y.width];
4502 assert_eq!(
4503 a, b,
4504 "band [{lo}..{hi}) block rows, keeping [{first}..{last}): \
4505 image row {r} differs from the whole-plane transform"
4506 );
4507 }
4508 first = last;
4509 }
4510 }
4511 }
4512
4513 #[test]
4523 fn banded_rgb_matches_whole_plane_rgb() {
4524 for asset in ["carte.djvu", "chicken.djvu", "colorbook.djvu"] {
4525 let data = std::fs::read(assets_path().join(asset)).expect("asset");
4526 let file = djvu_iff::parse(&data).expect("parse");
4527 let chunks = extract_bg44_chunks(&file);
4528 if chunks.is_empty() {
4529 continue;
4530 }
4531 let mut img = Iw44Image::new();
4532 for c in &chunks {
4533 img.decode_chunk(c).expect("decode_chunk");
4534 }
4535 if !img.is_color {
4536 continue;
4537 }
4538 let whole = img.to_rgb().expect("to_rgb");
4539
4540 let y_dec = img.y.as_ref().unwrap();
4541 let cb_dec = img.cb.as_ref().unwrap();
4542 let cr_dec = img.cr.as_ref().unwrap();
4543 let (pw, ph) = (img.width as usize, img.height as usize);
4544 for keep in [1usize, 3, 8] {
4545 let mut banded = Pixmap::try_new(img.width, img.height, 0, 0, 0, 255)
4546 .expect("fits the pixmap limit");
4547 img.rgb_sub1_banded(y_dec, cb_dec, cr_dec, keep, pw, ph, &mut banded);
4548 assert_eq!(
4549 banded.data, whole.data,
4550 "{asset}: banded conversion keeping {keep} block rows per \
4551 band differs from the whole-plane conversion"
4552 );
4553 }
4554 }
4555 }
4556
4557 #[test]
4562 fn rgb_rows_match_the_whole_picture() {
4563 for asset in ["carte.djvu", "chicken.djvu", "colorbook.djvu"] {
4564 let data = std::fs::read(assets_path().join(asset)).expect("asset");
4565 let file = djvu_iff::parse(&data).expect("parse");
4566 let chunks = extract_bg44_chunks(&file);
4567 if chunks.is_empty() {
4568 continue;
4569 }
4570 let mut img = Iw44Image::new();
4571 for c in &chunks {
4572 img.decode_chunk(c).expect("decode_chunk");
4573 }
4574 if !img.is_color {
4575 continue;
4576 }
4577 let whole = img.to_rgb().expect("to_rgb");
4578 let (w, h) = (img.width, img.height);
4579 let stride = w as usize * 4;
4580 assert!(h > 40, "{asset}: fixture must be taller than one test band");
4581 assert!(
4582 img.rgb_band_rows().is_none(),
4583 "{asset}: a small picture must not ask to be banded"
4584 );
4585
4586 let mut ranges = vec![0..h, 0..1, h - 1..h, 37..h - 5, 3..4];
4587 let mut o = 0;
4588 while o < h {
4589 ranges.push(o..(o + 37).min(h));
4590 o += 37;
4591 }
4592 for r in ranges {
4593 let band = img.rgb_rows(r.clone()).expect("rgb_rows");
4594 assert_eq!((band.width, band.height), (w, r.end - r.start));
4595 assert_eq!(
4596 band.data,
4597 &whole.data[r.start as usize * stride..r.end as usize * stride],
4598 "{asset}: rows {r:?} differ from the whole-picture conversion"
4599 );
4600 }
4601
4602 let empty = img.rgb_rows(5..5).expect("an empty range is fine");
4603 assert_eq!((empty.width, empty.height), (w, 0));
4604 assert!(matches!(img.rgb_rows(0..h + 1), Err(Iw44Error::Invalid)));
4605 assert!(matches!(img.rgb_rows(7..6), Err(Iw44Error::Invalid)));
4606 }
4607 }
4608
4609 #[test]
4610 fn iw44_new_decode_boy_bg() {
4611 let data = std::fs::read(assets_path().join("boy.djvu")).expect("boy.djvu not found");
4612 let file = djvu_iff::parse(&data).expect("failed to parse boy.djvu");
4613 let chunks = extract_bg44_chunks(&file);
4614 assert_eq!(chunks.len(), 1, "expected 1 BG44 chunk in boy.djvu");
4615
4616 let mut img = Iw44Image::new();
4617 for c in &chunks {
4618 img.decode_chunk(c).expect("decode_chunk failed");
4619 }
4620 assert_eq!(img.width, 192);
4621 assert_eq!(img.height, 256);
4622
4623 let pm = img.to_rgb().expect("to_rgb failed");
4624 assert_ppm_match(&pm.to_ppm(), "boy_bg.ppm");
4625 }
4626
4627 #[test]
4628 fn iw44_new_decode_chicken_bg() {
4629 let data =
4630 std::fs::read(assets_path().join("chicken.djvu")).expect("chicken.djvu not found");
4631 let file = djvu_iff::parse(&data).expect("failed to parse chicken.djvu");
4632 let chunks = extract_bg44_chunks(&file);
4633 assert_eq!(chunks.len(), 3, "expected 3 BG44 chunks in chicken.djvu");
4634
4635 let mut img = Iw44Image::new();
4636 for c in &chunks {
4637 img.decode_chunk(c).expect("decode_chunk failed");
4638 }
4639 assert_eq!(img.width, 181);
4640 assert_eq!(img.height, 240);
4641
4642 let pm = img.to_rgb().expect("to_rgb failed");
4643 assert_ppm_match(&pm.to_ppm(), "chicken_bg.ppm");
4644 }
4645
4646 #[test]
4649 fn iw44_to_gray8_matches_rgb_luma_boy() {
4650 let data = std::fs::read(assets_path().join("boy.djvu")).expect("boy.djvu not found");
4651 let file = djvu_iff::parse(&data).expect("failed to parse boy.djvu");
4652 let chunks = extract_bg44_chunks(&file);
4653 let mut img = Iw44Image::new();
4654 for c in &chunks {
4655 img.decode_chunk(c).expect("decode_chunk failed");
4656 }
4657
4658 let rgb = img.to_rgb().expect("to_rgb failed");
4659 let rgb_gray = rgb.to_gray8();
4660 let direct = img.to_gray8().expect("to_gray8 failed");
4661
4662 assert_eq!(direct.width, rgb_gray.width);
4663 assert_eq!(direct.height, rgb_gray.height);
4664 assert_eq!(direct.data.len(), rgb_gray.data.len());
4665
4666 if img.is_color {
4667 let mut sum = 0u64;
4669 let mut max = 0u8;
4670 for (a, b) in direct.data.iter().zip(rgb_gray.data.iter()) {
4671 let d = a.abs_diff(*b);
4672 sum += d as u64;
4673 max = max.max(d);
4674 }
4675 let mean = sum as f64 / direct.data.len() as f64;
4676 assert!(mean < 4.0, "mean gray diff {mean} too high");
4677 assert!(max <= 24, "max gray diff {max} too high");
4678 } else {
4679 assert_eq!(direct.data, rgb_gray.data, "gray path must be exact");
4681 }
4682 }
4683
4684 #[test]
4686 fn iw44_to_gray8_subsample_dims() {
4687 let data = std::fs::read(assets_path().join("boy.djvu")).expect("boy.djvu not found");
4688 let file = djvu_iff::parse(&data).expect("failed to parse boy.djvu");
4689 let chunks = extract_bg44_chunks(&file);
4690 let mut img = Iw44Image::new();
4691 for c in &chunks {
4692 img.decode_chunk(c).expect("decode_chunk failed");
4693 }
4694 for sub in [2u32, 4u32] {
4695 let g = img.to_gray8_subsample(sub).expect("gray sub");
4696 let rgb = img.to_rgb_subsample(sub).expect("rgb sub");
4697 assert_eq!((g.width, g.height), (rgb.width, rgb.height));
4698 assert_eq!(g.data.len(), (g.width * g.height) as usize);
4699 }
4700 assert!(matches!(
4701 img.to_gray8_subsample(0),
4702 Err(Iw44Error::InvalidSubsample)
4703 ));
4704 }
4705
4706 #[test]
4712 fn iw44_new_decode_boy_sub2() {
4713 let data = std::fs::read(assets_path().join("boy.djvu")).expect("boy.djvu not found");
4714 let file = djvu_iff::parse(&data).expect("failed to parse boy.djvu");
4715 let chunks = extract_bg44_chunks(&file);
4716
4717 let mut img = Iw44Image::new();
4718 for c in &chunks {
4719 img.decode_chunk(c).expect("decode_chunk failed");
4720 }
4721 assert_eq!(img.width, 192);
4722 assert_eq!(img.height, 256);
4723
4724 let pm = img.to_rgb_subsample(2).expect("to_rgb_subsample(2) failed");
4725 assert_eq!(pm.width, 96, "sub=2 width must be ceil(192/2)");
4726 assert_eq!(pm.height, 128, "sub=2 height must be ceil(256/2)");
4727
4728 assert_or_create_golden(&pm.to_ppm(), "boy_bg_sub2.ppm");
4729 }
4730
4731 #[test]
4735 fn iw44_new_decode_big_scanned_sub2() {
4736 let data = std::fs::read(assets_path().join("big-scanned-page.djvu"))
4737 .expect("big-scanned-page.djvu not found");
4738 let file = djvu_iff::parse(&data).expect("failed to parse big-scanned-page.djvu");
4739 let chunks = extract_bg44_chunks(&file);
4740
4741 let mut img = Iw44Image::new();
4742 for c in &chunks {
4743 img.decode_chunk(c).expect("decode_chunk failed");
4744 }
4745 assert_eq!(img.width, 6780);
4746 assert_eq!(img.height, 9148);
4747
4748 let pm = img.to_rgb_subsample(2).expect("to_rgb_subsample(2) failed");
4749 assert_eq!(pm.width, 3390, "sub=2 width must be ceil(6780/2)");
4750 assert_eq!(pm.height, 4574, "sub=2 height must be ceil(9148/2)");
4751
4752 assert_or_create_golden(&pm.to_ppm(), "big_scanned_sub2.ppm");
4753 }
4754
4755 #[test]
4756 fn iw44_new_decode_big_scanned_sub4() {
4757 let data = std::fs::read(assets_path().join("big-scanned-page.djvu"))
4758 .expect("big-scanned-page.djvu not found");
4759 let file = djvu_iff::parse(&data).expect("failed to parse big-scanned-page.djvu");
4760 let chunks = extract_bg44_chunks(&file);
4761 assert_eq!(chunks.len(), 4, "expected 4 BG44 chunks");
4762
4763 let mut img = Iw44Image::new();
4764 for c in &chunks {
4765 img.decode_chunk(c).expect("decode_chunk failed");
4766 }
4767 assert_eq!(img.width, 6780);
4768 assert_eq!(img.height, 9148);
4769
4770 let pm = img.to_rgb_subsample(4).expect("to_rgb_subsample failed");
4771 assert_ppm_match(&pm.to_ppm(), "big_scanned_sub4.ppm");
4772 }
4773
4774 fn extract_bg44_chunks_per_page(file: &djvu_iff::DjvuFile) -> Vec<Vec<&[u8]>> {
4778 fn walk<'a>(chunk: &'a djvu_iff::Chunk, out: &mut Vec<Vec<&'a [u8]>>) {
4779 if let djvu_iff::Chunk::Form {
4780 secondary_id,
4781 children,
4782 ..
4783 } = chunk
4784 {
4785 if secondary_id == b"DJVU" {
4786 let bg = children
4787 .iter()
4788 .filter_map(|c| match c {
4789 djvu_iff::Chunk::Leaf {
4790 id: [b'B', b'G', b'4', b'4'],
4791 data,
4792 } => Some(data.as_slice()),
4793 _ => None,
4794 })
4795 .collect::<Vec<_>>();
4796 out.push(bg);
4797 return;
4798 }
4799 for c in children {
4800 walk(c, out);
4801 }
4802 }
4803 }
4804 let mut out = Vec::new();
4805 walk(&file.root, &mut out);
4806 out
4807 }
4808
4809 #[test]
4822 fn iw44_colorbook_page2_decodes_all_slices_no_early_exit() {
4823 let data =
4824 std::fs::read(assets_path().join("colorbook.djvu")).expect("colorbook.djvu not found");
4825 let file = djvu_iff::parse(&data).expect("failed to parse colorbook.djvu");
4826 let pages = extract_bg44_chunks_per_page(&file);
4827 let chunks = &pages[2];
4828 assert_eq!(chunks.len(), 4, "colorbook page 2 must have 4 BG44 chunks");
4829
4830 let mut img = Iw44Image::new();
4831 for c in chunks {
4832 img.decode_chunk(c).expect("decode_chunk failed");
4833 }
4834 assert_eq!((img.width, img.height), (739, 1213));
4835
4836 let pm = img.to_rgb().expect("to_rgb failed");
4837 assert_or_create_golden(&pm.to_ppm(), "colorbook_bg_p2.ppm");
4838 }
4839
4840 #[test]
4843 fn iw44_new_progressive_matches_full_decode_chicken() {
4844 let data =
4845 std::fs::read(assets_path().join("chicken.djvu")).expect("chicken.djvu not found");
4846 let file = djvu_iff::parse(&data).expect("failed to parse");
4847 let chunks = extract_bg44_chunks(&file);
4848 assert!(
4849 chunks.len() > 1,
4850 "need multiple chunks for progressive test"
4851 );
4852
4853 let mut full = Iw44Image::new();
4855 for c in &chunks {
4856 full.decode_chunk(c).expect("full decode failed");
4857 }
4858 let full_pm = full.to_rgb().expect("full to_rgb failed");
4859
4860 let mut prog = Iw44Image::new();
4862 for c in chunks.iter().take(1) {
4863 prog.decode_chunk(c).expect("progressive decode failed");
4864 }
4865 for c in chunks.iter().skip(1) {
4866 prog.decode_chunk(c).expect("progressive decode failed");
4867 }
4868 let prog_pm = prog.to_rgb().expect("progressive to_rgb failed");
4869
4870 assert_eq!(
4871 full_pm.data, prog_pm.data,
4872 "progressive and full decode must produce identical pixels"
4873 );
4874 }
4875
4876 #[test]
4883 fn carte_v12_allocates_full_size_chroma_planes() {
4884 let data = std::fs::read(assets_path().join("carte.djvu")).expect("carte.djvu not found");
4885 let file = djvu_iff::parse(&data).expect("iff parse");
4886 let chunks = extract_bg44_chunks(&file);
4887 assert!(!chunks.is_empty(), "carte.djvu must have BG44 chunks");
4888
4889 let mut img = Iw44Image::new();
4890 img.decode_chunk(chunks[0]).expect("decode_chunk");
4891
4892 assert!(img.is_color(), "carte.djvu must be a color image");
4893 assert!(
4894 !img.chroma_half(),
4895 "v1.2 chroma planes must stay full resolution"
4896 );
4897 let (cw, ch) = img
4898 .chroma_plane_dims()
4899 .expect("chroma plane must be allocated after first color chunk");
4900 let lw = img.width as usize;
4901 let lh = img.height as usize;
4902 let expected_w = lw;
4903 let expected_h = lh;
4904 assert_eq!(
4905 cw, expected_w,
4906 "chroma plane width must equal luma_w={expected_w}, got {cw}"
4907 );
4908 assert_eq!(
4909 ch, expected_h,
4910 "chroma plane height must equal luma_h={expected_h}, got {ch}"
4911 );
4912 }
4913
4914 #[test]
4918 fn iw44_new_decode_carte_bg_full_chroma() {
4919 let data = std::fs::read(assets_path().join("carte.djvu")).expect("carte.djvu not found");
4920 let file = djvu_iff::parse(&data).expect("iff parse");
4921 let chunks = extract_bg44_chunks(&file);
4922
4923 let mut img = Iw44Image::new();
4924 for c in &chunks {
4925 img.decode_chunk(c).expect("decode_chunk failed");
4926 }
4927 assert_eq!(img.width, 1400);
4928 assert_eq!(img.height, 852);
4929
4930 let pm = img.to_rgb().expect("to_rgb failed");
4931 let hash = pm.data.iter().fold(0xcbf29ce484222325u64, |hash, &byte| {
4932 (hash ^ u64::from(byte)).wrapping_mul(0x100000001b3)
4933 });
4934 assert_eq!(
4935 hash, 0x2118_2ba8_f124_2124,
4936 "carte full-chroma pixel digest"
4937 );
4938 }
4939
4940 #[test]
4943 fn test_decode_empty_chunk() {
4944 let mut img = Iw44Image::new();
4945 let result = img.decode_chunk(&[]);
4946 assert!(result.is_err());
4947 }
4948
4949 #[test]
4950 fn test_decode_truncated_header() {
4951 let mut img = Iw44Image::new();
4952 let result = img.decode_chunk(&[0x00, 0x01]);
4954 assert!(result.is_err());
4955 }
4956
4957 #[test]
4958 fn test_to_rgb_before_decode() {
4959 let img = Iw44Image::new();
4960 let result = img.to_rgb();
4962 assert!(result.is_err());
4963 }
4964
4965 #[test]
4966 fn test_to_rgb_subsample_zero() {
4967 let img = Iw44Image::new();
4968 let result = img.to_rgb_subsample(0);
4969 assert!(result.is_err());
4970 }
4971
4972 #[test]
4976 fn simd_ycbcr_row_matches_scalar() {
4977 let n = 20usize;
4979 let ys: Vec<i32> = (0..n).map(|i| (i as i32 * 7) % 200 - 100).collect();
4980 let bs: Vec<i32> = (0..n).map(|i| (i as i32 * 13) % 200 - 100).collect();
4981 let rs: Vec<i32> = (0..n).map(|i| (i as i32 * 17) % 200 - 100).collect();
4982
4983 let mut expected = vec![0u8; n * 4];
4985 for col in 0..n {
4986 let y = ys[col];
4987 let b = bs[col];
4988 let r = rs[col];
4989 let t2 = r + (r >> 1);
4990 let t3 = y + 128 - (b >> 2);
4991 expected[col * 4] = (y + 128 + t2).clamp(0, 255) as u8;
4992 expected[col * 4 + 1] = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
4993 expected[col * 4 + 2] = (t3 + (b << 1)).clamp(0, 255) as u8;
4994 expected[col * 4 + 3] = 255;
4995 }
4996
4997 let mut actual = vec![0u8; n * 4];
4999 super::ycbcr_row_to_rgba(&ys, &bs, &rs, &mut actual);
5000
5001 assert_eq!(
5002 expected, actual,
5003 "SIMD must produce identical output to scalar"
5004 );
5005 }
5006
5007 #[test]
5009 fn simd_ycbcr_row_clamps_correctly() {
5010 let n = 8usize;
5011 let ys: Vec<i32> = vec![127, -128, 127, -128, 0, 0, 0, 0];
5013 let bs: Vec<i32> = vec![-128, 127, -128, 127, 0, 0, 0, 0];
5014 let rs: Vec<i32> = vec![127, -128, -128, 127, 0, 0, 0, 0];
5015
5016 let mut simd_out = vec![0u8; n * 4];
5017 super::ycbcr_row_to_rgba(&ys, &bs, &rs, &mut simd_out);
5018
5019 for chunk in simd_out.as_chunks::<4>().0 {
5021 assert_eq!(chunk[3], 255, "alpha must always be 255");
5022 }
5023 }
5024
5025 #[test]
5030 fn simd_render_matches_subsampled_render_dimensions() {
5031 let data = std::fs::read(assets_path().join("boy.djvu")).expect("boy.djvu not found");
5032 let file = djvu_iff::parse(&data).expect("parse failed");
5033 let chunks = extract_bg44_chunks(&file);
5034
5035 let mut img = Iw44Image::new();
5036 for c in &chunks {
5037 img.decode_chunk(c).expect("decode_chunk failed");
5038 }
5039
5040 let full = img.to_rgb().expect("to_rgb failed");
5042 let half = img.to_rgb_subsample(2).expect("subsample(2) failed");
5044
5045 assert_eq!(full.width, img.width);
5046 assert_eq!(full.height, img.height);
5047 assert_eq!(half.width, img.width.div_ceil(2));
5048 assert_eq!(half.height, img.height.div_ceil(2));
5049 }
5051
5052 #[test]
5059 fn simd_row_pass_matches_scalar() {
5060 let width = 32usize;
5061 let height = 16usize;
5062 let stride = width;
5063 let n = stride * height;
5064
5065 let initial: Vec<i16> = (0..n).map(|i| ((i * 7 + 13) % 511) as i16 - 255).collect();
5067
5068 let mut scalar_data = initial.clone();
5069 super::row_pass_inner(&mut scalar_data, width, height, stride, 1, 0, false);
5071
5072 let mut simd_data = initial.clone();
5073 super::row_pass_inner(&mut simd_data, width, height, stride, 1, 0, true);
5075
5076 assert_eq!(
5077 scalar_data, simd_data,
5078 "SIMD row pass must produce identical output to scalar"
5079 );
5080 }
5081
5082 #[test]
5088 fn simd_row_pass_s2_matches_scalar() {
5089 let width = 64usize;
5090 let height = 32usize;
5091 let stride = width;
5092 let n = stride * height;
5093 let s = 2usize;
5094 let sd = 1usize;
5095
5096 let initial: Vec<i16> = (0..n).map(|i| ((i * 7 + 13) % 511) as i16 - 255).collect();
5097
5098 let mut scalar_data = initial.clone();
5099 super::row_pass_inner(&mut scalar_data, width, height, stride, s, sd, false);
5100
5101 let mut simd_data = initial.clone();
5102 super::row_pass_inner(&mut simd_data, width, height, stride, s, sd, true);
5103
5104 assert_eq!(
5105 scalar_data, simd_data,
5106 "SIMD row pass (s=2) must produce identical output to scalar"
5107 );
5108 }
5109
5110 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5113 fn ycbcr_raw_scalar(y: &[i16], cb: &[i16], cr: &[i16], out: &mut [u8]) {
5114 let w = y.len();
5115 for col in 0..w {
5116 let yn = super::normalize(y[col]);
5117 let bn = super::normalize(cb[col]);
5118 let rn = super::normalize(cr[col]);
5119 let t2 = rn + (rn >> 1);
5120 let t3 = yn + 128 - (bn >> 2);
5121 out[col * 4] = (yn + 128 + t2).clamp(0, 255) as u8;
5122 out[col * 4 + 1] = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
5123 out[col * 4 + 2] = (t3 + (bn << 1)).clamp(0, 255) as u8;
5124 out[col * 4 + 3] = 255;
5125 }
5126 }
5127
5128 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5129 fn ycbcr_raw_half_scalar(y: &[i16], cb: &[i16], cr: &[i16], out: &mut [u8]) {
5130 let w = y.len();
5131 for col in 0..w {
5132 let yn = super::normalize(y[col]);
5133 let bn = super::normalize(cb[col / 2]);
5134 let rn = super::normalize(cr[col / 2]);
5135 let t2 = rn + (rn >> 1);
5136 let t3 = yn + 128 - (bn >> 2);
5137 out[col * 4] = (yn + 128 + t2).clamp(0, 255) as u8;
5138 out[col * 4 + 1] = (t3 - (t2 >> 1)).clamp(0, 255) as u8;
5139 out[col * 4 + 2] = (t3 + (bn << 1)).clamp(0, 255) as u8;
5140 out[col * 4 + 3] = 255;
5141 }
5142 }
5143
5144 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5148 #[test]
5149 fn ycbcr_avx2_raw_matches_scalar() {
5150 if !std::is_x86_feature_detected!("avx2") {
5151 eprintln!("skipping: AVX2 not available on this host");
5152 return;
5153 }
5154 let raw_vals: [i16; 8] = [-32768, -8192, -64, -1, 0, 63, 8191, 32767];
5156 for &width in &[1usize, 7, 16, 17, 31, 32, 33, 47, 48, 64, 100] {
5157 let n = width;
5158 let make_seq = |seed: usize| -> Vec<i16> {
5159 (0..n)
5160 .map(|i| raw_vals[(i + seed) % raw_vals.len()])
5161 .collect()
5162 };
5163 let y = make_seq(0);
5164 let cb = make_seq(3);
5165 let cr = make_seq(5);
5166
5167 let mut got = vec![0u8; n * 4];
5168 #[allow(unsafe_code)]
5169 unsafe {
5170 super::ycbcr_avx2_raw(y.as_ptr(), cb.as_ptr(), cr.as_ptr(), got.as_mut_ptr(), n);
5171 }
5172
5173 let mut want = vec![0u8; n * 4];
5174 ycbcr_raw_scalar(&y, &cb, &cr, &mut want);
5175
5176 assert_eq!(got, want, "AVX2 raw mismatch at width {}", width);
5177 }
5178 }
5179
5180 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5183 #[test]
5184 fn load8s_s1_avx2_matches_scalar() {
5185 if !std::is_x86_feature_detected!("avx2") {
5186 eprintln!("skipping: AVX2 not available on this host");
5187 return;
5188 }
5189 let raw_vals: [i16; 8] = [-32768, -8192, -64, -1, 0, 63, 8191, 32767];
5190 let n = 64;
5191 let buf: Vec<i16> = (0..n).map(|i| raw_vals[i % raw_vals.len()]).collect();
5192 for phys_off in 0..(n - 8) {
5193 #[allow(unsafe_code)]
5194 let got = unsafe { super::load8s_s1_avx2(&buf, phys_off) };
5195 let want = super::load8s(&buf, phys_off, 1);
5196 assert_eq!(
5197 got.to_array(),
5198 want.to_array(),
5199 "AVX2 load8s_s1 mismatch at phys_off {}",
5200 phys_off
5201 );
5202 }
5203 }
5204
5205 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5208 #[test]
5209 fn store8s_s1_avx2_matches_scalar() {
5210 if !std::is_x86_feature_detected!("avx2") {
5211 eprintln!("skipping: AVX2 not available on this host");
5212 return;
5213 }
5214 let raw_vals: [i32; 8] = [i32::MIN, -100_000, -32768, -1, 0, 32767, 100_000, i32::MAX];
5217 for offset in 0..8usize {
5218 let mut input = [0i32; 8];
5219 for j in 0..8 {
5220 input[j] = raw_vals[(j + offset) % 8];
5221 }
5222 let v = wide::i32x8::from(input);
5223
5224 let mut buf_avx2 = vec![0xABCDu16 as i16; 32];
5226 #[allow(unsafe_code)]
5227 unsafe {
5228 super::store8s_s1_avx2(&mut buf_avx2, 8, v);
5229 }
5230 let mut buf_scalar = vec![0xABCDu16 as i16; 32];
5233 for j in 0..8 {
5234 buf_scalar[8 + j] = input[j] as i16;
5235 }
5236 assert_eq!(buf_avx2, buf_scalar, "AVX2 store8s_s1 mismatch");
5237 }
5238 }
5239
5240 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5243 #[test]
5244 fn prelim_flags_bucket_avx2_matches_scalar() {
5245 if !std::is_x86_feature_detected!("avx2") {
5246 eprintln!("skipping: AVX2 not available on this host");
5247 return;
5248 }
5249 let test_vectors: &[[i16; 16]] = &[
5251 [0; 16],
5252 [
5253 1, 0, -1, 0, 100, 0, -200, 0, 0, 1234, 0, -1234, 0, 32767, -32768, 0,
5254 ],
5255 [1; 16],
5256 [-1; 16],
5257 [
5258 32767, -32768, 1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 32767, -32768, 1, -1,
5259 ],
5260 ];
5261 for &coefs in test_vectors {
5262 let mut bucket_avx2 = [0u8; 16];
5263 #[allow(unsafe_code)]
5264 let bstate_avx2 = unsafe { super::prelim_flags_bucket_avx2(&coefs, &mut bucket_avx2) };
5265
5266 let mut bucket_scalar = [0u8; 16];
5267 let mut bstate_scalar = 0u8;
5268 for k in 0..16 {
5269 let f = if coefs[k] == 0 {
5270 super::UNK
5271 } else {
5272 super::ACTIVE
5273 };
5274 bucket_scalar[k] = f;
5275 bstate_scalar |= f;
5276 }
5277
5278 assert_eq!(
5279 bucket_avx2, bucket_scalar,
5280 "bucket mismatch for coefs={coefs:?}"
5281 );
5282 assert_eq!(
5283 bstate_avx2, bstate_scalar,
5284 "bstatetmp mismatch for coefs={coefs:?}"
5285 );
5286 }
5287 }
5288
5289 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5293 #[test]
5294 fn prelim_flags_band0_avx2_matches_scalar() {
5295 if !std::is_x86_feature_detected!("avx2") {
5296 eprintln!("skipping: AVX2 not available on this host");
5297 return;
5298 }
5299 let old_patterns: &[[u8; 16]] = &[
5301 [super::ZERO; 16],
5302 [super::UNK; 16],
5303 [super::ACTIVE; 16],
5304 [
5305 super::ZERO,
5306 super::UNK,
5307 super::ACTIVE,
5308 super::ZERO,
5309 super::UNK,
5310 super::ACTIVE,
5311 super::ZERO,
5312 super::UNK,
5313 super::ACTIVE,
5314 super::ZERO,
5315 super::UNK,
5316 super::ACTIVE,
5317 super::ZERO,
5318 super::UNK,
5319 super::ACTIVE,
5320 super::ZERO,
5321 ],
5322 ];
5323 let coef_patterns: &[[i16; 16]] = &[
5324 [0; 16],
5325 [1; 16],
5326 [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
5327 [
5328 -32768, 0, 32767, 0, 100, 0, -100, 0, 0, 1, 0, -1, 0, 5, 0, -5,
5329 ],
5330 ];
5331
5332 for &old in old_patterns {
5333 for &coefs in coef_patterns {
5334 let mut flags_avx2 = old;
5335 #[allow(unsafe_code)]
5336 let bstate_avx2 =
5337 unsafe { super::prelim_flags_band0_avx2(&coefs, &mut flags_avx2) };
5338
5339 let mut flags_scalar = old;
5340 let mut bstate_scalar = 0u8;
5341 for k in 0..16 {
5342 if flags_scalar[k] != super::ZERO {
5343 flags_scalar[k] = if coefs[k] == 0 {
5344 super::UNK
5345 } else {
5346 super::ACTIVE
5347 };
5348 }
5349 bstate_scalar |= flags_scalar[k];
5350 }
5351
5352 assert_eq!(
5353 flags_avx2, flags_scalar,
5354 "flags mismatch old={old:?} coefs={coefs:?}"
5355 );
5356 assert_eq!(bstate_avx2, bstate_scalar, "bstatetmp mismatch");
5357 }
5358 }
5359 }
5360
5361 #[cfg(all(target_arch = "x86_64", feature = "std"))]
5362 #[test]
5363 fn ycbcr_avx2_raw_half_matches_scalar() {
5364 if !std::is_x86_feature_detected!("avx2") {
5365 eprintln!("skipping: AVX2 not available on this host");
5366 return;
5367 }
5368 let raw_vals: [i16; 8] = [-32768, -8192, -64, -1, 0, 63, 8191, 32767];
5369 for &width in &[2usize, 8, 16, 18, 30, 32, 34, 48, 64, 96] {
5370 let n = width;
5371 let half = n.div_ceil(2);
5372 let make_seq = |seed: usize, len: usize| -> Vec<i16> {
5373 (0..len)
5374 .map(|i| raw_vals[(i + seed) % raw_vals.len()])
5375 .collect()
5376 };
5377 let y = make_seq(0, n);
5378 let cb_half = make_seq(3, half);
5379 let cr_half = make_seq(5, half);
5380
5381 let mut got = vec![0u8; n * 4];
5382 #[allow(unsafe_code)]
5383 unsafe {
5384 super::ycbcr_avx2_raw_half(
5385 y.as_ptr(),
5386 cb_half.as_ptr(),
5387 cr_half.as_ptr(),
5388 got.as_mut_ptr(),
5389 n,
5390 );
5391 }
5392
5393 let mut want = vec![0u8; n * 4];
5394 ycbcr_raw_half_scalar(&y, &cb_half, &cr_half, &mut want);
5395
5396 assert_eq!(got, want, "AVX2 raw_half mismatch at width {}", width);
5397 }
5398 }
5399
5400 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
5406 #[test]
5407 fn load8s_s1_simd128_matches_scalar() {
5408 let raw_vals: [i16; 8] = [-32768, -8192, -64, -1, 0, 63, 8191, 32767];
5409 let n = 64;
5410 let buf: alloc::vec::Vec<i16> = (0..n).map(|i| raw_vals[i % raw_vals.len()]).collect();
5411 for phys_off in 0..(n - 8) {
5412 #[allow(unsafe_code)]
5413 let got = unsafe { super::load8s_s1_simd128(&buf, phys_off) };
5414 let want = super::load8s(&buf, phys_off, 1);
5415 assert_eq!(
5416 got.to_array(),
5417 want.to_array(),
5418 "simd128 load8s_s1 mismatch at phys_off {}",
5419 phys_off
5420 );
5421 }
5422 }
5423
5424 #[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
5429 #[test]
5430 fn store8s_s1_simd128_matches_scalar() {
5431 let raw_vals: [i32; 8] = [i32::MIN, -100_000, -32768, -1, 0, 32767, 100_000, i32::MAX];
5432 for offset in 0..8usize {
5433 let mut input = [0i32; 8];
5434 for j in 0..8 {
5435 input[j] = raw_vals[(j + offset) % 8];
5436 }
5437 let v = wide::i32x8::from(input);
5438
5439 let mut buf_simd128 = alloc::vec![0xABCDu16 as i16; 32];
5440 #[allow(unsafe_code)]
5441 unsafe {
5442 super::store8s_s1_simd128(&mut buf_simd128, 8, v);
5443 }
5444 let mut buf_scalar = alloc::vec![0xABCDu16 as i16; 32];
5445 for j in 0..8 {
5446 buf_scalar[8 + j] = input[j] as i16;
5447 }
5448 assert_eq!(buf_simd128, buf_scalar, "simd128 store8s_s1 mismatch");
5449 }
5450 }
5451}