1use oxideav_core::{PixelFormat, Result, VideoFrame};
25use oxideav_pixfmt::ConvertOptions;
26
27use crate::object::Canvas;
28use crate::render::RenderedFrame;
29use crate::source::{SceneSource, SourceFormat};
30
31pub fn adapt_frame_to(frame: VideoFrame, target: PixelFormat) -> Result<VideoFrame> {
33 if frame.format == target {
34 return Ok(frame);
35 }
36 oxideav_pixfmt::convert(&frame, target, &ConvertOptions::default())
37}
38
39pub fn adapt_frame_to_canvas(frame: VideoFrame, canvas: &Canvas) -> Result<VideoFrame> {
42 match canvas {
43 Canvas::Raster { pixel_format, .. } => adapt_frame_to(frame, *pixel_format),
44 Canvas::Vector { .. } => Ok(frame),
45 }
46}
47
48pub struct AdaptedSource<S: SceneSource> {
56 inner: S,
57 target: PixelFormat,
58}
59
60impl<S: SceneSource> AdaptedSource<S> {
61 pub fn new(inner: S, target: PixelFormat) -> Self {
66 AdaptedSource { inner, target }
67 }
68
69 pub fn inner(&self) -> &S {
71 &self.inner
72 }
73
74 pub fn inner_mut(&mut self) -> &mut S {
78 &mut self.inner
79 }
80}
81
82impl<S: SceneSource> SceneSource for AdaptedSource<S> {
83 fn format(&self) -> SourceFormat {
84 let mut f = self.inner.format();
85 if let Canvas::Raster {
88 ref mut pixel_format,
89 ..
90 } = f.canvas
91 {
92 *pixel_format = self.target;
93 }
94 f
95 }
96
97 fn pull(&mut self) -> Result<Option<RenderedFrame>> {
98 let Some(mut frame) = self.inner.pull()? else {
99 return Ok(None);
100 };
101 if let Some(video) = frame.video.take() {
102 frame.video = Some(adapt_frame_to(video, self.target)?);
103 }
104 Ok(Some(frame))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::scene::Scene;
112 use crate::source::SceneSource;
113 use oxideav_core::{Rational, TimeBase, VideoFrame, VideoPlane};
114
115 fn yuv420p_frame(width: u32, height: u32) -> VideoFrame {
116 let y_size = (width * height) as usize;
117 let c_size = ((width / 2) * (height / 2)) as usize;
118 VideoFrame {
119 format: PixelFormat::Yuv420P,
120 width,
121 height,
122 pts: None,
123 time_base: TimeBase::new(1, 30),
124 planes: vec![
125 VideoPlane {
126 stride: width as usize,
127 data: vec![128; y_size],
128 },
129 VideoPlane {
130 stride: (width / 2) as usize,
131 data: vec![128; c_size],
132 },
133 VideoPlane {
134 stride: (width / 2) as usize,
135 data: vec![128; c_size],
136 },
137 ],
138 }
139 }
140
141 #[test]
142 fn adapt_to_same_format_is_identity() {
143 let f = yuv420p_frame(8, 8);
144 let out = adapt_frame_to(f.clone(), PixelFormat::Yuv420P).unwrap();
145 assert_eq!(out.format, PixelFormat::Yuv420P);
146 assert_eq!(out.planes[0].data, f.planes[0].data);
147 }
148
149 #[test]
150 fn adapt_to_canvas_vector_passes_through() {
151 let f = yuv420p_frame(8, 8);
152 let canvas = Canvas::Vector {
153 width: 595.0,
154 height: 842.0,
155 unit: crate::object::LengthUnit::Point,
156 };
157 let out = adapt_frame_to_canvas(f, &canvas).unwrap();
158 assert_eq!(out.format, PixelFormat::Yuv420P);
159 }
160
161 struct StaticSource {
162 fmt: SourceFormat,
163 frames_left: u32,
164 }
165
166 impl SceneSource for StaticSource {
167 fn format(&self) -> SourceFormat {
168 self.fmt.clone()
169 }
170 fn pull(&mut self) -> Result<Option<RenderedFrame>> {
171 if self.frames_left == 0 {
172 return Ok(None);
173 }
174 self.frames_left -= 1;
175 Ok(Some(RenderedFrame {
176 video: Some(yuv420p_frame(8, 8)),
177 audio: Vec::new(),
178 operations: Vec::new(),
179 }))
180 }
181 }
182
183 #[test]
184 fn adapted_source_reports_target_format() {
185 let scene = Scene {
186 framerate: Rational::new(30, 1),
187 ..Scene::default()
188 };
189 let inner = StaticSource {
190 fmt: SourceFormat::from_scene(&scene),
191 frames_left: 1,
192 };
193 let adapted = AdaptedSource::new(inner, PixelFormat::Rgba);
194 match adapted.format().canvas {
195 Canvas::Raster { pixel_format, .. } => assert_eq!(pixel_format, PixelFormat::Rgba),
196 _ => panic!("expected Raster"),
197 }
198 }
199
200 #[test]
201 fn adapted_source_converts_on_pull() {
202 let scene = Scene::default();
206 let inner = StaticSource {
207 fmt: SourceFormat::from_scene(&scene),
208 frames_left: 1,
209 };
210 let mut adapted = AdaptedSource::new(inner, PixelFormat::Rgba);
211 let out = adapted.pull().unwrap().expect("frame");
212 let video = out.video.unwrap();
213 assert_eq!(video.format, PixelFormat::Rgba);
214 assert_eq!(video.width, 8);
215 assert_eq!(video.height, 8);
216 }
217}