oximedia_codec/reconstruct/
mod.rs1#![forbid(unsafe_code)]
31#![allow(clippy::unreadable_literal)]
32#![allow(clippy::items_after_statements)]
33#![allow(clippy::unnecessary_wraps)]
34#![allow(clippy::struct_excessive_bools)]
35#![allow(clippy::identity_op)]
36#![allow(clippy::range_plus_one)]
37#![allow(clippy::needless_range_loop)]
38#![allow(clippy::useless_conversion)]
39#![allow(clippy::redundant_closure_for_method_calls)]
40#![allow(clippy::single_match_else)]
41#![allow(dead_code)]
42#![allow(clippy::doc_markdown)]
43#![allow(clippy::module_name_repetitions)]
44#![allow(clippy::match_same_arms)]
45#![allow(clippy::similar_names)]
46#![allow(clippy::cast_possible_truncation)]
47#![allow(clippy::cast_precision_loss)]
48#![allow(clippy::cast_lossless)]
49#![allow(clippy::cast_sign_loss)]
50
51mod buffer;
52mod cdef_apply;
53mod deblock;
54mod film_grain;
55mod loop_filter;
56mod output;
57mod pipeline;
58mod residual;
59mod super_res;
60
61pub use buffer::{BufferPool, FrameBuffer, PlaneBuffer, ReferenceFrameManager};
63pub use cdef_apply::{CdefApplicator, CdefBlockConfig, CdefFilterResult};
64pub use deblock::{DeblockFilter, DeblockParams, FilterStrength};
65pub use film_grain::{FilmGrainParams, FilmGrainSynthesizer, GrainBlock};
66pub use loop_filter::{EdgeFilter, FilterDirection, LoopFilterPipeline};
67pub use output::{OutputConfig, OutputFormat, OutputFormatter};
68pub use pipeline::{DecoderPipeline, FrameContext, PipelineConfig, PipelineStage, StageResult};
69pub use residual::{ResidualBuffer, ResidualPlane};
70pub use super_res::{SuperResConfig, SuperResUpscaler, UpscaleMethod};
71
72use thiserror::Error;
73
74#[derive(Debug, Error)]
80pub enum ReconstructionError {
81 #[error("Invalid input: {0}")]
83 InvalidInput(String),
84
85 #[error("Buffer allocation failed: {0}")]
87 AllocationFailed(String),
88
89 #[error("Reference frame not available: index {0}")]
91 ReferenceNotAvailable(usize),
92
93 #[error("Pipeline stage '{stage}' failed: {message}")]
95 StageError {
96 stage: String,
98 message: String,
100 },
101
102 #[error("Invalid dimensions: {width}x{height}")]
104 InvalidDimensions {
105 width: u32,
107 height: u32,
109 },
110
111 #[error("Unsupported bit depth: {0}")]
113 UnsupportedBitDepth(u8),
114
115 #[error("Coefficient overflow at ({x}, {y})")]
117 CoefficientOverflow {
118 x: usize,
120 y: usize,
122 },
123
124 #[error("Filter parameter out of range: {name} = {value}")]
126 FilterParameterOutOfRange {
127 name: String,
129 value: i32,
131 },
132
133 #[error("Internal error: {0}")]
135 Internal(String),
136}
137
138pub type ReconstructResult<T> = Result<T, ReconstructionError>;
140
141pub const MAX_BIT_DEPTH: u8 = 12;
147
148pub const MIN_BIT_DEPTH: u8 = 8;
150
151pub const MAX_FRAME_WIDTH: u32 = 16384;
153
154pub const MAX_FRAME_HEIGHT: u32 = 16384;
156
157pub const NUM_REF_FRAMES: usize = 8;
159
160pub const MAX_SB_SIZE: usize = 128;
162
163pub const MIN_SB_SIZE: usize = 64;
165
166#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172pub enum PlaneType {
173 Y,
175 U,
177 V,
179}
180
181impl PlaneType {
182 #[must_use]
184 pub const fn index(self) -> usize {
185 match self {
186 Self::Y => 0,
187 Self::U => 1,
188 Self::V => 2,
189 }
190 }
191
192 #[must_use]
194 pub const fn is_chroma(self) -> bool {
195 !matches!(self, Self::Y)
196 }
197
198 #[must_use]
200 pub const fn all() -> [Self; 3] {
201 [Self::Y, Self::U, Self::V]
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
207pub enum ChromaSubsampling {
208 Cs444,
210 Cs422,
212 #[default]
214 Cs420,
215 Mono,
217}
218
219impl ChromaSubsampling {
220 #[must_use]
222 pub const fn ratios(self) -> (u32, u32) {
223 match self {
224 Self::Cs444 => (1, 1),
225 Self::Cs422 => (2, 1),
226 Self::Cs420 => (2, 2),
227 Self::Mono => (1, 1),
228 }
229 }
230
231 #[must_use]
233 pub const fn num_planes(self) -> usize {
234 match self {
235 Self::Mono => 1,
236 _ => 3,
237 }
238 }
239
240 #[must_use]
242 pub fn chroma_size(self, luma_width: u32, luma_height: u32) -> (u32, u32) {
243 let (h_ratio, v_ratio) = self.ratios();
244 (luma_width.div_ceil(h_ratio), luma_height.div_ceil(v_ratio))
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_plane_type() {
254 assert_eq!(PlaneType::Y.index(), 0);
255 assert_eq!(PlaneType::U.index(), 1);
256 assert_eq!(PlaneType::V.index(), 2);
257
258 assert!(!PlaneType::Y.is_chroma());
259 assert!(PlaneType::U.is_chroma());
260 assert!(PlaneType::V.is_chroma());
261 }
262
263 #[test]
264 fn test_chroma_subsampling() {
265 assert_eq!(ChromaSubsampling::Cs444.ratios(), (1, 1));
266 assert_eq!(ChromaSubsampling::Cs422.ratios(), (2, 1));
267 assert_eq!(ChromaSubsampling::Cs420.ratios(), (2, 2));
268
269 assert_eq!(ChromaSubsampling::Cs420.num_planes(), 3);
270 assert_eq!(ChromaSubsampling::Mono.num_planes(), 1);
271 }
272
273 #[test]
274 fn test_chroma_size_calculation() {
275 let cs = ChromaSubsampling::Cs420;
276 assert_eq!(cs.chroma_size(1920, 1080), (960, 540));
277 assert_eq!(cs.chroma_size(1921, 1081), (961, 541));
278 }
279
280 #[test]
281 fn test_reconstruction_error_display() {
282 let err = ReconstructionError::InvalidInput("test".to_string());
283 assert_eq!(format!("{err}"), "Invalid input: test");
284
285 let err = ReconstructionError::ReferenceNotAvailable(3);
286 assert_eq!(format!("{err}"), "Reference frame not available: index 3");
287
288 let err = ReconstructionError::InvalidDimensions {
289 width: 0,
290 height: 100,
291 };
292 assert_eq!(format!("{err}"), "Invalid dimensions: 0x100");
293 }
294
295 #[test]
296 fn test_constants() {
297 assert_eq!(MAX_BIT_DEPTH, 12);
298 assert_eq!(MIN_BIT_DEPTH, 8);
299 assert_eq!(NUM_REF_FRAMES, 8);
300 assert_eq!(MAX_SB_SIZE, 128);
301 assert_eq!(MIN_SB_SIZE, 64);
302 }
303}