Skip to main content

oximedia_codec/av1/
cdef.rs

1//! AV1 CDEF (Constrained Directional Enhancement Filter) parameters.
2//!
3//! CDEF is a directional filter applied after the loop filter to reduce
4//! ringing and mosquito noise artifacts. It uses directional filtering
5//! along edges to preserve sharpness while reducing noise.
6//!
7//! # CDEF Algorithm
8//!
9//! 1. For each 8x8 block, detect the edge direction
10//! 2. Apply filtering along and perpendicular to the edge
11//! 3. Use different strengths for primary (along edge) and secondary (perpendicular)
12//!
13//! # Parameters
14//!
15//! - Damping: Controls how much filtering is applied (higher = less filtering)
16//! - Primary strength: Filter strength along the detected edge direction
17//! - Secondary strength: Filter strength perpendicular to the edge
18//! - CDEF bits: Number of bits to signal CDEF preset index
19//!
20//! # Reference
21//!
22//! See AV1 Specification Section 5.9.19 for CDEF syntax and
23//! Section 7.15 for CDEF semantics.
24
25#![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
41// =============================================================================
42// Constants
43// =============================================================================
44
45/// Maximum CDEF bits.
46pub const CDEF_MAX_BITS: u8 = 3;
47
48/// Maximum number of CDEF presets (2^CDEF_MAX_BITS).
49pub const CDEF_MAX_PRESETS: usize = 8;
50
51/// Maximum primary strength.
52pub const CDEF_MAX_PRIMARY_STRENGTH: u8 = 15;
53
54/// Maximum secondary strength.
55pub const CDEF_MAX_SECONDARY_STRENGTH: u8 = 4;
56
57/// CDEF block size (8x8).
58pub const CDEF_BLOCK_SIZE: usize = 8;
59
60/// Number of directions for CDEF.
61pub const CDEF_NUM_DIRECTIONS: usize = 8;
62
63/// Minimum damping value.
64pub const CDEF_DAMPING_MIN: u8 = 3;
65
66/// Maximum damping value.
67pub const CDEF_DAMPING_MAX: u8 = 6;
68
69/// CDEF secondary strength table.
70pub const CDEF_SEC_STRENGTHS: [u8; 4] = [0, 1, 2, 4];
71
72// =============================================================================
73// Structures
74// =============================================================================
75
76/// CDEF strength parameters for a single preset.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
78pub struct CdefStrength {
79    /// Primary strength (0-15).
80    pub primary: u8,
81    /// Secondary strength index (0-3, maps to 0, 1, 2, 4).
82    pub secondary: u8,
83}
84
85impl CdefStrength {
86    /// Create a new CDEF strength.
87    #[must_use]
88    pub const fn new(primary: u8, secondary: u8) -> Self {
89        Self { primary, secondary }
90    }
91
92    /// Get the actual secondary strength value.
93    #[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    /// Check if this preset has any filtering.
102    #[must_use]
103    pub fn is_enabled(&self) -> bool {
104        self.primary > 0 || self.secondary > 0
105    }
106
107    /// Parse a CDEF strength from bitstream.
108    #[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/// CDEF parameters as parsed from the frame header.
117#[derive(Clone, Debug)]
118pub struct CdefParams {
119    /// Damping value for Y plane.
120    pub damping_y: u8,
121    /// Damping value for UV planes.
122    pub damping_uv: u8,
123    /// Number of bits to signal CDEF preset index.
124    pub bits: u8,
125    /// Y plane strength presets.
126    pub y_strengths: [CdefStrength; CDEF_MAX_PRESETS],
127    /// UV plane strength presets.
128    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    /// Create new CDEF parameters with default values.
145    #[must_use]
146    pub fn new() -> Self {
147        Self::default()
148    }
149
150    /// Parse CDEF parameters from the bitstream.
151    ///
152    /// # Errors
153    ///
154    /// Returns error if the bitstream is malformed.
155    #[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        // Damping
161        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        // Number of CDEF bits
166        cdef.bits = reader.read_bits(2).map_err(CodecError::Core)? as u8;
167
168        // Parse Y strengths
169        let num_presets = 1usize << cdef.bits;
170        for i in 0..num_presets {
171            cdef.y_strengths[i] = CdefStrength::parse(reader)?;
172        }
173
174        // Parse UV strengths (if not monochrome)
175        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    /// Get the number of CDEF presets.
185    #[must_use]
186    pub fn num_presets(&self) -> usize {
187        1usize << self.bits
188    }
189
190    /// Check if CDEF is enabled (at least one preset has non-zero strength).
191    #[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    /// Get the Y strength for a preset index.
203    #[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    /// Get the UV strength for a preset index.
209    #[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    /// Get damping for a specific plane.
215    #[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    /// Get the adjusted damping value for a bit depth.
225    #[must_use]
226    pub fn adjusted_damping(&self, plane: usize, bit_depth: u8) -> u8 {
227        let base = self.get_damping(plane);
228        // Damping is adjusted based on bit depth
229        base + (bit_depth - 8).min(4)
230    }
231
232    /// Check if a specific preset index is valid.
233    #[must_use]
234    pub fn is_valid_preset(&self, idx: usize) -> bool {
235        idx < self.num_presets()
236    }
237}
238
239/// CDEF direction and variance information for a block.
240#[derive(Clone, Copy, Debug, Default)]
241pub struct CdefDirection {
242    /// Detected direction (0-7).
243    pub direction: u8,
244    /// Variance of the direction.
245    pub variance: u32,
246}
247
248impl CdefDirection {
249    /// Create a new CDEF direction.
250    #[must_use]
251    pub const fn new(direction: u8, variance: u32) -> Self {
252        Self {
253            direction,
254            variance,
255        }
256    }
257
258    /// Get the opposite direction.
259    #[must_use]
260    pub const fn opposite(&self) -> u8 {
261        (self.direction + 4) % CDEF_NUM_DIRECTIONS as u8
262    }
263
264    /// Get the perpendicular direction (clockwise).
265    #[must_use]
266    pub const fn perpendicular_cw(&self) -> u8 {
267        (self.direction + 2) % CDEF_NUM_DIRECTIONS as u8
268    }
269
270    /// Get the perpendicular direction (counter-clockwise).
271    #[must_use]
272    pub const fn perpendicular_ccw(&self) -> u8 {
273        (self.direction + 6) % CDEF_NUM_DIRECTIONS as u8
274    }
275}
276
277/// Direction offsets for CDEF filtering.
278///
279/// Each direction has offsets for the primary and secondary taps.
280#[derive(Clone, Copy, Debug)]
281pub struct CdefDirectionOffsets {
282    /// Primary direction tap offsets (2 taps).
283    pub primary: [(i8, i8); 2],
284    /// Secondary direction tap offsets (4 taps).
285    pub secondary: [(i8, i8); 4],
286}
287
288/// Direction kernel offsets for all 8 directions.
289pub const CDEF_DIRECTION_OFFSETS: [CdefDirectionOffsets; CDEF_NUM_DIRECTIONS] = [
290    // Direction 0: Horizontal
291    CdefDirectionOffsets {
292        primary: [(-1, 0), (1, 0)],
293        secondary: [(-2, 0), (2, 0), (-1, -1), (1, 1)],
294    },
295    // Direction 1: 22.5 degrees
296    CdefDirectionOffsets {
297        primary: [(-1, -1), (1, 1)],
298        secondary: [(-2, -1), (2, 1), (-1, -2), (1, 2)],
299    },
300    // Direction 2: 45 degrees
301    CdefDirectionOffsets {
302        primary: [(0, -1), (0, 1)],
303        secondary: [(-1, -1), (1, 1), (-1, -2), (1, 2)],
304    },
305    // Direction 3: 67.5 degrees
306    CdefDirectionOffsets {
307        primary: [(1, -1), (-1, 1)],
308        secondary: [(2, -1), (-2, 1), (1, -2), (-1, 2)],
309    },
310    // Direction 4: Vertical
311    CdefDirectionOffsets {
312        primary: [(0, -1), (0, 1)],
313        secondary: [(0, -2), (0, 2), (-1, -1), (1, 1)],
314    },
315    // Direction 5: 112.5 degrees
316    CdefDirectionOffsets {
317        primary: [(-1, -1), (1, 1)],
318        secondary: [(-2, -1), (2, 1), (-1, -2), (1, 2)],
319    },
320    // Direction 6: 135 degrees
321    CdefDirectionOffsets {
322        primary: [(-1, 0), (1, 0)],
323        secondary: [(-1, -1), (1, 1), (-2, 0), (2, 0)],
324    },
325    // Direction 7: 157.5 degrees
326    CdefDirectionOffsets {
327        primary: [(1, -1), (-1, 1)],
328        secondary: [(2, -1), (-2, 1), (1, -2), (-1, 2)],
329    },
330];
331
332/// CDEF filter tap weights.
333#[derive(Clone, Copy, Debug)]
334pub struct CdefTapWeights {
335    /// Primary tap weight.
336    pub primary: i16,
337    /// Secondary tap weight.
338    pub secondary: i16,
339}
340
341impl CdefTapWeights {
342    /// Create tap weights from strengths and damping.
343    #[must_use]
344    pub fn from_strengths(strength: &CdefStrength, _damping: u8) -> Self {
345        // Weights depend on strength and damping
346        let primary = i16::from(strength.primary);
347        let secondary = i16::from(strength.secondary_value());
348
349        Self { primary, secondary }
350    }
351}
352
353/// CDEF block filter state.
354#[derive(Clone, Debug, Default)]
355pub struct CdefBlockState {
356    /// CDEF preset index for this block.
357    pub preset_idx: u8,
358    /// Skip CDEF for this block.
359    pub skip: bool,
360    /// Detected direction.
361    pub direction: CdefDirection,
362}
363
364impl CdefBlockState {
365    /// Create a new CDEF block state.
366    #[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    /// Check if filtering should be applied.
379    #[must_use]
380    pub const fn should_filter(&self) -> bool {
381        !self.skip
382    }
383}
384
385/// CDEF superblock information.
386#[derive(Clone, Debug)]
387pub struct CdefSuperblock {
388    /// CDEF indices for each 8x8 block in the superblock.
389    /// For 64x64 SB: 64 blocks (8x8), for 128x128 SB: 256 blocks.
390    pub block_indices: Vec<u8>,
391    /// Superblock size (64 or 128).
392    pub sb_size: usize,
393}
394
395impl CdefSuperblock {
396    /// Create a new CDEF superblock.
397    #[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    /// Get the CDEF index for a block at the given position.
407    #[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    /// Set the CDEF index for a block.
415    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    /// Get the number of 8x8 blocks in the superblock.
424    #[must_use]
425    pub fn num_blocks(&self) -> usize {
426        self.block_indices.len()
427    }
428
429    /// Get the number of blocks per row/column.
430    #[must_use]
431    pub fn blocks_per_side(&self) -> usize {
432        self.sb_size / CDEF_BLOCK_SIZE
433    }
434}
435
436/// CDEF configuration for a frame.
437#[derive(Clone, Debug)]
438pub struct CdefFrameConfig {
439    /// CDEF parameters from frame header.
440    pub params: CdefParams,
441    /// Bit depth.
442    pub bit_depth: u8,
443    /// Frame width in pixels.
444    pub width: u32,
445    /// Frame height in pixels.
446    pub height: u32,
447}
448
449impl CdefFrameConfig {
450    /// Create a new CDEF frame configuration.
451    #[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    /// Get the number of 8x8 blocks in the frame (width).
462    #[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    /// Get the number of 8x8 blocks in the frame (height).
468    #[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    /// Get total number of 8x8 blocks.
474    #[must_use]
475    pub fn total_blocks(&self) -> u32 {
476        self.blocks_wide() * self.blocks_high()
477    }
478
479    /// Get the adjusted damping for a plane.
480    #[must_use]
481    pub fn get_damping(&self, plane: usize) -> u8 {
482        self.params.adjusted_damping(plane, self.bit_depth)
483    }
484}
485
486// =============================================================================
487// Helper Functions
488// =============================================================================
489
490/// Compute the CDEF filter value with clamping.
491#[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/// Calculate the clipping value for CDEF.
514#[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// =============================================================================
526// Tests
527// =============================================================================
528
529#[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        // Zero threshold returns zero
691        assert_eq!(constrain(10, 0, 3), 0);
692
693        // Positive diff
694        let result = constrain(5, 10, 3);
695        assert!(result >= 0 && result <= 5);
696
697        // Negative diff
698        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        // Horizontal direction
712        let offsets = &CDEF_DIRECTION_OFFSETS[0];
713        assert_eq!(offsets.primary[0], (-1, 0));
714        assert_eq!(offsets.primary[1], (1, 0));
715
716        // Vertical direction (direction 4)
717        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}