1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! Bit-depth abstraction for the pixel/recon path.
//!
//! Replaces the C bitdepth templating pattern where `_tmpl.c` files are
//! compiled multiple times with different `BITDEPTH` defines. The 8-bit path
//! uses `u8` samples and a compile-time `bitdepth_max` of 255; the high-bit
//! depth path uses `u16` samples with a *runtime* `bitdepth_max` of
//! `(1 << bd) - 1` (1023 for 10-bit, 4095 for 12-bit), exactly mirroring
//! dav2d's `bitdepth.h`.
/// A storage type for one pixel sample (`u8` for 8bpc, `u16` for 10/12bpc).
pub trait Pixel: Copy + Default + Send + Sync + Into<i32> + 'static {
/// Compile-time storage width in bits (8 for `u8`, 16 for `u16`). This is
/// NOT the coded bit depth — for HBD the coded depth (10/12) is carried at
/// runtime by [`BitDepth::bitdepth_max`].
const BITDEPTH: u8;
/// Largest value representable in the storage type.
const MAX: Self;
fn from_i32(v: i32) -> Self;
fn as_u16(self) -> u16;
}
impl Pixel for u8 {
const BITDEPTH: u8 = 8;
const MAX: u8 = 0xFF;
#[inline(always)]
fn from_i32(v: i32) -> Self {
v as u8
}
#[inline(always)]
fn as_u16(self) -> u16 {
self as u16
}
}
impl Pixel for u16 {
const BITDEPTH: u8 = 16;
const MAX: u16 = 0xFFFF;
#[inline(always)]
fn from_i32(v: i32) -> Self {
v as u16
}
#[inline(always)]
fn as_u16(self) -> u16 {
self
}
}
/// Bit-depth dispatch trait, mirroring dav2d's `include/common/bitdepth.h`.
///
/// `BitDepth8` is a zero-sized type (the coded depth is always 8). `BitDepth16`
/// carries the coded bit depth at runtime (`bitdepth_max = (1 << bd) - 1`),
/// because a single `u16` storage type backs both 10- and 12-bit streams.
///
/// DSP kernels and the recon path are written once against this trait and
/// instantiated for both `BitDepth8` and `BitDepth16`; the `u8` instantiation
/// is byte-identical to the prior hard-coded `_8bpc` code.
pub trait BitDepth: Clone + Copy + Send + Sync + 'static {
/// Sample storage type (`u8` or `u16`).
type Pixel: Pixel;
/// Intermediate/coefficient signed type wide enough for this depth.
/// (`i16` for 8bpc, `i32` for HBD — matches dav2d's `coef`/`itxfm` ranges.)
type Coef: Copy;
/// Compile-time storage width in bits (8 or 16).
const BPC: u8;
/// Construct from the coded bit depth (8, 10 or 12). For `BitDepth8` the
/// argument is ignored.
fn new(bitdepth: u8) -> Self;
/// Coded bit depth (8/10/12).
fn bitdepth(&self) -> u8;
/// `(1 << bitdepth) - 1` — the clip ceiling for reconstructed pixels.
fn bitdepth_max(&self) -> i32;
/// `bitdepth - 8` — the extra-precision shift used throughout dav2d's HBD
/// kernels (deblock thresholds, cdef clips, intermediate rounding).
#[inline(always)]
fn bitdepth_min_8(&self) -> i32 {
self.bitdepth() as i32 - 8
}
/// Clip a reconstructed sample into `[0, bitdepth_max]`.
#[inline(always)]
fn pixel_clip(&self, v: i32) -> Self::Pixel {
let max = self.bitdepth_max();
Self::Pixel::from_i32(v.clamp(0, max))
}
}
/// 8-bit reconstruction (`u8` samples, fixed `bitdepth_max = 255`).
#[derive(Clone, Copy, Default)]
pub struct BitDepth8;
impl BitDepth for BitDepth8 {
type Pixel = u8;
type Coef = i16;
const BPC: u8 = 8;
#[inline(always)]
fn new(_bitdepth: u8) -> Self {
BitDepth8
}
#[inline(always)]
fn bitdepth(&self) -> u8 {
8
}
#[inline(always)]
fn bitdepth_max(&self) -> i32 {
255
}
}
/// High-bit-depth reconstruction (`u16` samples, runtime 10/12-bit `bitdepth`).
#[derive(Clone, Copy)]
pub struct BitDepth16 {
bitdepth: u8,
}
impl BitDepth for BitDepth16 {
type Pixel = u16;
type Coef = i32;
const BPC: u8 = 16;
#[inline(always)]
fn new(bitdepth: u8) -> Self {
debug_assert!(bitdepth == 10 || bitdepth == 12);
BitDepth16 { bitdepth }
}
#[inline(always)]
fn bitdepth(&self) -> u8 {
self.bitdepth
}
#[inline(always)]
fn bitdepth_max(&self) -> i32 {
(1 << self.bitdepth) - 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bitdepth8_constants() {
let bd = BitDepth8::new(8);
assert_eq!(bd.bitdepth(), 8);
assert_eq!(bd.bitdepth_max(), 255);
assert_eq!(bd.bitdepth_min_8(), 0);
assert_eq!(bd.pixel_clip(-5), 0u8);
assert_eq!(bd.pixel_clip(300), 255u8);
assert_eq!(bd.pixel_clip(128), 128u8);
}
#[test]
fn bitdepth16_10bit() {
let bd = BitDepth16::new(10);
assert_eq!(bd.bitdepth(), 10);
assert_eq!(bd.bitdepth_max(), 1023);
assert_eq!(bd.bitdepth_min_8(), 2);
assert_eq!(bd.pixel_clip(-5), 0u16);
assert_eq!(bd.pixel_clip(2000), 1023u16);
assert_eq!(bd.pixel_clip(512), 512u16);
}
#[test]
fn bitdepth16_12bit() {
let bd = BitDepth16::new(12);
assert_eq!(bd.bitdepth_max(), 4095);
assert_eq!(bd.bitdepth_min_8(), 4);
assert_eq!(bd.pixel_clip(9000), 4095u16);
}
#[test]
fn pixel_trait_roundtrip() {
assert_eq!(<u8 as Pixel>::from_i32(200), 200u8);
assert_eq!(<u16 as Pixel>::from_i32(1000), 1000u16);
assert_eq!(255u8.as_u16(), 255u16);
assert_eq!(1023u16.as_u16(), 1023u16);
}
}