1#![forbid(unsafe_code)]
26#![allow(dead_code)]
27#![allow(clippy::doc_markdown)]
28#![allow(clippy::unused_self)]
29#![allow(clippy::cast_possible_truncation)]
30#![allow(clippy::trivially_copy_pass_by_ref)]
31#![allow(clippy::match_same_arms)]
32#![allow(clippy::struct_excessive_bools)]
33#![allow(clippy::struct_field_names)]
34#![allow(clippy::missing_errors_doc)]
35#![allow(clippy::manual_div_ceil)]
36
37use super::sequence::SequenceHeader;
38use crate::error::{CodecError, CodecResult};
39use oximedia_io::BitReader;
40
41pub const CDEF_MAX_BITS: u8 = 3;
47
48pub const CDEF_MAX_PRESETS: usize = 8;
50
51pub const CDEF_MAX_PRIMARY_STRENGTH: u8 = 15;
53
54pub const CDEF_MAX_SECONDARY_STRENGTH: u8 = 4;
56
57pub const CDEF_BLOCK_SIZE: usize = 8;
59
60pub const CDEF_NUM_DIRECTIONS: usize = 8;
62
63pub const CDEF_DAMPING_MIN: u8 = 3;
65
66pub const CDEF_DAMPING_MAX: u8 = 6;
68
69pub const CDEF_SEC_STRENGTHS: [u8; 4] = [0, 1, 2, 4];
71
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
78pub struct CdefStrength {
79 pub primary: u8,
81 pub secondary: u8,
83}
84
85impl CdefStrength {
86 #[must_use]
88 pub const fn new(primary: u8, secondary: u8) -> Self {
89 Self { primary, secondary }
90 }
91
92 #[must_use]
94 pub fn secondary_value(&self) -> u8 {
95 CDEF_SEC_STRENGTHS
96 .get(self.secondary as usize)
97 .copied()
98 .unwrap_or(0)
99 }
100
101 #[must_use]
103 pub fn is_enabled(&self) -> bool {
104 self.primary > 0 || self.secondary > 0
105 }
106
107 #[allow(clippy::cast_possible_truncation)]
109 pub fn parse(reader: &mut BitReader<'_>) -> CodecResult<Self> {
110 let primary = reader.read_bits(4).map_err(CodecError::Core)? as u8;
111 let secondary = reader.read_bits(2).map_err(CodecError::Core)? as u8;
112 Ok(Self { primary, secondary })
113 }
114}
115
116#[derive(Clone, Debug)]
118pub struct CdefParams {
119 pub damping_y: u8,
121 pub damping_uv: u8,
123 pub bits: u8,
125 pub y_strengths: [CdefStrength; CDEF_MAX_PRESETS],
127 pub uv_strengths: [CdefStrength; CDEF_MAX_PRESETS],
129}
130
131impl Default for CdefParams {
132 fn default() -> Self {
133 Self {
134 damping_y: CDEF_DAMPING_MIN,
135 damping_uv: CDEF_DAMPING_MIN,
136 bits: 0,
137 y_strengths: [CdefStrength::default(); CDEF_MAX_PRESETS],
138 uv_strengths: [CdefStrength::default(); CDEF_MAX_PRESETS],
139 }
140 }
141}
142
143impl CdefParams {
144 #[must_use]
146 pub fn new() -> Self {
147 Self::default()
148 }
149
150 #[allow(clippy::cast_possible_truncation)]
156 pub fn parse(reader: &mut BitReader<'_>, seq: &SequenceHeader) -> CodecResult<Self> {
157 let mut cdef = Self::new();
158 let num_planes = if seq.color_config.mono_chrome { 1 } else { 3 };
159
160 let damping_minus_3 = reader.read_bits(2).map_err(CodecError::Core)? as u8;
162 cdef.damping_y = damping_minus_3 + CDEF_DAMPING_MIN;
163 cdef.damping_uv = cdef.damping_y;
164
165 cdef.bits = reader.read_bits(2).map_err(CodecError::Core)? as u8;
167
168 let num_presets = 1usize << cdef.bits;
170 for i in 0..num_presets {
171 cdef.y_strengths[i] = CdefStrength::parse(reader)?;
172 }
173
174 if num_planes > 1 {
176 for i in 0..num_presets {
177 cdef.uv_strengths[i] = CdefStrength::parse(reader)?;
178 }
179 }
180
181 Ok(cdef)
182 }
183
184 #[must_use]
186 pub fn num_presets(&self) -> usize {
187 1usize << self.bits
188 }
189
190 #[must_use]
192 pub fn is_enabled(&self) -> bool {
193 let num = self.num_presets();
194 for i in 0..num {
195 if self.y_strengths[i].is_enabled() || self.uv_strengths[i].is_enabled() {
196 return true;
197 }
198 }
199 false
200 }
201
202 #[must_use]
204 pub fn get_y_strength(&self, idx: usize) -> CdefStrength {
205 self.y_strengths.get(idx).copied().unwrap_or_default()
206 }
207
208 #[must_use]
210 pub fn get_uv_strength(&self, idx: usize) -> CdefStrength {
211 self.uv_strengths.get(idx).copied().unwrap_or_default()
212 }
213
214 #[must_use]
216 pub const fn get_damping(&self, plane: usize) -> u8 {
217 if plane == 0 {
218 self.damping_y
219 } else {
220 self.damping_uv
221 }
222 }
223
224 #[must_use]
226 pub fn adjusted_damping(&self, plane: usize, bit_depth: u8) -> u8 {
227 let base = self.get_damping(plane);
228 base + (bit_depth - 8).min(4)
230 }
231
232 #[must_use]
234 pub fn is_valid_preset(&self, idx: usize) -> bool {
235 idx < self.num_presets()
236 }
237}
238
239#[derive(Clone, Copy, Debug, Default)]
241pub struct CdefDirection {
242 pub direction: u8,
244 pub variance: u32,
246}
247
248impl CdefDirection {
249 #[must_use]
251 pub const fn new(direction: u8, variance: u32) -> Self {
252 Self {
253 direction,
254 variance,
255 }
256 }
257
258 #[must_use]
260 pub const fn opposite(&self) -> u8 {
261 (self.direction + 4) % CDEF_NUM_DIRECTIONS as u8
262 }
263
264 #[must_use]
266 pub const fn perpendicular_cw(&self) -> u8 {
267 (self.direction + 2) % CDEF_NUM_DIRECTIONS as u8
268 }
269
270 #[must_use]
272 pub const fn perpendicular_ccw(&self) -> u8 {
273 (self.direction + 6) % CDEF_NUM_DIRECTIONS as u8
274 }
275}
276
277#[derive(Clone, Copy, Debug)]
281pub struct CdefDirectionOffsets {
282 pub primary: [(i8, i8); 2],
284 pub secondary: [(i8, i8); 4],
286}
287
288pub const CDEF_DIRECTION_OFFSETS: [CdefDirectionOffsets; CDEF_NUM_DIRECTIONS] = [
290 CdefDirectionOffsets {
292 primary: [(-1, 0), (1, 0)],
293 secondary: [(-2, 0), (2, 0), (-1, -1), (1, 1)],
294 },
295 CdefDirectionOffsets {
297 primary: [(-1, -1), (1, 1)],
298 secondary: [(-2, -1), (2, 1), (-1, -2), (1, 2)],
299 },
300 CdefDirectionOffsets {
302 primary: [(0, -1), (0, 1)],
303 secondary: [(-1, -1), (1, 1), (-1, -2), (1, 2)],
304 },
305 CdefDirectionOffsets {
307 primary: [(1, -1), (-1, 1)],
308 secondary: [(2, -1), (-2, 1), (1, -2), (-1, 2)],
309 },
310 CdefDirectionOffsets {
312 primary: [(0, -1), (0, 1)],
313 secondary: [(0, -2), (0, 2), (-1, -1), (1, 1)],
314 },
315 CdefDirectionOffsets {
317 primary: [(-1, -1), (1, 1)],
318 secondary: [(-2, -1), (2, 1), (-1, -2), (1, 2)],
319 },
320 CdefDirectionOffsets {
322 primary: [(-1, 0), (1, 0)],
323 secondary: [(-1, -1), (1, 1), (-2, 0), (2, 0)],
324 },
325 CdefDirectionOffsets {
327 primary: [(1, -1), (-1, 1)],
328 secondary: [(2, -1), (-2, 1), (1, -2), (-1, 2)],
329 },
330];
331
332#[derive(Clone, Copy, Debug)]
334pub struct CdefTapWeights {
335 pub primary: i16,
337 pub secondary: i16,
339}
340
341impl CdefTapWeights {
342 #[must_use]
344 pub fn from_strengths(strength: &CdefStrength, _damping: u8) -> Self {
345 let primary = i16::from(strength.primary);
347 let secondary = i16::from(strength.secondary_value());
348
349 Self { primary, secondary }
350 }
351}
352
353#[derive(Clone, Debug, Default)]
355pub struct CdefBlockState {
356 pub preset_idx: u8,
358 pub skip: bool,
360 pub direction: CdefDirection,
362}
363
364impl CdefBlockState {
365 #[must_use]
367 pub const fn new(preset_idx: u8) -> Self {
368 Self {
369 preset_idx,
370 skip: false,
371 direction: CdefDirection {
372 direction: 0,
373 variance: 0,
374 },
375 }
376 }
377
378 #[must_use]
380 pub const fn should_filter(&self) -> bool {
381 !self.skip
382 }
383}
384
385#[derive(Clone, Debug)]
387pub struct CdefSuperblock {
388 pub block_indices: Vec<u8>,
391 pub sb_size: usize,
393}
394
395impl CdefSuperblock {
396 #[must_use]
398 pub fn new(sb_size: usize) -> Self {
399 let num_blocks = (sb_size / CDEF_BLOCK_SIZE) * (sb_size / CDEF_BLOCK_SIZE);
400 Self {
401 block_indices: vec![0; num_blocks],
402 sb_size,
403 }
404 }
405
406 #[must_use]
408 pub fn get_index(&self, row: usize, col: usize) -> u8 {
409 let blocks_per_row = self.sb_size / CDEF_BLOCK_SIZE;
410 let idx = row * blocks_per_row + col;
411 self.block_indices.get(idx).copied().unwrap_or(0)
412 }
413
414 pub fn set_index(&mut self, row: usize, col: usize, value: u8) {
416 let blocks_per_row = self.sb_size / CDEF_BLOCK_SIZE;
417 let idx = row * blocks_per_row + col;
418 if idx < self.block_indices.len() {
419 self.block_indices[idx] = value;
420 }
421 }
422
423 #[must_use]
425 pub fn num_blocks(&self) -> usize {
426 self.block_indices.len()
427 }
428
429 #[must_use]
431 pub fn blocks_per_side(&self) -> usize {
432 self.sb_size / CDEF_BLOCK_SIZE
433 }
434}
435
436#[derive(Clone, Debug)]
438pub struct CdefFrameConfig {
439 pub params: CdefParams,
441 pub bit_depth: u8,
443 pub width: u32,
445 pub height: u32,
447}
448
449impl CdefFrameConfig {
450 #[must_use]
452 pub fn new(params: CdefParams, bit_depth: u8, width: u32, height: u32) -> Self {
453 Self {
454 params,
455 bit_depth,
456 width,
457 height,
458 }
459 }
460
461 #[must_use]
463 pub fn blocks_wide(&self) -> u32 {
464 (self.width + (CDEF_BLOCK_SIZE as u32) - 1) / (CDEF_BLOCK_SIZE as u32)
465 }
466
467 #[must_use]
469 pub fn blocks_high(&self) -> u32 {
470 (self.height + (CDEF_BLOCK_SIZE as u32) - 1) / (CDEF_BLOCK_SIZE as u32)
471 }
472
473 #[must_use]
475 pub fn total_blocks(&self) -> u32 {
476 self.blocks_wide() * self.blocks_high()
477 }
478
479 #[must_use]
481 pub fn get_damping(&self, plane: usize) -> u8 {
482 self.params.adjusted_damping(plane, self.bit_depth)
483 }
484}
485
486#[must_use]
492pub fn constrain(diff: i16, threshold: i16, damping: u8) -> i16 {
493 if threshold == 0 {
494 return 0;
495 }
496
497 #[allow(clippy::cast_possible_truncation)]
498 let shift = damping.saturating_sub(threshold.unsigned_abs() as u8);
499 let magnitude = diff.abs().min(threshold.abs());
500
501 if shift >= 15 {
502 0
503 } else {
504 let clamped = magnitude - (magnitude >> shift);
505 if diff < 0 {
506 -clamped
507 } else {
508 clamped
509 }
510 }
511}
512
513#[must_use]
515pub const fn cdef_clip(value: i16, max: i16) -> i16 {
516 if value < 0 {
517 0
518 } else if value > max {
519 max
520 } else {
521 value
522 }
523}
524
525#[cfg(test)]
530mod tests {
531 use super::*;
532
533 #[test]
534 fn test_cdef_strength_default() {
535 let s = CdefStrength::default();
536 assert_eq!(s.primary, 0);
537 assert_eq!(s.secondary, 0);
538 assert!(!s.is_enabled());
539 }
540
541 #[test]
542 fn test_cdef_strength_new() {
543 let s = CdefStrength::new(10, 2);
544 assert_eq!(s.primary, 10);
545 assert_eq!(s.secondary, 2);
546 assert!(s.is_enabled());
547 }
548
549 #[test]
550 fn test_cdef_strength_secondary_value() {
551 assert_eq!(CdefStrength::new(0, 0).secondary_value(), 0);
552 assert_eq!(CdefStrength::new(0, 1).secondary_value(), 1);
553 assert_eq!(CdefStrength::new(0, 2).secondary_value(), 2);
554 assert_eq!(CdefStrength::new(0, 3).secondary_value(), 4);
555 }
556
557 #[test]
558 fn test_cdef_params_default() {
559 let params = CdefParams::default();
560 assert_eq!(params.damping_y, CDEF_DAMPING_MIN);
561 assert_eq!(params.damping_uv, CDEF_DAMPING_MIN);
562 assert_eq!(params.bits, 0);
563 assert_eq!(params.num_presets(), 1);
564 }
565
566 #[test]
567 fn test_cdef_params_num_presets() {
568 let mut params = CdefParams::default();
569 assert_eq!(params.num_presets(), 1);
570
571 params.bits = 1;
572 assert_eq!(params.num_presets(), 2);
573
574 params.bits = 2;
575 assert_eq!(params.num_presets(), 4);
576
577 params.bits = 3;
578 assert_eq!(params.num_presets(), 8);
579 }
580
581 #[test]
582 fn test_cdef_params_is_enabled() {
583 let mut params = CdefParams::default();
584 assert!(!params.is_enabled());
585
586 params.y_strengths[0].primary = 5;
587 assert!(params.is_enabled());
588
589 params.y_strengths[0].primary = 0;
590 params.uv_strengths[0].secondary = 2;
591 assert!(params.is_enabled());
592 }
593
594 #[test]
595 fn test_cdef_params_get_damping() {
596 let params = CdefParams {
597 damping_y: 5,
598 damping_uv: 4,
599 ..Default::default()
600 };
601
602 assert_eq!(params.get_damping(0), 5);
603 assert_eq!(params.get_damping(1), 4);
604 assert_eq!(params.get_damping(2), 4);
605 }
606
607 #[test]
608 fn test_cdef_params_adjusted_damping() {
609 let params = CdefParams {
610 damping_y: 3,
611 damping_uv: 3,
612 ..Default::default()
613 };
614
615 assert_eq!(params.adjusted_damping(0, 8), 3);
616 assert_eq!(params.adjusted_damping(0, 10), 5);
617 assert_eq!(params.adjusted_damping(0, 12), 7);
618 }
619
620 #[test]
621 fn test_cdef_params_valid_preset() {
622 let mut params = CdefParams::default();
623 params.bits = 2;
624
625 assert!(params.is_valid_preset(0));
626 assert!(params.is_valid_preset(3));
627 assert!(!params.is_valid_preset(4));
628 }
629
630 #[test]
631 fn test_cdef_direction() {
632 let dir = CdefDirection::new(2, 100);
633 assert_eq!(dir.direction, 2);
634 assert_eq!(dir.variance, 100);
635 assert_eq!(dir.opposite(), 6);
636 assert_eq!(dir.perpendicular_cw(), 4);
637 assert_eq!(dir.perpendicular_ccw(), 0);
638 }
639
640 #[test]
641 fn test_cdef_direction_wrap() {
642 let dir = CdefDirection::new(6, 0);
643 assert_eq!(dir.opposite(), 2);
644 assert_eq!(dir.perpendicular_cw(), 0);
645 assert_eq!(dir.perpendicular_ccw(), 4);
646 }
647
648 #[test]
649 fn test_cdef_block_state() {
650 let state = CdefBlockState::new(3);
651 assert_eq!(state.preset_idx, 3);
652 assert!(!state.skip);
653 assert!(state.should_filter());
654
655 let mut state2 = CdefBlockState::new(0);
656 state2.skip = true;
657 assert!(!state2.should_filter());
658 }
659
660 #[test]
661 fn test_cdef_superblock() {
662 let mut sb = CdefSuperblock::new(64);
663 assert_eq!(sb.num_blocks(), 64);
664 assert_eq!(sb.blocks_per_side(), 8);
665
666 sb.set_index(2, 3, 5);
667 assert_eq!(sb.get_index(2, 3), 5);
668 assert_eq!(sb.get_index(0, 0), 0);
669 }
670
671 #[test]
672 fn test_cdef_superblock_128() {
673 let sb = CdefSuperblock::new(128);
674 assert_eq!(sb.num_blocks(), 256);
675 assert_eq!(sb.blocks_per_side(), 16);
676 }
677
678 #[test]
679 fn test_cdef_frame_config() {
680 let params = CdefParams::default();
681 let config = CdefFrameConfig::new(params, 8, 1920, 1080);
682
683 assert_eq!(config.blocks_wide(), 240);
684 assert_eq!(config.blocks_high(), 135);
685 assert_eq!(config.total_blocks(), 240 * 135);
686 }
687
688 #[test]
689 fn test_constrain() {
690 assert_eq!(constrain(10, 0, 3), 0);
692
693 let result = constrain(5, 10, 3);
695 assert!(result >= 0 && result <= 5);
696
697 let result = constrain(-5, 10, 3);
699 assert!(result <= 0 && result >= -5);
700 }
701
702 #[test]
703 fn test_cdef_clip() {
704 assert_eq!(cdef_clip(-5, 255), 0);
705 assert_eq!(cdef_clip(100, 255), 100);
706 assert_eq!(cdef_clip(300, 255), 255);
707 }
708
709 #[test]
710 fn test_cdef_direction_offsets() {
711 let offsets = &CDEF_DIRECTION_OFFSETS[0];
713 assert_eq!(offsets.primary[0], (-1, 0));
714 assert_eq!(offsets.primary[1], (1, 0));
715
716 let offsets = &CDEF_DIRECTION_OFFSETS[4];
718 assert_eq!(offsets.primary[0], (0, -1));
719 assert_eq!(offsets.primary[1], (0, 1));
720 }
721
722 #[test]
723 fn test_cdef_tap_weights() {
724 let strength = CdefStrength::new(8, 2);
725 let weights = CdefTapWeights::from_strengths(&strength, 3);
726 assert_eq!(weights.primary, 8);
727 assert_eq!(weights.secondary, 2);
728 }
729
730 #[test]
731 fn test_constants() {
732 assert_eq!(CDEF_MAX_BITS, 3);
733 assert_eq!(CDEF_MAX_PRESETS, 8);
734 assert_eq!(CDEF_BLOCK_SIZE, 8);
735 assert_eq!(CDEF_NUM_DIRECTIONS, 8);
736 assert_eq!(CDEF_DAMPING_MIN, 3);
737 assert_eq!(CDEF_DAMPING_MAX, 6);
738 }
739
740 #[test]
741 fn test_sec_strengths_table() {
742 assert_eq!(CDEF_SEC_STRENGTHS, [0, 1, 2, 4]);
743 }
744}