1#![forbid(unsafe_code)]
8#![allow(clippy::unreadable_literal)]
9#![allow(clippy::items_after_statements)]
10#![allow(clippy::unnecessary_wraps)]
11#![allow(clippy::struct_excessive_bools)]
12#![allow(clippy::identity_op)]
13#![allow(clippy::range_plus_one)]
14#![allow(clippy::needless_range_loop)]
15#![allow(clippy::useless_conversion)]
16#![allow(clippy::redundant_closure_for_method_calls)]
17#![allow(clippy::single_match_else)]
18#![allow(dead_code)]
19#![allow(clippy::doc_markdown)]
20#![allow(clippy::unused_self)]
21#![allow(clippy::trivially_copy_pass_by_ref)]
22#![allow(clippy::cast_possible_truncation)]
23#![allow(clippy::cast_sign_loss)]
24#![allow(clippy::cast_possible_wrap)]
25#![allow(clippy::missing_errors_doc)]
26#![allow(clippy::too_many_arguments)]
27#![allow(clippy::similar_names)]
28#![allow(clippy::many_single_char_names)]
29#![allow(clippy::cast_precision_loss)]
30#![allow(clippy::cast_lossless)]
31
32use super::pipeline::FrameContext;
33use super::{FrameBuffer, PlaneBuffer, PlaneType, ReconstructResult};
34
35pub const CDEF_BLOCK_SIZE: usize = 8;
41
42pub const CDEF_NUM_DIRECTIONS: usize = 8;
44
45pub const CDEF_MAX_PRIMARY: u8 = 15;
47
48pub const CDEF_MAX_SECONDARY: u8 = 4;
50
51pub const CDEF_DAMPING_MIN: u8 = 3;
53
54pub const CDEF_DAMPING_MAX: u8 = 6;
56
57pub const CDEF_SEC_STRENGTHS: [u8; 4] = [0, 1, 2, 4];
59
60const CDEF_PRIMARY_OFFSETS: [[(i8, i8); 2]; CDEF_NUM_DIRECTIONS] = [
66 [(-1, 0), (1, 0)], [(-1, -1), (1, 1)], [(0, -1), (0, 1)], [(1, -1), (-1, 1)], [(0, -1), (0, 1)], [(-1, -1), (1, 1)], [(-1, 0), (1, 0)], [(1, -1), (-1, 1)], ];
75
76const CDEF_SECONDARY_OFFSETS: [[(i8, i8); 4]; CDEF_NUM_DIRECTIONS] = [
78 [(-2, 0), (2, 0), (-1, -1), (1, 1)],
79 [(-2, -1), (2, 1), (-1, -2), (1, 2)],
80 [(-1, -1), (1, 1), (-1, -2), (1, 2)],
81 [(2, -1), (-2, 1), (1, -2), (-1, 2)],
82 [(0, -2), (0, 2), (-1, -1), (1, 1)],
83 [(-2, -1), (2, 1), (-1, -2), (1, 2)],
84 [(-1, -1), (1, 1), (-2, 0), (2, 0)],
85 [(2, -1), (-2, 1), (1, -2), (-1, 2)],
86];
87
88#[derive(Clone, Copy, Debug, Default)]
94pub struct CdefBlockConfig {
95 pub primary_strength: u8,
97 pub secondary_strength: u8,
99 pub damping: u8,
101 pub direction: u8,
103 pub skip: bool,
105}
106
107impl CdefBlockConfig {
108 #[must_use]
110 pub const fn new(primary: u8, secondary: u8, damping: u8) -> Self {
111 Self {
112 primary_strength: primary,
113 secondary_strength: secondary,
114 damping,
115 direction: 0,
116 skip: false,
117 }
118 }
119
120 #[must_use]
122 pub fn secondary_value(&self) -> u8 {
123 CDEF_SEC_STRENGTHS
124 .get(self.secondary_strength as usize)
125 .copied()
126 .unwrap_or(0)
127 }
128
129 #[must_use]
131 pub const fn is_enabled(&self) -> bool {
132 !self.skip && (self.primary_strength > 0 || self.secondary_strength > 0)
133 }
134}
135
136#[derive(Clone, Debug, Default)]
142pub struct CdefFilterResult {
143 pub direction: u8,
145 pub variance: u32,
147 pub pixels_modified: u32,
149}
150
151fn detect_direction(block: &[i16], stride: usize) -> (u8, u32) {
157 let mut best_dir = 0u8;
158 let mut best_var = u32::MAX;
159
160 const DIR_KERNELS: [[i8; 8]; 8] = [
162 [1, 1, 1, 1, -1, -1, -1, -1], [1, 1, 1, 0, 0, -1, -1, -1], [1, 1, 0, -1, -1, 0, 1, 1], [-1, -1, -1, 0, 0, 1, 1, 1], [-1, -1, -1, -1, 1, 1, 1, 1], [-1, -1, 0, 1, 1, 0, -1, -1], [-1, -1, -1, -1, 1, 1, 1, 1], [1, 1, 1, 0, 0, -1, -1, -1], ];
171
172 for (dir, kernel) in DIR_KERNELS.iter().enumerate() {
173 let mut sum: i32 = 0;
174 let mut sum_sq: i32 = 0;
175
176 for row in 0..8 {
177 for col in 0..8 {
178 let pixel = i32::from(block[row * stride + col]);
179 let weight = i32::from(kernel[(row + col) % 8]);
180 let val = pixel * weight;
181 sum += val;
182 sum_sq += val * val;
183 }
184 }
185
186 let variance = (sum_sq - (sum * sum / 64)).unsigned_abs();
188
189 if variance < best_var {
190 best_var = variance;
191 best_dir = dir as u8;
192 }
193 }
194
195 (best_dir, best_var)
196}
197
198fn constrain(diff: i16, threshold: i16, damping: u8) -> i16 {
204 if threshold == 0 {
205 return 0;
206 }
207
208 let sign = if diff < 0 { -1i16 } else { 1i16 };
209 let abs_diff = diff.abs();
210 let abs_thresh = threshold.abs();
211
212 let damping_shift = damping.saturating_sub(abs_thresh as u8);
213
214 if damping_shift >= 15 {
215 return 0;
216 }
217
218 let clamped = abs_diff.min(abs_thresh);
219 let adjusted = clamped - (clamped >> damping_shift);
220
221 sign * adjusted
222}
223
224fn filter_block(
226 src: &[i16],
227 src_stride: usize,
228 dst: &mut [i16],
229 dst_stride: usize,
230 config: &CdefBlockConfig,
231 bd: u8,
232) {
233 let max_val = (1i16 << bd) - 1;
234 let primary = i16::from(config.primary_strength);
235 let secondary = i16::from(config.secondary_value());
236 let damping = config.damping;
237 let dir = config.direction as usize;
238
239 for row in 0..CDEF_BLOCK_SIZE {
240 for col in 0..CDEF_BLOCK_SIZE {
241 let src_idx = row * src_stride + col;
242 let dst_idx = row * dst_stride + col;
243
244 let center = src[src_idx];
245 let mut sum: i32 = 0;
246
247 if primary > 0 {
249 for &(dx, dy) in &CDEF_PRIMARY_OFFSETS[dir] {
250 let nx = col as i32 + i32::from(dx);
251 let ny = row as i32 + i32::from(dy);
252
253 if nx >= 0
254 && nx < CDEF_BLOCK_SIZE as i32
255 && ny >= 0
256 && ny < CDEF_BLOCK_SIZE as i32
257 {
258 let neighbor_idx = ny as usize * src_stride + nx as usize;
259 let neighbor = src[neighbor_idx];
260 let diff = neighbor - center;
261 sum += i32::from(constrain(diff, primary, damping)) * 2;
262 }
263 }
264 }
265
266 if secondary > 0 {
268 for &(dx, dy) in &CDEF_SECONDARY_OFFSETS[dir] {
269 let nx = col as i32 + i32::from(dx);
270 let ny = row as i32 + i32::from(dy);
271
272 if nx >= 0
273 && nx < CDEF_BLOCK_SIZE as i32
274 && ny >= 0
275 && ny < CDEF_BLOCK_SIZE as i32
276 {
277 let neighbor_idx = ny as usize * src_stride + nx as usize;
278 let neighbor = src[neighbor_idx];
279 let diff = neighbor - center;
280 sum += i32::from(constrain(diff, secondary, damping));
281 }
282 }
283 }
284
285 let filtered = i32::from(center) + ((sum + 8) >> 4);
287 dst[dst_idx] = (filtered as i16).clamp(0, max_val);
288 }
289 }
290}
291
292#[derive(Debug)]
298pub struct CdefApplicator {
299 width: u32,
301 height: u32,
303 bit_depth: u8,
305 damping_y: u8,
307 damping_uv: u8,
309 y_presets: Vec<(u8, u8)>,
311 uv_presets: Vec<(u8, u8)>,
313 temp_buffer: Vec<i16>,
315}
316
317impl CdefApplicator {
318 #[must_use]
320 pub fn new(width: u32, height: u32, bit_depth: u8) -> Self {
321 let buffer_size = CDEF_BLOCK_SIZE * CDEF_BLOCK_SIZE;
322
323 Self {
324 width,
325 height,
326 bit_depth,
327 damping_y: CDEF_DAMPING_MIN,
328 damping_uv: CDEF_DAMPING_MIN,
329 y_presets: vec![(0, 0); 8],
330 uv_presets: vec![(0, 0); 8],
331 temp_buffer: vec![0i16; buffer_size],
332 }
333 }
334
335 pub fn set_damping(&mut self, y_damping: u8, uv_damping: u8) {
337 self.damping_y = y_damping.clamp(CDEF_DAMPING_MIN, CDEF_DAMPING_MAX);
338 self.damping_uv = uv_damping.clamp(CDEF_DAMPING_MIN, CDEF_DAMPING_MAX);
339 }
340
341 pub fn set_y_preset(&mut self, index: usize, primary: u8, secondary: u8) {
343 if index < self.y_presets.len() {
344 self.y_presets[index] = (primary.min(CDEF_MAX_PRIMARY), secondary.min(3));
345 }
346 }
347
348 pub fn set_uv_preset(&mut self, index: usize, primary: u8, secondary: u8) {
350 if index < self.uv_presets.len() {
351 self.uv_presets[index] = (primary.min(CDEF_MAX_PRIMARY), secondary.min(3));
352 }
353 }
354
355 #[must_use]
357 pub fn damping(&self, plane: PlaneType) -> u8 {
358 match plane {
359 PlaneType::Y => self.damping_y,
360 PlaneType::U | PlaneType::V => self.damping_uv,
361 }
362 }
363
364 #[must_use]
366 pub fn adjusted_damping(&self, plane: PlaneType) -> u8 {
367 let base = self.damping(plane);
368 base + self.bit_depth.saturating_sub(8).min(4)
369 }
370
371 pub fn apply(
377 &mut self,
378 frame: &mut FrameBuffer,
379 _context: &FrameContext,
380 ) -> ReconstructResult<()> {
381 let bd = frame.bit_depth();
382
383 self.apply_to_plane(frame.y_plane_mut(), PlaneType::Y, bd)?;
385
386 if let Some(u) = frame.u_plane_mut() {
388 self.apply_to_plane(u, PlaneType::U, bd)?;
389 }
390 if let Some(v) = frame.v_plane_mut() {
391 self.apply_to_plane(v, PlaneType::V, bd)?;
392 }
393
394 Ok(())
395 }
396
397 fn apply_to_plane(
399 &mut self,
400 plane: &mut PlaneBuffer,
401 plane_type: PlaneType,
402 bd: u8,
403 ) -> ReconstructResult<()> {
404 let width = plane.width() as usize;
405 let height = plane.height() as usize;
406 let stride = plane.stride();
407
408 let damping = self.adjusted_damping(plane_type);
409
410 let blocks_x = width / CDEF_BLOCK_SIZE;
412 let blocks_y = height / CDEF_BLOCK_SIZE;
413
414 for by in 0..blocks_y {
415 for bx in 0..blocks_x {
416 let x = bx * CDEF_BLOCK_SIZE;
417 let y = by * CDEF_BLOCK_SIZE;
418
419 let block_start = y * stride + x;
421 let block_data = &plane.data()[block_start..];
422
423 let (direction, _variance) = detect_direction(block_data, stride);
425
426 let (primary, secondary) = match plane_type {
428 PlaneType::Y => self.y_presets.first().copied().unwrap_or((0, 0)),
429 _ => self.uv_presets.first().copied().unwrap_or((0, 0)),
430 };
431
432 if primary == 0 && secondary == 0 {
434 continue;
435 }
436
437 let config = CdefBlockConfig {
439 primary_strength: primary,
440 secondary_strength: secondary,
441 damping,
442 direction,
443 skip: false,
444 };
445
446 filter_block(
448 block_data,
449 stride,
450 &mut self.temp_buffer,
451 CDEF_BLOCK_SIZE,
452 &config,
453 bd,
454 );
455
456 let plane_data = plane.data_mut();
458 for row in 0..CDEF_BLOCK_SIZE {
459 for col in 0..CDEF_BLOCK_SIZE {
460 let src_idx = row * CDEF_BLOCK_SIZE + col;
461 let dst_idx = (y + row) * stride + (x + col);
462 plane_data[dst_idx] = self.temp_buffer[src_idx];
463 }
464 }
465 }
466 }
467
468 Ok(())
469 }
470
471 pub fn filter_single_block(
473 &mut self,
474 plane: &mut PlaneBuffer,
475 x: u32,
476 y: u32,
477 config: &CdefBlockConfig,
478 ) -> CdefFilterResult {
479 let bd = plane.bit_depth();
480 let stride = plane.stride();
481
482 let block_start = y as usize * stride + x as usize;
484 let block_data = &plane.data()[block_start..];
485
486 let (direction, variance) = if config.direction == 0 {
488 detect_direction(block_data, stride)
489 } else {
490 (config.direction, 0)
491 };
492
493 let mut actual_config = *config;
494 actual_config.direction = direction;
495
496 if !actual_config.is_enabled() {
497 return CdefFilterResult {
498 direction,
499 variance,
500 pixels_modified: 0,
501 };
502 }
503
504 filter_block(
506 block_data,
507 stride,
508 &mut self.temp_buffer,
509 CDEF_BLOCK_SIZE,
510 &actual_config,
511 bd,
512 );
513
514 let mut pixels_modified = 0u32;
516 let plane_data = plane.data_mut();
517
518 for row in 0..CDEF_BLOCK_SIZE {
519 for col in 0..CDEF_BLOCK_SIZE {
520 let src_idx = row * CDEF_BLOCK_SIZE + col;
521 let dst_idx = (y as usize + row) * stride + (x as usize + col);
522
523 if plane_data[dst_idx] != self.temp_buffer[src_idx] {
524 pixels_modified += 1;
525 }
526 plane_data[dst_idx] = self.temp_buffer[src_idx];
527 }
528 }
529
530 CdefFilterResult {
531 direction,
532 variance,
533 pixels_modified,
534 }
535 }
536}
537
538#[cfg(test)]
543mod tests {
544 use super::*;
545 use crate::reconstruct::ChromaSubsampling;
546
547 #[test]
548 fn test_cdef_block_config() {
549 let config = CdefBlockConfig::new(8, 2, 4);
550 assert_eq!(config.primary_strength, 8);
551 assert_eq!(config.secondary_strength, 2);
552 assert_eq!(config.secondary_value(), 2);
553 assert!(config.is_enabled());
554
555 let config_disabled = CdefBlockConfig::new(0, 0, 4);
556 assert!(!config_disabled.is_enabled());
557 }
558
559 #[test]
560 fn test_cdef_secondary_values() {
561 let mut config = CdefBlockConfig::new(0, 0, 4);
562 assert_eq!(config.secondary_value(), 0);
563
564 config.secondary_strength = 1;
565 assert_eq!(config.secondary_value(), 1);
566
567 config.secondary_strength = 2;
568 assert_eq!(config.secondary_value(), 2);
569
570 config.secondary_strength = 3;
571 assert_eq!(config.secondary_value(), 4);
572 }
573
574 #[test]
575 fn test_constrain() {
576 assert_eq!(constrain(10, 0, 3), 0);
578
579 let result = constrain(5, 10, 3);
581 assert!(result >= 0 && result <= 5);
582
583 let result = constrain(-5, 10, 3);
585 assert!(result <= 0 && result >= -5);
586 }
587
588 #[test]
589 fn test_detect_direction() {
590 let block = vec![128i16; 64];
592 let (dir, var) = detect_direction(&block, 8);
593 assert!(dir < 8); let _ = var; }
596
597 #[test]
598 fn test_cdef_applicator_creation() {
599 let applicator = CdefApplicator::new(1920, 1080, 8);
600 assert_eq!(applicator.width, 1920);
601 assert_eq!(applicator.height, 1080);
602 assert_eq!(applicator.bit_depth, 8);
603 }
604
605 #[test]
606 fn test_cdef_applicator_damping() {
607 let mut applicator = CdefApplicator::new(64, 64, 8);
608 applicator.set_damping(4, 5);
609
610 assert_eq!(applicator.damping(PlaneType::Y), 4);
611 assert_eq!(applicator.damping(PlaneType::U), 5);
612 assert_eq!(applicator.damping(PlaneType::V), 5);
613 }
614
615 #[test]
616 fn test_cdef_applicator_adjusted_damping() {
617 let applicator = CdefApplicator::new(64, 64, 10);
618 let adj_damping = applicator.adjusted_damping(PlaneType::Y);
619
620 assert_eq!(adj_damping, CDEF_DAMPING_MIN + 2);
622 }
623
624 #[test]
625 fn test_cdef_applicator_presets() {
626 let mut applicator = CdefApplicator::new(64, 64, 8);
627 applicator.set_y_preset(0, 8, 2);
628 applicator.set_uv_preset(0, 4, 1);
629
630 assert_eq!(applicator.y_presets[0], (8, 2));
631 assert_eq!(applicator.uv_presets[0], (4, 1));
632 }
633
634 #[test]
635 fn test_cdef_applicator_apply() {
636 let mut frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
637 let context = FrameContext::new(64, 64);
638
639 let mut applicator = CdefApplicator::new(64, 64, 8);
640 applicator.set_y_preset(0, 4, 1);
641
642 let result = applicator.apply(&mut frame, &context);
643 assert!(result.is_ok());
644 }
645
646 #[test]
647 fn test_filter_block() {
648 let src = vec![128i16; 64];
649 let mut dst = vec![0i16; 64];
650
651 let config = CdefBlockConfig::new(4, 1, 4);
652
653 filter_block(&src, 8, &mut dst, 8, &config, 8);
654
655 for &val in &dst {
657 assert!((val - 128).abs() < 10);
658 }
659 }
660
661 #[test]
662 fn test_filter_single_block() {
663 let mut frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
664
665 for y in 0..8 {
667 for x in 0..8 {
668 frame.y_plane_mut().set(x, y, 128);
669 }
670 }
671
672 let mut applicator = CdefApplicator::new(64, 64, 8);
673 let config = CdefBlockConfig::new(4, 1, 4);
674
675 let result = applicator.filter_single_block(frame.y_plane_mut(), 0, 0, &config);
676
677 assert!(result.direction < CDEF_NUM_DIRECTIONS as u8);
678 }
679
680 #[test]
681 fn test_constants() {
682 assert_eq!(CDEF_BLOCK_SIZE, 8);
683 assert_eq!(CDEF_NUM_DIRECTIONS, 8);
684 assert_eq!(CDEF_MAX_PRIMARY, 15);
685 assert_eq!(CDEF_MAX_SECONDARY, 4);
686 assert_eq!(CDEF_DAMPING_MIN, 3);
687 assert_eq!(CDEF_DAMPING_MAX, 6);
688 }
689}