Skip to main content

oximedia_codec/intra/
modes.rs

1//! Intra prediction modes and traits.
2//!
3//! This module defines the prediction mode types and the common trait
4//! that all intra predictors implement.
5
6#![forbid(unsafe_code)]
7#![allow(dead_code)]
8#![allow(clippy::cast_possible_wrap)]
9#![allow(clippy::match_same_arms)]
10#![allow(clippy::doc_markdown)]
11#![allow(clippy::cast_lossless)]
12#![allow(clippy::cast_possible_truncation)]
13
14use super::{BlockDimensions, IntraPredContext};
15
16/// Trait for intra prediction implementations.
17///
18/// All prediction modes implement this trait to provide a unified interface
19/// for generating prediction samples.
20pub trait IntraPredictor {
21    /// Generate prediction samples into the output buffer.
22    ///
23    /// # Arguments
24    /// * `ctx` - Prediction context with neighbor samples
25    /// * `output` - Output buffer to fill with predicted samples
26    /// * `stride` - Stride (row width) of the output buffer
27    /// * `dims` - Block dimensions (width x height)
28    fn predict(
29        &self,
30        ctx: &IntraPredContext,
31        output: &mut [u16],
32        stride: usize,
33        dims: BlockDimensions,
34    );
35}
36
37/// Intra prediction mode enumeration (shared by AV1 and VP9).
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
39#[repr(u8)]
40pub enum IntraMode {
41    /// DC prediction (average of neighbors).
42    #[default]
43    Dc = 0,
44    /// Vertical prediction (project top samples down).
45    Vertical = 1,
46    /// Horizontal prediction (project left samples right).
47    Horizontal = 2,
48    /// Diagonal 45 degrees (down-right).
49    D45 = 3,
50    /// Diagonal 135 degrees (up-left).
51    D135 = 4,
52    /// Diagonal 113 degrees (AV1 naming).
53    D113 = 5,
54    /// Diagonal 157 degrees (AV1 naming).
55    D157 = 6,
56    /// Diagonal 203 degrees (AV1 naming).
57    D203 = 7,
58    /// Diagonal 67 degrees (AV1 naming).
59    D67 = 8,
60    /// Smooth prediction (AV1).
61    Smooth = 9,
62    /// Smooth vertical prediction (AV1).
63    SmoothV = 10,
64    /// Smooth horizontal prediction (AV1).
65    SmoothH = 11,
66    /// Paeth prediction (AV1).
67    Paeth = 12,
68    /// Filter intra (AV1 small blocks).
69    FilterIntra = 13,
70}
71
72impl IntraMode {
73    /// Total number of intra modes.
74    pub const COUNT: usize = 14;
75
76    /// Check if this is a directional mode.
77    #[must_use]
78    pub const fn is_directional(self) -> bool {
79        matches!(
80            self,
81            Self::Vertical
82                | Self::Horizontal
83                | Self::D45
84                | Self::D135
85                | Self::D113
86                | Self::D157
87                | Self::D203
88                | Self::D67
89        )
90    }
91
92    /// Check if this is a smooth mode.
93    #[must_use]
94    pub const fn is_smooth(self) -> bool {
95        matches!(self, Self::Smooth | Self::SmoothV | Self::SmoothH)
96    }
97
98    /// Get the nominal angle for directional modes (in degrees * 2).
99    /// Returns None for non-directional modes.
100    #[must_use]
101    pub const fn nominal_angle(self) -> Option<u16> {
102        match self {
103            Self::Vertical => Some(90),
104            Self::Horizontal => Some(180),
105            Self::D45 => Some(45),
106            Self::D135 => Some(135),
107            Self::D113 => Some(113),
108            Self::D157 => Some(157),
109            Self::D203 => Some(203),
110            Self::D67 => Some(67),
111            _ => None,
112        }
113    }
114
115    /// Convert from u8.
116    #[must_use]
117    pub const fn from_u8(value: u8) -> Option<Self> {
118        match value {
119            0 => Some(Self::Dc),
120            1 => Some(Self::Vertical),
121            2 => Some(Self::Horizontal),
122            3 => Some(Self::D45),
123            4 => Some(Self::D135),
124            5 => Some(Self::D113),
125            6 => Some(Self::D157),
126            7 => Some(Self::D203),
127            8 => Some(Self::D67),
128            9 => Some(Self::Smooth),
129            10 => Some(Self::SmoothV),
130            11 => Some(Self::SmoothH),
131            12 => Some(Self::Paeth),
132            13 => Some(Self::FilterIntra),
133            _ => None,
134        }
135    }
136}
137
138/// Directional mode with angle information.
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub struct DirectionalMode {
141    /// Base angle in degrees.
142    pub angle: u16,
143    /// Angle delta (AV1 only, -3 to +3 steps of 3 degrees).
144    pub delta: AngleDelta,
145}
146
147impl DirectionalMode {
148    /// Create a new directional mode.
149    #[must_use]
150    pub const fn new(angle: u16) -> Self {
151        Self {
152            angle,
153            delta: AngleDelta::Zero,
154        }
155    }
156
157    /// Create a new directional mode with delta.
158    #[must_use]
159    pub const fn with_delta(angle: u16, delta: AngleDelta) -> Self {
160        Self { angle, delta }
161    }
162
163    /// Get the effective angle including delta adjustment.
164    #[must_use]
165    pub const fn effective_angle(self) -> i16 {
166        self.angle as i16 + self.delta.degrees()
167    }
168
169    /// Check if this is a vertical-ish direction (45-135 degrees).
170    #[must_use]
171    pub const fn is_vertical_ish(self) -> bool {
172        let angle = self.effective_angle();
173        angle > 45 && angle < 135
174    }
175
176    /// Check if this is a horizontal-ish direction (135-225 degrees).
177    #[must_use]
178    pub const fn is_horizontal_ish(self) -> bool {
179        let angle = self.effective_angle();
180        angle > 135 && angle < 225
181    }
182}
183
184impl Default for DirectionalMode {
185    fn default() -> Self {
186        Self::new(90) // Vertical
187    }
188}
189
190/// Angle delta for AV1 directional modes.
191///
192/// AV1 allows fine-tuning directional prediction angles by -3 to +3 steps
193/// of 3 degrees each, giving a range of -9 to +9 degrees.
194#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
195#[repr(i8)]
196pub enum AngleDelta {
197    /// -3 steps (-9 degrees)
198    Minus3 = -3,
199    /// -2 steps (-6 degrees)
200    Minus2 = -2,
201    /// -1 step (-3 degrees)
202    Minus1 = -1,
203    /// No delta (0 degrees)
204    #[default]
205    Zero = 0,
206    /// +1 step (+3 degrees)
207    Plus1 = 1,
208    /// +2 steps (+6 degrees)
209    Plus2 = 2,
210    /// +3 steps (+9 degrees)
211    Plus3 = 3,
212}
213
214impl AngleDelta {
215    /// Angle step size in degrees.
216    pub const STEP_DEGREES: i16 = 3;
217
218    /// Convert to degrees offset.
219    #[must_use]
220    pub const fn degrees(self) -> i16 {
221        (self as i8 as i16) * Self::STEP_DEGREES
222    }
223
224    /// Create from step count (-3 to +3).
225    #[must_use]
226    pub const fn from_steps(steps: i8) -> Option<Self> {
227        match steps {
228            -3 => Some(Self::Minus3),
229            -2 => Some(Self::Minus2),
230            -1 => Some(Self::Minus1),
231            0 => Some(Self::Zero),
232            1 => Some(Self::Plus1),
233            2 => Some(Self::Plus2),
234            3 => Some(Self::Plus3),
235            _ => None,
236        }
237    }
238
239    /// Convert to step count.
240    #[must_use]
241    pub const fn steps(self) -> i8 {
242        self as i8
243    }
244}
245
246/// Non-directional prediction modes.
247#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
248pub enum NonDirectionalMode {
249    /// DC prediction.
250    #[default]
251    Dc,
252    /// Smooth prediction (bilinear interpolation).
253    Smooth,
254    /// Smooth vertical only.
255    SmoothV,
256    /// Smooth horizontal only.
257    SmoothH,
258    /// Paeth prediction (adaptive).
259    Paeth,
260}
261
262impl NonDirectionalMode {
263    /// Convert from IntraMode.
264    #[must_use]
265    pub const fn from_intra_mode(mode: IntraMode) -> Option<Self> {
266        match mode {
267            IntraMode::Dc => Some(Self::Dc),
268            IntraMode::Smooth => Some(Self::Smooth),
269            IntraMode::SmoothV => Some(Self::SmoothV),
270            IntraMode::SmoothH => Some(Self::SmoothH),
271            IntraMode::Paeth => Some(Self::Paeth),
272            _ => None,
273        }
274    }
275}
276
277/// Mode angle table for directional prediction.
278///
279/// Maps mode indices to base angles. Used for VP9 compatibility.
280pub const MODE_ANGLE_TABLE: [u16; 8] = [
281    90,  // V (Vertical)
282    180, // H (Horizontal)
283    45,  // D45
284    135, // D135
285    113, // D113 / D117 in VP9
286    157, // D157 / D153 in VP9
287    203, // D203 / D207 in VP9
288    67,  // D67 / D63 in VP9
289];
290
291/// VP9 directional mode mapping (VP9 uses different naming).
292#[derive(Clone, Copy, Debug, PartialEq, Eq)]
293pub enum Vp9DirectionalMode {
294    /// Vertical (V_PRED)
295    Vertical = 0,
296    /// Horizontal (H_PRED)
297    Horizontal = 1,
298    /// D45 prediction
299    D45 = 2,
300    /// D135 prediction
301    D135 = 3,
302    /// D117 prediction (similar to AV1 D113)
303    D117 = 4,
304    /// D153 prediction (similar to AV1 D157)
305    D153 = 5,
306    /// D207 prediction (similar to AV1 D203)
307    D207 = 6,
308    /// D63 prediction (similar to AV1 D67)
309    D63 = 7,
310}
311
312impl Vp9DirectionalMode {
313    /// Get angle in degrees.
314    #[must_use]
315    pub const fn angle(self) -> u16 {
316        match self {
317            Self::Vertical => 90,
318            Self::Horizontal => 180,
319            Self::D45 => 45,
320            Self::D135 => 135,
321            Self::D117 => 117,
322            Self::D153 => 153,
323            Self::D207 => 207,
324            Self::D63 => 63,
325        }
326    }
327
328    /// Convert to generic DirectionalMode.
329    #[must_use]
330    pub const fn to_directional(self) -> DirectionalMode {
331        DirectionalMode::new(self.angle())
332    }
333}
334
335/// Extended angle table for AV1 with deltas.
336/// Format: base_angle + delta * 3
337/// Range: 0 to 270 degrees with 3-degree granularity.
338pub const EXTENDED_ANGLES: [i16; 7] = [
339    -9, // delta = -3
340    -6, // delta = -2
341    -3, // delta = -1
342    0,  // delta = 0
343    3,  // delta = +1
344    6,  // delta = +2
345    9,  // delta = +3
346];
347
348/// Calculate the effective angle given base angle and delta.
349#[must_use]
350pub const fn calculate_effective_angle(base_angle: u16, delta: AngleDelta) -> i16 {
351    base_angle as i16 + delta.degrees()
352}
353
354/// Get the dx/dy values for directional prediction.
355///
356/// Returns (dx, dy) where each value is in 1/256ths of a pixel.
357/// Used for sub-pixel interpolation in directional modes.
358#[must_use]
359pub fn get_direction_delta(angle: i16) -> (i32, i32) {
360    // Angle 0 = top-left diagonal
361    // Angle 90 = vertical (straight down)
362    // Angle 180 = horizontal (straight right)
363    // Angle 270 = top-right diagonal
364
365    // Convert angle to radians for calculation
366    let radians = (angle as f64) * std::f64::consts::PI / 180.0;
367
368    // Calculate dx and dy in 1/256ths
369    // Note: In video codec coordinates, y increases downward
370    let dx = (radians.sin() * 256.0).round() as i32;
371    let dy = (radians.cos() * 256.0).round() as i32;
372
373    (dx, dy)
374}
375
376/// Precomputed dx values for angles 0-90 (in 1/256ths of a pixel).
377/// Index by angle.
378pub const DX_TABLE: [i32; 91] = {
379    let mut table = [0i32; 91];
380    let mut i = 0;
381    while i < 91 {
382        // sin(angle) * 256
383        // Using integer approximation for const fn
384        table[i] = match i {
385            0 => 0,
386            15 => 66,
387            30 => 128,
388            45 => 181,
389            60 => 222,
390            75 => 247,
391            90 => 256,
392            _ => {
393                // Linear interpolation between known values
394                let base = (i / 15) * 15;
395                let next = base + 15;
396                if next > 90 {
397                    256
398                } else {
399                    let base_val = match base {
400                        0 => 0,
401                        15 => 66,
402                        30 => 128,
403                        45 => 181,
404                        60 => 222,
405                        75 => 247,
406                        _ => 0,
407                    };
408                    let next_val = match next {
409                        15 => 66,
410                        30 => 128,
411                        45 => 181,
412                        60 => 222,
413                        75 => 247,
414                        90 => 256,
415                        _ => 256,
416                    };
417                    base_val + ((next_val - base_val) * ((i - base) as i32)) / 15
418                }
419            }
420        };
421        i += 1;
422    }
423    table
424};
425
426/// Precomputed dy values for angles 0-90 (in 1/256ths of a pixel).
427pub const DY_TABLE: [i32; 91] = {
428    let mut table = [0i32; 91];
429    let mut i = 0;
430    while i < 91 {
431        // cos(angle) * 256
432        table[i] = match i {
433            0 => 256,
434            15 => 247,
435            30 => 222,
436            45 => 181,
437            60 => 128,
438            75 => 66,
439            90 => 0,
440            _ => {
441                let base = (i / 15) * 15;
442                let next = base + 15;
443                if next > 90 {
444                    0
445                } else {
446                    let base_val = match base {
447                        0 => 256,
448                        15 => 247,
449                        30 => 222,
450                        45 => 181,
451                        60 => 128,
452                        75 => 66,
453                        _ => 0,
454                    };
455                    let next_val = match next {
456                        15 => 247,
457                        30 => 222,
458                        45 => 181,
459                        60 => 128,
460                        75 => 66,
461                        90 => 0,
462                        _ => 0,
463                    };
464                    base_val + ((next_val - base_val) * ((i - base) as i32)) / 15
465                }
466            }
467        };
468        i += 1;
469    }
470    table
471};
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    #[test]
478    fn test_intra_mode_directional() {
479        assert!(IntraMode::Vertical.is_directional());
480        assert!(IntraMode::Horizontal.is_directional());
481        assert!(IntraMode::D45.is_directional());
482        assert!(IntraMode::D135.is_directional());
483        assert!(!IntraMode::Dc.is_directional());
484        assert!(!IntraMode::Smooth.is_directional());
485        assert!(!IntraMode::Paeth.is_directional());
486    }
487
488    #[test]
489    fn test_intra_mode_smooth() {
490        assert!(IntraMode::Smooth.is_smooth());
491        assert!(IntraMode::SmoothV.is_smooth());
492        assert!(IntraMode::SmoothH.is_smooth());
493        assert!(!IntraMode::Dc.is_smooth());
494        assert!(!IntraMode::Vertical.is_smooth());
495    }
496
497    #[test]
498    fn test_angle_delta() {
499        assert_eq!(AngleDelta::Zero.degrees(), 0);
500        assert_eq!(AngleDelta::Plus1.degrees(), 3);
501        assert_eq!(AngleDelta::Plus3.degrees(), 9);
502        assert_eq!(AngleDelta::Minus1.degrees(), -3);
503        assert_eq!(AngleDelta::Minus3.degrees(), -9);
504    }
505
506    #[test]
507    fn test_directional_mode() {
508        let mode = DirectionalMode::new(90);
509        assert_eq!(mode.effective_angle(), 90);
510        assert!(mode.is_vertical_ish());
511        assert!(!mode.is_horizontal_ish());
512
513        let mode_with_delta = DirectionalMode::with_delta(90, AngleDelta::Plus2);
514        assert_eq!(mode_with_delta.effective_angle(), 96);
515    }
516
517    #[test]
518    fn test_vp9_directional_mode() {
519        assert_eq!(Vp9DirectionalMode::Vertical.angle(), 90);
520        assert_eq!(Vp9DirectionalMode::Horizontal.angle(), 180);
521        assert_eq!(Vp9DirectionalMode::D45.angle(), 45);
522        assert_eq!(Vp9DirectionalMode::D117.angle(), 117);
523    }
524
525    #[test]
526    fn test_intra_mode_from_u8() {
527        assert_eq!(IntraMode::from_u8(0), Some(IntraMode::Dc));
528        assert_eq!(IntraMode::from_u8(1), Some(IntraMode::Vertical));
529        assert_eq!(IntraMode::from_u8(12), Some(IntraMode::Paeth));
530        assert_eq!(IntraMode::from_u8(100), None);
531    }
532
533    #[test]
534    fn test_nominal_angles() {
535        assert_eq!(IntraMode::Vertical.nominal_angle(), Some(90));
536        assert_eq!(IntraMode::Horizontal.nominal_angle(), Some(180));
537        assert_eq!(IntraMode::D45.nominal_angle(), Some(45));
538        assert_eq!(IntraMode::Dc.nominal_angle(), None);
539    }
540
541    #[test]
542    fn test_angle_delta_from_steps() {
543        assert_eq!(AngleDelta::from_steps(0), Some(AngleDelta::Zero));
544        assert_eq!(AngleDelta::from_steps(3), Some(AngleDelta::Plus3));
545        assert_eq!(AngleDelta::from_steps(-3), Some(AngleDelta::Minus3));
546        assert_eq!(AngleDelta::from_steps(4), None);
547        assert_eq!(AngleDelta::from_steps(-4), None);
548    }
549}