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