Skip to main content

oximedia_codec/reconstruct/
deblock.rs

1//! Deblocking filter for reducing blocking artifacts.
2//!
3//! The deblocking filter is applied at block boundaries to reduce
4//! artifacts caused by block-based transform coding. It uses adaptive
5//! filtering based on boundary strength (bS) calculations.
6
7#![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, ReconstructResult};
34
35// =============================================================================
36// Constants
37// =============================================================================
38
39/// Maximum boundary strength.
40pub const MAX_BOUNDARY_STRENGTH: u8 = 4;
41
42/// Minimum block size for deblocking.
43pub const MIN_DEBLOCK_SIZE: usize = 4;
44
45/// Filter tap count for normal filter.
46pub const NORMAL_FILTER_TAPS: usize = 4;
47
48/// Filter tap count for strong filter.
49pub const STRONG_FILTER_TAPS: usize = 8;
50
51// =============================================================================
52// Filter Strength
53// =============================================================================
54
55/// Boundary strength (bS) for deblocking.
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub struct FilterStrength {
58    /// Boundary strength value (0-4).
59    pub bs: u8,
60    /// Alpha threshold parameter.
61    pub alpha: u8,
62    /// Beta threshold parameter.
63    pub beta: u8,
64    /// tc0 clipping parameter.
65    pub tc0: u8,
66}
67
68impl FilterStrength {
69    /// Create a new filter strength.
70    #[must_use]
71    pub const fn new(bs: u8) -> Self {
72        Self {
73            bs,
74            alpha: 0,
75            beta: 0,
76            tc0: 0,
77        }
78    }
79
80    /// Create from quantization parameter.
81    #[must_use]
82    pub fn from_qp(qp: u8, bs: u8) -> Self {
83        let idx = qp.min(51) as usize;
84
85        // Alpha table (indexed by QP)
86        const ALPHA: [u8; 52] = [
87            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15,
88            17, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162,
89            182, 203, 226, 255, 255,
90        ];
91
92        // Beta table (indexed by QP)
93        const BETA: [u8; 52] = [
94            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7,
95            7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18,
96        ];
97
98        // tc0 table (indexed by QP and bS)
99        const TC0: [[u8; 3]; 52] = [
100            [0, 0, 0],
101            [0, 0, 0],
102            [0, 0, 0],
103            [0, 0, 0],
104            [0, 0, 0],
105            [0, 0, 0],
106            [0, 0, 0],
107            [0, 0, 0],
108            [0, 0, 0],
109            [0, 0, 0],
110            [0, 0, 0],
111            [0, 0, 0],
112            [0, 0, 0],
113            [0, 0, 0],
114            [0, 0, 0],
115            [0, 0, 0],
116            [0, 0, 0],
117            [0, 0, 1],
118            [0, 0, 1],
119            [0, 0, 1],
120            [0, 0, 1],
121            [0, 1, 1],
122            [0, 1, 1],
123            [1, 1, 1],
124            [1, 1, 1],
125            [1, 1, 1],
126            [1, 1, 1],
127            [1, 1, 2],
128            [1, 1, 2],
129            [1, 1, 2],
130            [1, 1, 2],
131            [1, 2, 3],
132            [1, 2, 3],
133            [2, 2, 3],
134            [2, 2, 4],
135            [2, 3, 4],
136            [2, 3, 4],
137            [3, 3, 5],
138            [3, 4, 6],
139            [3, 4, 6],
140            [4, 5, 7],
141            [4, 5, 8],
142            [4, 6, 9],
143            [5, 7, 10],
144            [6, 8, 11],
145            [6, 8, 13],
146            [7, 10, 14],
147            [8, 11, 16],
148            [9, 12, 18],
149            [10, 13, 20],
150            [11, 15, 23],
151            [13, 17, 25],
152        ];
153
154        let tc0 = if bs > 0 && bs < 4 {
155            TC0[idx][(bs - 1) as usize]
156        } else {
157            0
158        };
159
160        Self {
161            bs,
162            alpha: ALPHA[idx],
163            beta: BETA[idx],
164            tc0,
165        }
166    }
167
168    /// Check if filtering should be applied.
169    #[must_use]
170    pub const fn should_filter(&self) -> bool {
171        self.bs > 0
172    }
173
174    /// Check if this is a strong filter (bS = 4).
175    #[must_use]
176    pub const fn is_strong(&self) -> bool {
177        self.bs >= 4
178    }
179
180    /// Get the tc parameter for filtering.
181    #[must_use]
182    pub const fn tc(&self) -> i16 {
183        if self.bs >= 4 {
184            0 // Not used for strong filter
185        } else {
186            self.tc0 as i16
187        }
188    }
189}
190
191// =============================================================================
192// Deblock Parameters
193// =============================================================================
194
195/// Parameters for deblocking filter.
196#[derive(Clone, Debug, Default)]
197pub struct DeblockParams {
198    /// Deblocking filter disabled.
199    pub disable_deblock: bool,
200    /// Alpha offset (slice level).
201    pub alpha_offset: i8,
202    /// Beta offset (slice level).
203    pub beta_offset: i8,
204    /// Quantization parameter.
205    pub qp: u8,
206}
207
208impl DeblockParams {
209    /// Create new deblock parameters.
210    #[must_use]
211    pub fn new(qp: u8) -> Self {
212        Self {
213            qp,
214            ..Default::default()
215        }
216    }
217
218    /// Set alpha offset.
219    #[must_use]
220    pub const fn with_alpha_offset(mut self, offset: i8) -> Self {
221        self.alpha_offset = offset;
222        self
223    }
224
225    /// Set beta offset.
226    #[must_use]
227    pub const fn with_beta_offset(mut self, offset: i8) -> Self {
228        self.beta_offset = offset;
229        self
230    }
231
232    /// Get effective QP for alpha calculation.
233    #[must_use]
234    pub fn effective_qp_alpha(&self) -> u8 {
235        ((self.qp as i16) + i16::from(self.alpha_offset)).clamp(0, 51) as u8
236    }
237
238    /// Get effective QP for beta calculation.
239    #[must_use]
240    pub fn effective_qp_beta(&self) -> u8 {
241        ((self.qp as i16) + i16::from(self.beta_offset)).clamp(0, 51) as u8
242    }
243
244    /// Create filter strength for an edge.
245    #[must_use]
246    pub fn create_strength(&self, bs: u8) -> FilterStrength {
247        FilterStrength::from_qp(self.qp, bs)
248    }
249}
250
251// =============================================================================
252// Boundary Strength Calculator
253// =============================================================================
254
255/// Block information for boundary strength calculation.
256#[derive(Clone, Copy, Debug, Default)]
257pub struct BlockInfo {
258    /// Block is intra-coded.
259    pub is_intra: bool,
260    /// Has non-zero coefficients.
261    pub has_coeffs: bool,
262    /// Reference frame index.
263    pub ref_frame: u8,
264    /// Motion vector x component (in quarter-pel).
265    pub mv_x: i16,
266    /// Motion vector y component (in quarter-pel).
267    pub mv_y: i16,
268}
269
270impl BlockInfo {
271    /// Create info for an intra block.
272    #[must_use]
273    pub const fn intra() -> Self {
274        Self {
275            is_intra: true,
276            has_coeffs: true,
277            ref_frame: 0,
278            mv_x: 0,
279            mv_y: 0,
280        }
281    }
282
283    /// Create info for an inter block.
284    #[must_use]
285    pub const fn inter(ref_frame: u8, mv_x: i16, mv_y: i16, has_coeffs: bool) -> Self {
286        Self {
287            is_intra: false,
288            has_coeffs,
289            ref_frame,
290            mv_x,
291            mv_y,
292        }
293    }
294}
295
296/// Calculate boundary strength between two adjacent blocks.
297#[must_use]
298pub fn calculate_boundary_strength(p: &BlockInfo, q: &BlockInfo) -> u8 {
299    // If either block is intra, bS = 4
300    if p.is_intra || q.is_intra {
301        return 4;
302    }
303
304    // If either block has non-zero coefficients, bS = 2
305    if p.has_coeffs || q.has_coeffs {
306        return 2;
307    }
308
309    // Check reference frame difference
310    if p.ref_frame != q.ref_frame {
311        return 1;
312    }
313
314    // Check motion vector difference (threshold is 4 quarter-pels = 1 full pel)
315    if (p.mv_x - q.mv_x).abs() >= 4 || (p.mv_y - q.mv_y).abs() >= 4 {
316        return 1;
317    }
318
319    // No filtering needed
320    0
321}
322
323// =============================================================================
324// Deblocking Filter
325// =============================================================================
326
327/// Deblocking filter implementation.
328#[derive(Debug)]
329pub struct DeblockFilter {
330    /// Filter parameters.
331    params: DeblockParams,
332    /// Block size.
333    block_size: usize,
334}
335
336impl Default for DeblockFilter {
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342impl DeblockFilter {
343    /// Create a new deblocking filter.
344    #[must_use]
345    pub fn new() -> Self {
346        Self {
347            params: DeblockParams::default(),
348            block_size: 8,
349        }
350    }
351
352    /// Create with specific parameters.
353    #[must_use]
354    pub fn with_params(params: DeblockParams) -> Self {
355        Self {
356            params,
357            block_size: 8,
358        }
359    }
360
361    /// Set filter parameters.
362    pub fn set_params(&mut self, params: DeblockParams) {
363        self.params = params;
364    }
365
366    /// Get current parameters.
367    #[must_use]
368    pub fn params(&self) -> &DeblockParams {
369        &self.params
370    }
371
372    /// Apply deblocking filter to a frame.
373    ///
374    /// # Errors
375    ///
376    /// Returns error if filtering fails.
377    pub fn apply(
378        &mut self,
379        frame: &mut FrameBuffer,
380        _context: &FrameContext,
381    ) -> ReconstructResult<()> {
382        if self.params.disable_deblock {
383            return Ok(());
384        }
385
386        let bd = frame.bit_depth();
387
388        // Filter Y plane
389        self.filter_plane(frame.y_plane_mut(), bd, false)?;
390
391        // Filter chroma planes (use default bS = 1 for simplicity)
392        if let Some(u) = frame.u_plane_mut() {
393            self.filter_plane(u, bd, true)?;
394        }
395        if let Some(v) = frame.v_plane_mut() {
396            self.filter_plane(v, bd, true)?;
397        }
398
399        Ok(())
400    }
401
402    /// Filter a single plane.
403    fn filter_plane(
404        &self,
405        plane: &mut PlaneBuffer,
406        bd: u8,
407        is_chroma: bool,
408    ) -> ReconstructResult<()> {
409        let width = plane.width() as usize;
410        let height = plane.height() as usize;
411        let block_size = if is_chroma { 4 } else { self.block_size };
412
413        // Use a default boundary strength for demonstration
414        // In a full implementation, this would come from block info
415        let strength = self.params.create_strength(2);
416
417        // Filter vertical edges
418        for by in 0..(height / block_size) {
419            for bx in 1..(width / block_size) {
420                let x = (bx * block_size) as u32;
421                let y = (by * block_size) as u32;
422                self.filter_edge_vertical(plane, x, y, block_size, &strength, bd);
423            }
424        }
425
426        // Filter horizontal edges
427        for by in 1..(height / block_size) {
428            for bx in 0..(width / block_size) {
429                let x = (bx * block_size) as u32;
430                let y = (by * block_size) as u32;
431                self.filter_edge_horizontal(plane, x, y, block_size, &strength, bd);
432            }
433        }
434
435        Ok(())
436    }
437
438    /// Filter a vertical edge.
439    fn filter_edge_vertical(
440        &self,
441        plane: &mut PlaneBuffer,
442        x: u32,
443        y: u32,
444        length: usize,
445        strength: &FilterStrength,
446        bd: u8,
447    ) {
448        if !strength.should_filter() {
449            return;
450        }
451
452        for i in 0..length {
453            let py = y + i as u32;
454
455            // Get samples
456            let p2 = plane.get(x.saturating_sub(3), py);
457            let p1 = plane.get(x.saturating_sub(2), py);
458            let p0 = plane.get(x.saturating_sub(1), py);
459            let q0 = plane.get(x, py);
460            let q1 = plane.get(x + 1, py);
461            let q2 = plane.get(x + 2, py);
462
463            // Check filter condition
464            if !self.should_filter_edge(p0, p1, q0, q1, strength) {
465                continue;
466            }
467
468            // Apply filter
469            let (new_p0, new_p1, new_q0, new_q1) = if strength.is_strong() {
470                self.strong_filter(p0, p1, p2, q0, q1, q2, bd)
471            } else {
472                self.normal_filter(p0, p1, q0, q1, strength, bd)
473            };
474
475            // Write back
476            plane.set(x.saturating_sub(1), py, new_p0);
477            plane.set(x.saturating_sub(2), py, new_p1);
478            plane.set(x, py, new_q0);
479            plane.set(x + 1, py, new_q1);
480        }
481    }
482
483    /// Filter a horizontal edge.
484    fn filter_edge_horizontal(
485        &self,
486        plane: &mut PlaneBuffer,
487        x: u32,
488        y: u32,
489        length: usize,
490        strength: &FilterStrength,
491        bd: u8,
492    ) {
493        if !strength.should_filter() {
494            return;
495        }
496
497        for i in 0..length {
498            let px = x + i as u32;
499
500            // Get samples
501            let p2 = plane.get(px, y.saturating_sub(3));
502            let p1 = plane.get(px, y.saturating_sub(2));
503            let p0 = plane.get(px, y.saturating_sub(1));
504            let q0 = plane.get(px, y);
505            let q1 = plane.get(px, y + 1);
506            let q2 = plane.get(px, y + 2);
507
508            // Check filter condition
509            if !self.should_filter_edge(p0, p1, q0, q1, strength) {
510                continue;
511            }
512
513            // Apply filter
514            let (new_p0, new_p1, new_q0, new_q1) = if strength.is_strong() {
515                self.strong_filter(p0, p1, p2, q0, q1, q2, bd)
516            } else {
517                self.normal_filter(p0, p1, q0, q1, strength, bd)
518            };
519
520            // Write back
521            plane.set(px, y.saturating_sub(1), new_p0);
522            plane.set(px, y.saturating_sub(2), new_p1);
523            plane.set(px, y, new_q0);
524            plane.set(px, y + 1, new_q1);
525        }
526    }
527
528    /// Check if edge should be filtered.
529    fn should_filter_edge(
530        &self,
531        p0: i16,
532        p1: i16,
533        q0: i16,
534        q1: i16,
535        strength: &FilterStrength,
536    ) -> bool {
537        let alpha = i16::from(strength.alpha);
538        let beta = i16::from(strength.beta);
539
540        // Check thresholds
541        (p0 - q0).abs() < alpha && (p1 - p0).abs() < beta && (q1 - q0).abs() < beta
542    }
543
544    /// Apply normal (4-tap) filter.
545    fn normal_filter(
546        &self,
547        p0: i16,
548        p1: i16,
549        q0: i16,
550        q1: i16,
551        strength: &FilterStrength,
552        bd: u8,
553    ) -> (i16, i16, i16, i16) {
554        let max_val = (1i16 << bd) - 1;
555        let tc = strength.tc();
556
557        // Compute delta
558        let delta0 = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3).clamp(-tc, tc);
559
560        let new_p0 = (p0 + delta0).clamp(0, max_val);
561        let new_q0 = (q0 - delta0).clamp(0, max_val);
562
563        // P1 and Q1 filtering (only if tc0 > 0)
564        let (new_p1, new_q1) = if tc > 0 {
565            let delta_p1 = ((p2_avg(p0, p1) - p1 + delta0) >> 1).clamp(-tc, tc);
566            let delta_q1 = ((p2_avg(q0, q1) - q1 - delta0) >> 1).clamp(-tc, tc);
567            (
568                (p1 + delta_p1).clamp(0, max_val),
569                (q1 + delta_q1).clamp(0, max_val),
570            )
571        } else {
572            (p1, q1)
573        };
574
575        (new_p0, new_p1, new_q0, new_q1)
576    }
577
578    /// Apply strong (8-tap) filter.
579    fn strong_filter(
580        &self,
581        p0: i16,
582        p1: i16,
583        p2: i16,
584        q0: i16,
585        q1: i16,
586        q2: i16,
587        bd: u8,
588    ) -> (i16, i16, i16, i16) {
589        let max_val = (1i16 << bd) - 1;
590
591        // Strong filter uses weighted average
592        let new_p0 = ((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3).clamp(0, max_val);
593        let new_p1 = ((p2 + p1 + p0 + q0 + 2) >> 2).clamp(0, max_val);
594        let new_q0 = ((p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3).clamp(0, max_val);
595        let new_q1 = ((p0 + q0 + q1 + q2 + 2) >> 2).clamp(0, max_val);
596
597        (new_p0, new_p1, new_q0, new_q1)
598    }
599}
600
601/// Helper for p2 average calculation.
602#[inline]
603fn p2_avg(p0: i16, p1: i16) -> i16 {
604    (p0 + p1 + 1) >> 1
605}
606
607// =============================================================================
608// Tests
609// =============================================================================
610
611#[cfg(test)]
612mod tests {
613    use super::*;
614    use crate::reconstruct::ChromaSubsampling;
615
616    #[test]
617    fn test_filter_strength_new() {
618        let strength = FilterStrength::new(2);
619        assert_eq!(strength.bs, 2);
620        assert!(strength.should_filter());
621        assert!(!strength.is_strong());
622
623        let strength_zero = FilterStrength::new(0);
624        assert!(!strength_zero.should_filter());
625    }
626
627    #[test]
628    fn test_filter_strength_from_qp() {
629        let strength = FilterStrength::from_qp(26, 2);
630        assert_eq!(strength.bs, 2);
631        assert!(strength.alpha > 0);
632        assert!(strength.beta > 0);
633
634        let strength_strong = FilterStrength::from_qp(26, 4);
635        assert!(strength_strong.is_strong());
636    }
637
638    #[test]
639    fn test_deblock_params() {
640        let params = DeblockParams::new(26)
641            .with_alpha_offset(2)
642            .with_beta_offset(-2);
643
644        assert_eq!(params.qp, 26);
645        assert_eq!(params.effective_qp_alpha(), 28);
646        assert_eq!(params.effective_qp_beta(), 24);
647    }
648
649    #[test]
650    fn test_block_info() {
651        let intra = BlockInfo::intra();
652        assert!(intra.is_intra);
653
654        let inter = BlockInfo::inter(1, 10, 20, true);
655        assert!(!inter.is_intra);
656        assert_eq!(inter.ref_frame, 1);
657        assert_eq!(inter.mv_x, 10);
658    }
659
660    #[test]
661    fn test_boundary_strength_calculation() {
662        let intra = BlockInfo::intra();
663        let inter = BlockInfo::inter(0, 0, 0, false);
664
665        // Intra edge
666        assert_eq!(calculate_boundary_strength(&intra, &inter), 4);
667
668        // Two inter blocks with different refs
669        let inter2 = BlockInfo::inter(1, 0, 0, false);
670        assert_eq!(calculate_boundary_strength(&inter, &inter2), 1);
671
672        // Two inter blocks with same ref but different MV
673        let inter3 = BlockInfo::inter(0, 10, 0, false);
674        assert_eq!(calculate_boundary_strength(&inter, &inter3), 1);
675
676        // Two identical inter blocks
677        assert_eq!(calculate_boundary_strength(&inter, &inter), 0);
678    }
679
680    #[test]
681    fn test_boundary_strength_with_coeffs() {
682        let inter_coeffs = BlockInfo::inter(0, 0, 0, true);
683        let inter_no_coeffs = BlockInfo::inter(0, 0, 0, false);
684
685        assert_eq!(
686            calculate_boundary_strength(&inter_coeffs, &inter_no_coeffs),
687            2
688        );
689    }
690
691    #[test]
692    fn test_deblock_filter_creation() {
693        let filter = DeblockFilter::new();
694        assert!(!filter.params().disable_deblock);
695    }
696
697    #[test]
698    fn test_deblock_filter_with_params() {
699        let params = DeblockParams::new(26);
700        let filter = DeblockFilter::with_params(params);
701        assert_eq!(filter.params().qp, 26);
702    }
703
704    #[test]
705    fn test_deblock_filter_apply_disabled() {
706        let mut frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
707        let context = FrameContext::new(64, 64);
708
709        let mut params = DeblockParams::new(26);
710        params.disable_deblock = true;
711        let mut filter = DeblockFilter::with_params(params);
712
713        let result = filter.apply(&mut frame, &context);
714        assert!(result.is_ok());
715    }
716
717    #[test]
718    fn test_deblock_filter_apply() {
719        let mut frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
720
721        // Create an artificial edge
722        for y in 0..64 {
723            for x in 0..8 {
724                frame.y_plane_mut().set(x, y as u32, 100);
725            }
726            for x in 8..64 {
727                frame.y_plane_mut().set(x as u32, y as u32, 150);
728            }
729        }
730
731        let context = FrameContext::new(64, 64);
732        let params = DeblockParams::new(26);
733        let mut filter = DeblockFilter::with_params(params);
734
735        let result = filter.apply(&mut frame, &context);
736        assert!(result.is_ok());
737    }
738
739    #[test]
740    fn test_strong_filter() {
741        let filter = DeblockFilter::new();
742
743        let (new_p0, _new_p1, new_q0, _new_q1) =
744            filter.strong_filter(100, 95, 90, 150, 155, 160, 8);
745
746        // After strong filtering, edge should be smoother
747        assert!((new_p0 - new_q0).abs() < (100 - 150i16).abs());
748        assert!(new_p0 >= 0 && new_p0 <= 255);
749        assert!(new_q0 >= 0 && new_q0 <= 255);
750    }
751
752    #[test]
753    fn test_normal_filter() {
754        let filter = DeblockFilter::new();
755        let strength = FilterStrength::from_qp(26, 2);
756
757        let (new_p0, _new_p1, new_q0, _new_q1) =
758            filter.normal_filter(100, 95, 150, 155, &strength, 8);
759
760        // After normal filtering, values should be closer
761        assert!((new_p0 - new_q0).abs() <= (100 - 150i16).abs());
762    }
763
764    #[test]
765    fn test_constants() {
766        assert_eq!(MAX_BOUNDARY_STRENGTH, 4);
767        assert_eq!(MIN_DEBLOCK_SIZE, 4);
768        assert_eq!(NORMAL_FILTER_TAPS, 4);
769        assert_eq!(STRONG_FILTER_TAPS, 8);
770    }
771}