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#![allow(clippy::needless_bool)]
32#![allow(clippy::collapsible_if)]
33#![allow(clippy::if_not_else)]
34
35use super::pipeline::FrameContext;
36use super::{FrameBuffer, PlaneBuffer, PlaneType, ReconstructResult};
37
38pub const MAX_LOOP_FILTER_LEVEL: u8 = 63;
44
45pub const MAX_SHARPNESS_LEVEL: u8 = 7;
47
48pub const NARROW_FILTER_TAPS: usize = 4;
50
51pub const WIDE_FILTER_TAPS: usize = 8;
53
54pub const EXTRA_WIDE_FILTER_TAPS: usize = 14;
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum FilterDirection {
64 Vertical,
66 Horizontal,
68}
69
70impl FilterDirection {
71 #[must_use]
73 pub const fn perpendicular(self) -> Self {
74 match self {
75 Self::Vertical => Self::Horizontal,
76 Self::Horizontal => Self::Vertical,
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, Default)]
87pub struct EdgeFilter {
88 pub level: u8,
90 pub limit: u8,
92 pub threshold: u8,
94 pub hev_threshold: u8,
96 pub filter_size: u8,
98}
99
100impl EdgeFilter {
101 #[must_use]
103 pub fn new(level: u8, sharpness: u8) -> Self {
104 let limit = Self::compute_limit(level, sharpness);
105 let threshold = limit >> 1;
106 let hev_threshold = Self::compute_hev_threshold(level);
107
108 Self {
109 level,
110 limit,
111 threshold,
112 hev_threshold,
113 filter_size: 4,
114 }
115 }
116
117 fn compute_limit(level: u8, sharpness: u8) -> u8 {
119 if sharpness > 0 {
120 let block_limit = (9u8).saturating_sub(sharpness).max(1);
121 let shift = (sharpness + 3) >> 2;
122 let shifted = level >> shift;
123 shifted.min(block_limit).max(1)
124 } else {
125 level.max(1)
126 }
127 }
128
129 const fn compute_hev_threshold(level: u8) -> u8 {
131 if level >= 40 {
132 2
133 } else if level >= 20 {
134 1
135 } else {
136 0
137 }
138 }
139
140 #[must_use]
142 pub const fn should_filter(&self) -> bool {
143 self.level > 0
144 }
145
146 #[must_use]
148 pub fn is_flat(&self, p: &[i16], q: &[i16]) -> bool {
149 if p.len() < 4 || q.len() < 4 {
150 return false;
151 }
152
153 let flat_threshold = i16::from(self.threshold);
154
155 for i in 1..4 {
157 if (p[i] - p[0]).abs() > flat_threshold {
158 return false;
159 }
160 }
161
162 for i in 1..4 {
164 if (q[i] - q[0]).abs() > flat_threshold {
165 return false;
166 }
167 }
168
169 true
170 }
171
172 #[must_use]
174 pub fn is_flat2(&self, p: &[i16], q: &[i16]) -> bool {
175 if p.len() < 7 || q.len() < 7 {
176 return false;
177 }
178
179 let flat_threshold = i16::from(self.threshold);
180
181 for i in 4..7 {
183 if (p[i] - p[0]).abs() > flat_threshold {
184 return false;
185 }
186 }
187
188 for i in 4..7 {
190 if (q[i] - q[0]).abs() > flat_threshold {
191 return false;
192 }
193 }
194
195 true
196 }
197
198 #[must_use]
200 pub fn has_hev(&self, p1: i16, p0: i16, q0: i16, q1: i16) -> bool {
201 let hev = i16::from(self.hev_threshold);
202 (p1 - p0).abs() > hev || (q1 - q0).abs() > hev
203 }
204}
205
206#[derive(Clone, Debug, Default)]
212pub struct LoopFilterConfig {
213 pub y_levels: [u8; 2],
215 pub u_levels: [u8; 2],
217 pub v_levels: [u8; 2],
219 pub sharpness: u8,
221 pub delta_enabled: bool,
223 pub ref_deltas: [i8; 8],
225 pub mode_deltas: [i8; 2],
227}
228
229impl LoopFilterConfig {
230 #[must_use]
232 pub fn new() -> Self {
233 Self::default()
234 }
235
236 #[must_use]
238 pub const fn with_y_levels(mut self, vertical: u8, horizontal: u8) -> Self {
239 self.y_levels = [vertical, horizontal];
240 self
241 }
242
243 #[must_use]
245 pub const fn with_sharpness(mut self, sharpness: u8) -> Self {
246 self.sharpness = sharpness;
247 self
248 }
249
250 #[must_use]
252 pub fn get_level(&self, plane: PlaneType, direction: FilterDirection) -> u8 {
253 let dir_idx = match direction {
254 FilterDirection::Vertical => 0,
255 FilterDirection::Horizontal => 1,
256 };
257
258 match plane {
259 PlaneType::Y => self.y_levels[dir_idx],
260 PlaneType::U => self.u_levels[dir_idx],
261 PlaneType::V => self.v_levels[dir_idx],
262 }
263 }
264
265 #[must_use]
267 pub fn is_enabled(&self) -> bool {
268 self.y_levels.iter().any(|&l| l > 0)
269 || self.u_levels.iter().any(|&l| l > 0)
270 || self.v_levels.iter().any(|&l| l > 0)
271 }
272
273 #[must_use]
275 pub fn create_edge_filter(&self, plane: PlaneType, direction: FilterDirection) -> EdgeFilter {
276 let level = self.get_level(plane, direction);
277 EdgeFilter::new(level, self.sharpness)
278 }
279}
280
281fn filter4(p1: i16, p0: i16, q0: i16, q1: i16, hev: bool, bd: u8) -> (i16, i16, i16, i16) {
287 let max_val = (1i16 << bd) - 1;
288
289 let filter = if hev {
290 let f = (p1 - q1).clamp(-128, 127) + 3 * (q0 - p0);
292 f.clamp(-128, 127)
293 } else {
294 3 * (q0 - p0)
296 };
297
298 let filter1 = (filter + 4).clamp(-128, 127) >> 3;
299 let filter2 = (filter + 3).clamp(-128, 127) >> 3;
300
301 let new_q0 = (q0 - filter1).clamp(0, max_val);
302 let new_p0 = (p0 + filter2).clamp(0, max_val);
303
304 let (new_p1, new_q1) = if !hev {
305 let filter3 = (filter1 + 1) >> 1;
307 (
308 (p1 + filter3).clamp(0, max_val),
309 (q1 - filter3).clamp(0, max_val),
310 )
311 } else {
312 (p1, q1)
313 };
314
315 (new_p1, new_p0, new_q0, new_q1)
316}
317
318fn filter8(p: &mut [i16], q: &mut [i16], bd: u8) {
320 if p.len() < 4 || q.len() < 4 {
321 return;
322 }
323
324 let max_val = (1i16 << bd) - 1;
325
326 let p0 = i32::from(p[0]);
328 let p1 = i32::from(p[1]);
329 let p2 = i32::from(p[2]);
330 let p3 = i32::from(p[3]);
331 let q0 = i32::from(q[0]);
332 let q1 = i32::from(q[1]);
333 let q2 = i32::from(q[2]);
334 let q3 = i32::from(q[3]);
335
336 p[0] = ((p0 * 6 + p1 * 2 + q0 * 2 + q1 + p2 + 6) >> 3).clamp(0, i32::from(max_val)) as i16;
338 p[1] = ((p1 * 6 + p0 * 2 + p2 * 2 + q0 + p3 + 6) >> 3).clamp(0, i32::from(max_val)) as i16;
339 p[2] = ((p2 * 6 + p1 * 2 + p3 * 2 + p0 + q0 + 6) >> 3).clamp(0, i32::from(max_val)) as i16;
340
341 q[0] = ((q0 * 6 + q1 * 2 + p0 * 2 + p1 + q2 + 6) >> 3).clamp(0, i32::from(max_val)) as i16;
342 q[1] = ((q1 * 6 + q0 * 2 + q2 * 2 + p0 + q3 + 6) >> 3).clamp(0, i32::from(max_val)) as i16;
343 q[2] = ((q2 * 6 + q1 * 2 + q3 * 2 + q0 + p0 + 6) >> 3).clamp(0, i32::from(max_val)) as i16;
344}
345
346fn filter14(p: &mut [i16], q: &mut [i16], bd: u8) {
348 if p.len() < 7 || q.len() < 7 {
349 return;
350 }
351
352 let max_val = (1i32 << bd) - 1;
353
354 let orig_p: Vec<i32> = p.iter().map(|&x| i32::from(x)).collect();
356 let orig_q: Vec<i32> = q.iter().map(|&x| i32::from(x)).collect();
357
358 let weights = [1, 1, 2, 2, 4, 2, 2, 1, 1];
360 let sum_weights = 16;
361
362 for i in 0..6 {
364 let mut sum = 0i32;
365 for (j, &w) in weights.iter().enumerate() {
366 let idx = i as i32 - 4 + j as i32;
367 let val = if idx < 0 {
368 orig_p[(-idx) as usize].min(orig_p[6])
369 } else if idx < 7 {
370 orig_p[idx as usize]
371 } else {
372 orig_q[(idx - 7) as usize].min(orig_q[6])
373 };
374 sum += val * i32::from(w);
375 }
376 p[i] = ((sum + sum_weights / 2) / sum_weights).clamp(0, max_val) as i16;
377 }
378
379 for i in 0..6 {
381 let mut sum = 0i32;
382 for (j, &w) in weights.iter().enumerate() {
383 let idx = i as i32 - 4 + j as i32;
384 let val = if idx < 0 {
385 orig_p[(6 + idx) as usize].min(orig_p[6])
386 } else if idx < 7 {
387 orig_q[idx as usize]
388 } else {
389 orig_q[6]
390 };
391 sum += val * i32::from(w);
392 }
393 q[i] = ((sum + sum_weights / 2) / sum_weights).clamp(0, max_val) as i16;
394 }
395}
396
397#[derive(Debug)]
403pub struct LoopFilterPipeline {
404 config: LoopFilterConfig,
406 block_size: usize,
408}
409
410impl Default for LoopFilterPipeline {
411 fn default() -> Self {
412 Self::new()
413 }
414}
415
416impl LoopFilterPipeline {
417 #[must_use]
419 pub fn new() -> Self {
420 Self {
421 config: LoopFilterConfig::new(),
422 block_size: 8,
423 }
424 }
425
426 #[must_use]
428 pub fn with_config(config: LoopFilterConfig) -> Self {
429 Self {
430 config,
431 block_size: 8,
432 }
433 }
434
435 pub fn set_config(&mut self, config: LoopFilterConfig) {
437 self.config = config;
438 }
439
440 #[must_use]
442 pub fn config(&self) -> &LoopFilterConfig {
443 &self.config
444 }
445
446 pub fn apply(
452 &mut self,
453 frame: &mut FrameBuffer,
454 _context: &FrameContext,
455 ) -> ReconstructResult<()> {
456 if !self.config.is_enabled() {
457 return Ok(());
458 }
459
460 self.filter_plane(frame.y_plane_mut(), PlaneType::Y)?;
462
463 if let Some(u_plane) = frame.u_plane_mut() {
465 self.filter_plane(u_plane, PlaneType::U)?;
466 }
467
468 if let Some(v_plane) = frame.v_plane_mut() {
470 self.filter_plane(v_plane, PlaneType::V)?;
471 }
472
473 Ok(())
474 }
475
476 fn filter_plane(
478 &self,
479 plane: &mut PlaneBuffer,
480 plane_type: PlaneType,
481 ) -> ReconstructResult<()> {
482 let bit_depth = plane.bit_depth();
483 let width = plane.width() as usize;
484 let height = plane.height() as usize;
485
486 let v_filter = self
488 .config
489 .create_edge_filter(plane_type, FilterDirection::Vertical);
490 if v_filter.should_filter() {
491 for by in 0..(height / self.block_size) {
492 for bx in 1..(width / self.block_size) {
493 self.filter_vertical_edge(
494 plane,
495 (bx * self.block_size) as u32,
496 (by * self.block_size) as u32,
497 &v_filter,
498 bit_depth,
499 );
500 }
501 }
502 }
503
504 let h_filter = self
506 .config
507 .create_edge_filter(plane_type, FilterDirection::Horizontal);
508 if h_filter.should_filter() {
509 for by in 1..(height / self.block_size) {
510 for bx in 0..(width / self.block_size) {
511 self.filter_horizontal_edge(
512 plane,
513 (bx * self.block_size) as u32,
514 (by * self.block_size) as u32,
515 &h_filter,
516 bit_depth,
517 );
518 }
519 }
520 }
521
522 Ok(())
523 }
524
525 fn filter_vertical_edge(
527 &self,
528 plane: &mut PlaneBuffer,
529 x: u32,
530 y: u32,
531 filter: &EdgeFilter,
532 bd: u8,
533 ) {
534 for row in 0..self.block_size {
535 let py = y + row as u32;
536
537 let mut p = [
539 plane.get(x.saturating_sub(1), py),
540 plane.get(x.saturating_sub(2), py),
541 plane.get(x.saturating_sub(3), py),
542 plane.get(x.saturating_sub(4), py),
543 ];
544 let mut q = [
545 plane.get(x, py),
546 plane.get(x + 1, py),
547 plane.get(x + 2, py),
548 plane.get(x + 3, py),
549 ];
550
551 if !self.filter_mask(&p, &q, filter) {
553 continue;
554 }
555
556 let hev = filter.has_hev(p[1], p[0], q[0], q[1]);
557
558 if filter.is_flat(&p, &q) {
559 filter8(&mut p, &mut q, bd);
560 } else {
561 let (new_p1, new_p0, new_q0, new_q1) = filter4(p[1], p[0], q[0], q[1], hev, bd);
562 p[1] = new_p1;
563 p[0] = new_p0;
564 q[0] = new_q0;
565 q[1] = new_q1;
566 }
567
568 plane.set(x.saturating_sub(1), py, p[0]);
570 plane.set(x.saturating_sub(2), py, p[1]);
571 plane.set(x.saturating_sub(3), py, p[2]);
572 plane.set(x.saturating_sub(4), py, p[3]);
573 plane.set(x, py, q[0]);
574 plane.set(x + 1, py, q[1]);
575 plane.set(x + 2, py, q[2]);
576 plane.set(x + 3, py, q[3]);
577 }
578 }
579
580 fn filter_horizontal_edge(
582 &self,
583 plane: &mut PlaneBuffer,
584 x: u32,
585 y: u32,
586 filter: &EdgeFilter,
587 bd: u8,
588 ) {
589 for col in 0..self.block_size {
590 let px = x + col as u32;
591
592 let mut p = [
594 plane.get(px, y.saturating_sub(1)),
595 plane.get(px, y.saturating_sub(2)),
596 plane.get(px, y.saturating_sub(3)),
597 plane.get(px, y.saturating_sub(4)),
598 ];
599 let mut q = [
600 plane.get(px, y),
601 plane.get(px, y + 1),
602 plane.get(px, y + 2),
603 plane.get(px, y + 3),
604 ];
605
606 if !self.filter_mask(&p, &q, filter) {
608 continue;
609 }
610
611 let hev = filter.has_hev(p[1], p[0], q[0], q[1]);
612
613 if filter.is_flat(&p, &q) {
614 filter8(&mut p, &mut q, bd);
615 } else {
616 let (new_p1, new_p0, new_q0, new_q1) = filter4(p[1], p[0], q[0], q[1], hev, bd);
617 p[1] = new_p1;
618 p[0] = new_p0;
619 q[0] = new_q0;
620 q[1] = new_q1;
621 }
622
623 plane.set(px, y.saturating_sub(1), p[0]);
625 plane.set(px, y.saturating_sub(2), p[1]);
626 plane.set(px, y.saturating_sub(3), p[2]);
627 plane.set(px, y.saturating_sub(4), p[3]);
628 plane.set(px, y, q[0]);
629 plane.set(px, y + 1, q[1]);
630 plane.set(px, y + 2, q[2]);
631 plane.set(px, y + 3, q[3]);
632 }
633 }
634
635 fn filter_mask(&self, p: &[i16], q: &[i16], filter: &EdgeFilter) -> bool {
637 if p.len() < 2 || q.len() < 2 {
638 return false;
639 }
640
641 let limit = i16::from(filter.limit);
642 let threshold = i16::from(filter.threshold);
643
644 if (p[0] - q[0]).abs() > (limit * 2 + threshold) {
646 return false;
647 }
648
649 if (p[1] - p[0]).abs() > limit {
651 return false;
652 }
653
654 if (q[1] - q[0]).abs() > limit {
655 return false;
656 }
657
658 true
659 }
660}
661
662#[cfg(test)]
667mod tests {
668 use super::*;
669 use crate::reconstruct::ChromaSubsampling;
670
671 #[test]
672 fn test_filter_direction() {
673 assert_eq!(
674 FilterDirection::Vertical.perpendicular(),
675 FilterDirection::Horizontal
676 );
677 assert_eq!(
678 FilterDirection::Horizontal.perpendicular(),
679 FilterDirection::Vertical
680 );
681 }
682
683 #[test]
684 fn test_edge_filter_new() {
685 let filter = EdgeFilter::new(32, 0);
686 assert_eq!(filter.level, 32);
687 assert!(filter.limit > 0);
688 assert!(filter.should_filter());
689
690 let filter_zero = EdgeFilter::new(0, 0);
691 assert!(!filter_zero.should_filter());
692 }
693
694 #[test]
695 fn test_edge_filter_hev_threshold() {
696 let filter_low = EdgeFilter::new(10, 0);
697 assert_eq!(filter_low.hev_threshold, 0);
698
699 let filter_mid = EdgeFilter::new(30, 0);
700 assert_eq!(filter_mid.hev_threshold, 1);
701
702 let filter_high = EdgeFilter::new(50, 0);
703 assert_eq!(filter_high.hev_threshold, 2);
704 }
705
706 #[test]
707 fn test_edge_filter_flat_detection() {
708 let filter = EdgeFilter::new(32, 0);
709
710 let p = [128i16, 128, 128, 128];
712 let q = [128i16, 128, 128, 128];
713 assert!(filter.is_flat(&p, &q));
714
715 let p_nonflat = [128i16, 100, 128, 128];
717 assert!(!filter.is_flat(&p_nonflat, &q));
718 }
719
720 #[test]
721 fn test_loop_filter_config() {
722 let config = LoopFilterConfig::new()
723 .with_y_levels(32, 32)
724 .with_sharpness(2);
725
726 assert_eq!(
727 config.get_level(PlaneType::Y, FilterDirection::Vertical),
728 32
729 );
730 assert_eq!(
731 config.get_level(PlaneType::Y, FilterDirection::Horizontal),
732 32
733 );
734 assert!(config.is_enabled());
735 }
736
737 #[test]
738 fn test_loop_filter_config_disabled() {
739 let config = LoopFilterConfig::new();
740 assert!(!config.is_enabled());
741 }
742
743 #[test]
744 fn test_loop_filter_pipeline_creation() {
745 let pipeline = LoopFilterPipeline::new();
746 assert!(!pipeline.config().is_enabled());
747 }
748
749 #[test]
750 fn test_loop_filter_pipeline_with_config() {
751 let config = LoopFilterConfig::new().with_y_levels(20, 20);
752 let pipeline = LoopFilterPipeline::with_config(config);
753 assert!(pipeline.config().is_enabled());
754 }
755
756 #[test]
757 fn test_filter4() {
758 let p1 = 100i16;
759 let p0 = 120i16;
760 let q0 = 140i16;
761 let q1 = 130i16;
762
763 let (_new_p1, new_p0, new_q0, _new_q1) = filter4(p1, p0, q0, q1, false, 8);
764
765 assert!((new_p0 - new_q0).abs() < (120 - 140i16).abs());
767 }
768
769 #[test]
770 fn test_filter8() {
771 let mut p = [130i16, 128, 126, 124];
772 let mut q = [140i16, 142, 144, 146];
773
774 filter8(&mut p, &mut q, 8);
775
776 let edge_diff_after = (p[0] - q[0]).abs();
779 assert!(edge_diff_after < 15);
780 }
781
782 #[test]
783 fn test_loop_filter_apply_disabled() {
784 let mut frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
785 let context = FrameContext::new(64, 64);
786
787 let mut pipeline = LoopFilterPipeline::new();
788 let result = pipeline.apply(&mut frame, &context);
789
790 assert!(result.is_ok());
791 }
792
793 #[test]
794 fn test_loop_filter_apply_enabled() {
795 let mut frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
796
797 for y in 0..64 {
799 for x in 0..8 {
800 frame.y_plane_mut().set(x, y as u32, 100);
801 }
802 for x in 8..64 {
803 frame.y_plane_mut().set(x as u32, y as u32, 150);
804 }
805 }
806
807 let context = FrameContext::new(64, 64);
808 let config = LoopFilterConfig::new().with_y_levels(32, 32);
809 let mut pipeline = LoopFilterPipeline::with_config(config);
810
811 let result = pipeline.apply(&mut frame, &context);
812 assert!(result.is_ok());
813 }
814
815 #[test]
816 fn test_constants() {
817 assert_eq!(MAX_LOOP_FILTER_LEVEL, 63);
818 assert_eq!(MAX_SHARPNESS_LEVEL, 7);
819 assert_eq!(NARROW_FILTER_TAPS, 4);
820 assert_eq!(WIDE_FILTER_TAPS, 8);
821 assert_eq!(EXTRA_WIDE_FILTER_TAPS, 14);
822 }
823}