Skip to main content

oximedia_codec/motion/
types.rs

1//! Core types for motion estimation.
2//!
3//! This module provides fundamental types used throughout the motion
4//! estimation pipeline, including motion vectors, search ranges, and
5//! block matching results.
6
7#![forbid(unsafe_code)]
8#![allow(dead_code)]
9#![allow(clippy::similar_names)]
10#![allow(clippy::cast_precision_loss)]
11#![allow(clippy::cast_sign_loss)]
12#![allow(clippy::cast_possible_truncation)]
13#![allow(clippy::trivially_copy_pass_by_ref)]
14
15use std::ops::{Add, Neg, Sub};
16
17/// Maximum motion vector component magnitude (in sub-pixel units).
18pub const MV_MAX: i32 = 16383 * 8; // 1/8 pel precision
19
20/// Minimum motion vector component magnitude (in sub-pixel units).
21pub const MV_MIN: i32 = -16384 * 8;
22
23/// Default search range in pixels.
24pub const DEFAULT_SEARCH_RANGE: i32 = 64;
25
26/// Motion vector precision levels.
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
28#[repr(u8)]
29pub enum MvPrecision {
30    /// Full pixel precision (integer pel).
31    FullPel = 0,
32    /// Half pixel precision (1/2 pel).
33    HalfPel = 1,
34    /// Quarter pixel precision (1/4 pel).
35    #[default]
36    QuarterPel = 2,
37    /// Eighth pixel precision (1/8 pel).
38    EighthPel = 3,
39}
40
41impl MvPrecision {
42    /// Returns the number of fractional bits for this precision.
43    #[must_use]
44    pub const fn fractional_bits(self) -> u8 {
45        match self {
46            Self::FullPel => 0,
47            Self::HalfPel => 1,
48            Self::QuarterPel => 2,
49            Self::EighthPel => 3,
50        }
51    }
52
53    /// Returns the scale factor for sub-pixel units.
54    #[must_use]
55    pub const fn scale(self) -> i32 {
56        1 << self.fractional_bits()
57    }
58
59    /// Returns the mask for extracting fractional part.
60    #[must_use]
61    pub const fn frac_mask(self) -> i32 {
62        self.scale() - 1
63    }
64
65    /// Converts a value from this precision to another.
66    #[must_use]
67    pub const fn convert(self, value: i32, target: Self) -> i32 {
68        let src_bits = self.fractional_bits() as i32;
69        let dst_bits = target.fractional_bits() as i32;
70        let shift = dst_bits - src_bits;
71        if shift > 0 {
72            value << shift
73        } else {
74            value >> (-shift)
75        }
76    }
77}
78
79/// A motion vector with sub-pixel precision.
80///
81/// Components are stored in 1/8 pixel (eighth-pel) precision internally.
82/// This allows conversion to any lower precision without loss.
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
84pub struct MotionVector {
85    /// Horizontal displacement (dx) in 1/8 pixel units.
86    pub dx: i32,
87    /// Vertical displacement (dy) in 1/8 pixel units.
88    pub dy: i32,
89}
90
91impl MotionVector {
92    /// Creates a zero motion vector.
93    #[must_use]
94    pub const fn zero() -> Self {
95        Self { dx: 0, dy: 0 }
96    }
97
98    /// Creates a motion vector with the given components (in 1/8 pel).
99    #[must_use]
100    pub const fn new(dx: i32, dy: i32) -> Self {
101        Self { dx, dy }
102    }
103
104    /// Creates a motion vector from full-pixel coordinates.
105    #[must_use]
106    pub const fn from_full_pel(dx: i32, dy: i32) -> Self {
107        Self {
108            dx: dx << 3,
109            dy: dy << 3,
110        }
111    }
112
113    /// Creates a motion vector at the given precision.
114    #[must_use]
115    pub const fn from_precision(dx: i32, dy: i32, precision: MvPrecision) -> Self {
116        let shift = 3 - precision.fractional_bits() as i32;
117        Self {
118            dx: dx << shift,
119            dy: dy << shift,
120        }
121    }
122
123    /// Returns true if this is a zero motion vector.
124    #[must_use]
125    pub const fn is_zero(&self) -> bool {
126        self.dx == 0 && self.dy == 0
127    }
128
129    /// Returns the full-pixel horizontal component.
130    #[must_use]
131    pub const fn full_pel_x(&self) -> i32 {
132        self.dx >> 3
133    }
134
135    /// Returns the full-pixel vertical component.
136    #[must_use]
137    pub const fn full_pel_y(&self) -> i32 {
138        self.dy >> 3
139    }
140
141    /// Returns the fractional horizontal component (0-7).
142    #[must_use]
143    pub const fn frac_x(&self) -> i32 {
144        self.dx & 7
145    }
146
147    /// Returns the fractional vertical component (0-7).
148    #[must_use]
149    pub const fn frac_y(&self) -> i32 {
150        self.dy & 7
151    }
152
153    /// Returns the half-pel x component (0-1).
154    #[must_use]
155    pub const fn half_pel_x(&self) -> i32 {
156        (self.dx >> 2) & 1
157    }
158
159    /// Returns the half-pel y component (0-1).
160    #[must_use]
161    pub const fn half_pel_y(&self) -> i32 {
162        (self.dy >> 2) & 1
163    }
164
165    /// Returns the quarter-pel x component (0-3).
166    #[must_use]
167    pub const fn quarter_pel_x(&self) -> i32 {
168        (self.dx >> 1) & 3
169    }
170
171    /// Returns the quarter-pel y component (0-3).
172    #[must_use]
173    pub const fn quarter_pel_y(&self) -> i32 {
174        (self.dy >> 1) & 3
175    }
176
177    /// Converts to the specified precision (may lose fractional bits).
178    #[must_use]
179    pub const fn to_precision(&self, precision: MvPrecision) -> Self {
180        let shift = 3 - precision.fractional_bits() as i32;
181        let mask = !((1 << shift) - 1);
182        Self {
183            dx: self.dx & mask,
184            dy: self.dy & mask,
185        }
186    }
187
188    /// Rounds to the specified precision.
189    #[must_use]
190    pub const fn round_to_precision(&self, precision: MvPrecision) -> Self {
191        let shift = 3 - precision.fractional_bits() as i32;
192        let round = 1 << (shift - 1);
193        if shift > 0 {
194            Self {
195                dx: ((self.dx + round) >> shift) << shift,
196                dy: ((self.dy + round) >> shift) << shift,
197            }
198        } else {
199            *self
200        }
201    }
202
203    /// Clamps the motion vector to valid range.
204    #[must_use]
205    pub fn clamp(&self) -> Self {
206        Self {
207            dx: self.dx.clamp(MV_MIN, MV_MAX),
208            dy: self.dy.clamp(MV_MIN, MV_MAX),
209        }
210    }
211
212    /// Clamps to the specified search range (in full pixels).
213    #[must_use]
214    pub fn clamp_to_range(&self, range: &SearchRange) -> Self {
215        Self {
216            dx: self.dx.clamp(-range.horizontal << 3, range.horizontal << 3),
217            dy: self.dy.clamp(-range.vertical << 3, range.vertical << 3),
218        }
219    }
220
221    /// Returns the squared magnitude.
222    #[must_use]
223    pub const fn magnitude_squared(&self) -> i64 {
224        (self.dx as i64) * (self.dx as i64) + (self.dy as i64) * (self.dy as i64)
225    }
226
227    /// Returns the L1 norm (Manhattan distance).
228    #[must_use]
229    pub const fn l1_norm(&self) -> i32 {
230        self.dx.abs() + self.dy.abs()
231    }
232
233    /// Returns the L-infinity norm (Chebyshev distance).
234    #[must_use]
235    pub fn linf_norm(&self) -> i32 {
236        self.dx.abs().max(self.dy.abs())
237    }
238
239    /// Scales the motion vector.
240    #[must_use]
241    #[allow(clippy::cast_possible_truncation)]
242    pub fn scale(&self, num: i32, den: i32) -> Self {
243        if den == 0 {
244            return *self;
245        }
246        Self {
247            dx: ((i64::from(self.dx) * i64::from(num)) / i64::from(den)) as i32,
248            dy: ((i64::from(self.dy) * i64::from(num)) / i64::from(den)) as i32,
249        }
250    }
251}
252
253impl Add for MotionVector {
254    type Output = Self;
255
256    fn add(self, other: Self) -> Self {
257        Self {
258            dx: self.dx.saturating_add(other.dx),
259            dy: self.dy.saturating_add(other.dy),
260        }
261    }
262}
263
264impl Sub for MotionVector {
265    type Output = Self;
266
267    fn sub(self, other: Self) -> Self {
268        Self {
269            dx: self.dx.saturating_sub(other.dx),
270            dy: self.dy.saturating_sub(other.dy),
271        }
272    }
273}
274
275impl Neg for MotionVector {
276    type Output = Self;
277
278    fn neg(self) -> Self {
279        Self {
280            dx: self.dx.saturating_neg(),
281            dy: self.dy.saturating_neg(),
282        }
283    }
284}
285
286/// Search range for motion estimation.
287#[derive(Clone, Copy, Debug, PartialEq, Eq)]
288pub struct SearchRange {
289    /// Horizontal search range in full pixels.
290    pub horizontal: i32,
291    /// Vertical search range in full pixels.
292    pub vertical: i32,
293}
294
295impl Default for SearchRange {
296    fn default() -> Self {
297        Self::new(DEFAULT_SEARCH_RANGE, DEFAULT_SEARCH_RANGE)
298    }
299}
300
301impl SearchRange {
302    /// Creates a new search range.
303    #[must_use]
304    pub const fn new(horizontal: i32, vertical: i32) -> Self {
305        Self {
306            horizontal,
307            vertical,
308        }
309    }
310
311    /// Creates a symmetric search range.
312    #[must_use]
313    pub const fn symmetric(range: i32) -> Self {
314        Self::new(range, range)
315    }
316
317    /// Returns the total number of search positions.
318    #[must_use]
319    pub const fn num_positions(&self) -> u64 {
320        let w = (2 * self.horizontal + 1) as u64;
321        let h = (2 * self.vertical + 1) as u64;
322        w * h
323    }
324
325    /// Checks if a position is within the search range.
326    #[must_use]
327    pub const fn contains(&self, dx: i32, dy: i32) -> bool {
328        dx >= -self.horizontal
329            && dx <= self.horizontal
330            && dy >= -self.vertical
331            && dy <= self.vertical
332    }
333
334    /// Returns a scaled search range.
335    #[must_use]
336    pub const fn scale(&self, factor: i32) -> Self {
337        Self {
338            horizontal: self.horizontal * factor,
339            vertical: self.vertical * factor,
340        }
341    }
342
343    /// Returns a reduced search range (for refinement).
344    #[must_use]
345    pub const fn reduce(&self, factor: i32) -> Self {
346        if factor == 0 {
347            *self
348        } else {
349            Self {
350                horizontal: self.horizontal / factor,
351                vertical: self.vertical / factor,
352            }
353        }
354    }
355}
356
357/// Result of a block matching operation.
358#[derive(Clone, Copy, Debug, PartialEq, Eq)]
359pub struct BlockMatch {
360    /// Motion vector.
361    pub mv: MotionVector,
362    /// Sum of Absolute Differences (distortion).
363    pub sad: u32,
364    /// Rate-distortion cost (if computed).
365    pub cost: u32,
366}
367
368impl Default for BlockMatch {
369    fn default() -> Self {
370        Self::worst()
371    }
372}
373
374impl BlockMatch {
375    /// Creates a new block match result.
376    #[must_use]
377    pub const fn new(mv: MotionVector, sad: u32, cost: u32) -> Self {
378        Self { mv, sad, cost }
379    }
380
381    /// Creates a zero motion vector match.
382    #[must_use]
383    pub const fn zero_mv(sad: u32) -> Self {
384        Self {
385            mv: MotionVector::zero(),
386            sad,
387            cost: sad,
388        }
389    }
390
391    /// Creates the worst possible match (for initialization).
392    #[must_use]
393    pub const fn worst() -> Self {
394        Self {
395            mv: MotionVector::zero(),
396            sad: u32::MAX,
397            cost: u32::MAX,
398        }
399    }
400
401    /// Returns true if this match is better than another.
402    #[must_use]
403    pub const fn is_better_than(&self, other: &Self) -> bool {
404        self.cost < other.cost
405    }
406
407    /// Updates with a better match if found.
408    pub fn update_if_better(&mut self, other: &Self) {
409        if other.is_better_than(self) {
410            *self = *other;
411        }
412    }
413}
414
415/// Motion vector cost calculator for rate-distortion optimization.
416#[derive(Clone, Copy, Debug)]
417pub struct MvCost {
418    /// Lambda for rate-distortion tradeoff.
419    pub lambda: f32,
420    /// Weight for MV bits.
421    pub mv_weight: f32,
422    /// Reference motion vector for differential coding.
423    pub ref_mv: MotionVector,
424}
425
426impl Default for MvCost {
427    fn default() -> Self {
428        Self::new(1.0)
429    }
430}
431
432impl MvCost {
433    /// Creates a new MV cost calculator.
434    #[must_use]
435    pub const fn new(lambda: f32) -> Self {
436        Self {
437            lambda,
438            mv_weight: 1.0,
439            ref_mv: MotionVector::zero(),
440        }
441    }
442
443    /// Creates with a reference motion vector.
444    #[must_use]
445    pub const fn with_ref_mv(lambda: f32, ref_mv: MotionVector) -> Self {
446        Self {
447            lambda,
448            mv_weight: 1.0,
449            ref_mv,
450        }
451    }
452
453    /// Estimates the bit cost of a motion vector.
454    #[must_use]
455    pub fn estimate_bits(&self, mv: &MotionVector) -> f32 {
456        let diff = *mv - self.ref_mv;
457        let dx_bits = Self::component_bits(diff.dx);
458        let dy_bits = Self::component_bits(diff.dy);
459        (dx_bits + dy_bits) * self.mv_weight
460    }
461
462    /// Estimates bits for a single component.
463    #[must_use]
464    fn component_bits(value: i32) -> f32 {
465        if value == 0 {
466            return 1.0;
467        }
468        let abs_val = value.unsigned_abs();
469        // Approximate: 2 * log2(abs) + constant overhead
470        let log2_approx = 32 - abs_val.leading_zeros();
471        (2 * log2_approx + 2) as f32
472    }
473
474    /// Calculates the rate-distortion cost.
475    #[must_use]
476    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
477    pub fn rd_cost(&self, mv: &MotionVector, sad: u32) -> u32 {
478        let bits = self.estimate_bits(mv);
479        let rate_cost = (bits * self.lambda) as u32;
480        sad.saturating_add(rate_cost)
481    }
482
483    /// Updates the reference motion vector.
484    pub fn set_ref_mv(&mut self, ref_mv: MotionVector) {
485        self.ref_mv = ref_mv;
486    }
487}
488
489/// Block size for motion estimation.
490#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
491#[repr(u8)]
492pub enum BlockSize {
493    /// 4x4 block.
494    Block4x4 = 0,
495    /// 4x8 block.
496    Block4x8 = 1,
497    /// 8x4 block.
498    Block8x4 = 2,
499    /// 8x8 block.
500    #[default]
501    Block8x8 = 3,
502    /// 8x16 block.
503    Block8x16 = 4,
504    /// 16x8 block.
505    Block16x8 = 5,
506    /// 16x16 block.
507    Block16x16 = 6,
508    /// 16x32 block.
509    Block16x32 = 7,
510    /// 32x16 block.
511    Block32x16 = 8,
512    /// 32x32 block.
513    Block32x32 = 9,
514    /// 32x64 block.
515    Block32x64 = 10,
516    /// 64x32 block.
517    Block64x32 = 11,
518    /// 64x64 block.
519    Block64x64 = 12,
520    /// 64x128 block.
521    Block64x128 = 13,
522    /// 128x64 block.
523    Block128x64 = 14,
524    /// 128x128 block.
525    Block128x128 = 15,
526}
527
528impl BlockSize {
529    /// Returns the width in pixels.
530    #[must_use]
531    pub const fn width(&self) -> usize {
532        match self {
533            Self::Block4x4 | Self::Block4x8 => 4,
534            Self::Block8x4 | Self::Block8x8 | Self::Block8x16 => 8,
535            Self::Block16x8 | Self::Block16x16 | Self::Block16x32 => 16,
536            Self::Block32x16 | Self::Block32x32 | Self::Block32x64 => 32,
537            Self::Block64x32 | Self::Block64x64 | Self::Block64x128 => 64,
538            Self::Block128x64 | Self::Block128x128 => 128,
539        }
540    }
541
542    /// Returns the height in pixels.
543    #[must_use]
544    pub const fn height(&self) -> usize {
545        match self {
546            Self::Block4x4 | Self::Block8x4 => 4,
547            Self::Block4x8 | Self::Block8x8 | Self::Block16x8 => 8,
548            Self::Block8x16 | Self::Block16x16 | Self::Block32x16 => 16,
549            Self::Block16x32 | Self::Block32x32 | Self::Block64x32 => 32,
550            Self::Block32x64 | Self::Block64x64 | Self::Block128x64 => 64,
551            Self::Block64x128 | Self::Block128x128 => 128,
552        }
553    }
554
555    /// Returns the number of pixels in the block.
556    #[must_use]
557    pub const fn num_pixels(&self) -> usize {
558        self.width() * self.height()
559    }
560
561    /// Returns true if the block is square.
562    #[must_use]
563    pub const fn is_square(&self) -> bool {
564        matches!(
565            self,
566            Self::Block4x4
567                | Self::Block8x8
568                | Self::Block16x16
569                | Self::Block32x32
570                | Self::Block64x64
571                | Self::Block128x128
572        )
573    }
574
575    /// Returns the log2 of width.
576    #[must_use]
577    pub const fn width_log2(&self) -> u8 {
578        match self.width() {
579            4 => 2,
580            8 => 3,
581            16 => 4,
582            32 => 5,
583            64 => 6,
584            128 => 7,
585            _ => 0,
586        }
587    }
588
589    /// Returns the log2 of height.
590    #[must_use]
591    pub const fn height_log2(&self) -> u8 {
592        match self.height() {
593            4 => 2,
594            8 => 3,
595            16 => 4,
596            32 => 5,
597            64 => 6,
598            128 => 7,
599            _ => 0,
600        }
601    }
602}
603
604#[cfg(test)]
605mod tests {
606    use super::*;
607
608    #[test]
609    fn test_mv_precision() {
610        assert_eq!(MvPrecision::FullPel.fractional_bits(), 0);
611        assert_eq!(MvPrecision::HalfPel.fractional_bits(), 1);
612        assert_eq!(MvPrecision::QuarterPel.fractional_bits(), 2);
613        assert_eq!(MvPrecision::EighthPel.fractional_bits(), 3);
614
615        assert_eq!(MvPrecision::FullPel.scale(), 1);
616        assert_eq!(MvPrecision::QuarterPel.scale(), 4);
617        assert_eq!(MvPrecision::EighthPel.scale(), 8);
618    }
619
620    #[test]
621    fn test_mv_precision_convert() {
622        // Full pel to quarter pel
623        assert_eq!(MvPrecision::FullPel.convert(2, MvPrecision::QuarterPel), 8);
624        // Quarter pel to full pel
625        assert_eq!(MvPrecision::QuarterPel.convert(8, MvPrecision::FullPel), 2);
626    }
627
628    #[test]
629    fn test_motion_vector_creation() {
630        let mv = MotionVector::new(16, -24);
631        assert_eq!(mv.dx, 16);
632        assert_eq!(mv.dy, -24);
633
634        let mv_fp = MotionVector::from_full_pel(2, -3);
635        assert_eq!(mv_fp.dx, 16);
636        assert_eq!(mv_fp.dy, -24);
637    }
638
639    #[test]
640    fn test_motion_vector_components() {
641        let mv = MotionVector::new(27, -19); // 3.375, -2.375 in full pixels
642
643        assert_eq!(mv.full_pel_x(), 3);
644        assert_eq!(mv.full_pel_y(), -3); // -19 >> 3 = -3
645        assert_eq!(mv.frac_x(), 3);
646        assert_eq!(mv.frac_y(), -19 & 7);
647    }
648
649    #[test]
650    fn test_motion_vector_zero() {
651        let mv = MotionVector::zero();
652        assert!(mv.is_zero());
653        assert_eq!(mv.magnitude_squared(), 0);
654    }
655
656    #[test]
657    fn test_motion_vector_arithmetic() {
658        let mv1 = MotionVector::new(10, 20);
659        let mv2 = MotionVector::new(5, -10);
660
661        let sum = mv1 + mv2;
662        assert_eq!(sum.dx, 15);
663        assert_eq!(sum.dy, 10);
664
665        let diff = mv1 - mv2;
666        assert_eq!(diff.dx, 5);
667        assert_eq!(diff.dy, 30);
668
669        let neg = -mv1;
670        assert_eq!(neg.dx, -10);
671        assert_eq!(neg.dy, -20);
672    }
673
674    #[test]
675    fn test_motion_vector_magnitude() {
676        let mv = MotionVector::new(3, 4);
677        assert_eq!(mv.magnitude_squared(), 25);
678        assert_eq!(mv.l1_norm(), 7);
679        assert_eq!(mv.linf_norm(), 4);
680    }
681
682    #[test]
683    fn test_motion_vector_precision_conversion() {
684        let mv = MotionVector::new(27, 19); // 3 + 3/8, 2 + 3/8
685
686        let qpel = mv.to_precision(MvPrecision::QuarterPel);
687        assert_eq!(qpel.dx & 1, 0); // Should be even
688        assert_eq!(qpel.dy & 1, 0);
689
690        let fpel = mv.to_precision(MvPrecision::FullPel);
691        assert_eq!(fpel.dx & 7, 0); // Should be multiple of 8
692        assert_eq!(fpel.dy & 7, 0);
693    }
694
695    #[test]
696    fn test_search_range() {
697        let range = SearchRange::symmetric(32);
698        assert_eq!(range.horizontal, 32);
699        assert_eq!(range.vertical, 32);
700
701        assert!(range.contains(0, 0));
702        assert!(range.contains(32, 32));
703        assert!(range.contains(-32, -32));
704        assert!(!range.contains(33, 0));
705    }
706
707    #[test]
708    fn test_search_range_positions() {
709        let range = SearchRange::symmetric(2);
710        // (-2..2) x (-2..2) = 5 x 5 = 25 positions
711        assert_eq!(range.num_positions(), 25);
712    }
713
714    #[test]
715    fn test_block_match() {
716        let best = BlockMatch::new(MotionVector::new(8, 16), 100, 120);
717        let worst = BlockMatch::worst();
718
719        assert!(best.is_better_than(&worst));
720        assert!(!worst.is_better_than(&best));
721    }
722
723    #[test]
724    fn test_block_match_update() {
725        let mut current = BlockMatch::worst();
726        let better = BlockMatch::new(MotionVector::new(8, 16), 100, 120);
727
728        current.update_if_better(&better);
729        assert_eq!(current.sad, 100);
730    }
731
732    #[test]
733    fn test_mv_cost() {
734        let cost = MvCost::new(1.0);
735        let mv = MotionVector::new(16, 16);
736
737        let bits = cost.estimate_bits(&mv);
738        assert!(bits > 0.0);
739
740        let rd = cost.rd_cost(&mv, 100);
741        assert!(rd >= 100);
742    }
743
744    #[test]
745    fn test_mv_cost_with_ref() {
746        let ref_mv = MotionVector::new(16, 16);
747        let cost = MvCost::with_ref_mv(1.0, ref_mv);
748
749        // Same MV as reference should have low cost
750        let same_bits = cost.estimate_bits(&ref_mv);
751
752        // Different MV should have higher cost
753        let diff_mv = MotionVector::new(32, 32);
754        let diff_bits = cost.estimate_bits(&diff_mv);
755
756        assert!(same_bits < diff_bits);
757    }
758
759    #[test]
760    fn test_block_size() {
761        assert_eq!(BlockSize::Block8x8.width(), 8);
762        assert_eq!(BlockSize::Block8x8.height(), 8);
763        assert_eq!(BlockSize::Block8x8.num_pixels(), 64);
764        assert!(BlockSize::Block8x8.is_square());
765
766        assert_eq!(BlockSize::Block16x8.width(), 16);
767        assert_eq!(BlockSize::Block16x8.height(), 8);
768        assert!(!BlockSize::Block16x8.is_square());
769    }
770
771    #[test]
772    fn test_block_size_log2() {
773        assert_eq!(BlockSize::Block4x4.width_log2(), 2);
774        assert_eq!(BlockSize::Block8x8.width_log2(), 3);
775        assert_eq!(BlockSize::Block16x16.width_log2(), 4);
776        assert_eq!(BlockSize::Block64x64.width_log2(), 6);
777    }
778}