1#![allow(clippy::upper_case_acronyms)]
8#![allow(dead_code)]
9
10use super::VideoFrame;
11use crate::graphics::PixelFormat;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub(crate) enum ScaleMode {
20 NearestNeighbor,
22 Bilinear,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub(crate) enum ColorSpace {
33 SRGB,
34 LinearRGB,
35 YUV420,
36 YUV422,
37}
38
39pub(crate) fn scale_frame(
48 src: &VideoFrame,
49 dst_width: u32,
50 dst_height: u32,
51 mode: ScaleMode,
52) -> VideoFrame {
53 if dst_width == 0 || dst_height == 0 || src.width == 0 || src.height == 0 {
54 return VideoFrame::new(dst_width, dst_height, src.format);
55 }
56
57 let mut dst = VideoFrame::new(dst_width, dst_height, src.format);
58
59 match mode {
60 ScaleMode::NearestNeighbor => {
61 for dy in 0..dst_height {
62 let sy = (dy as u64 * src.height as u64 / dst_height as u64) as u32;
63 let sy = sy.min(src.height - 1);
64 for dx in 0..dst_width {
65 let sx = (dx as u64 * src.width as u64 / dst_width as u64) as u32;
66 let sx = sx.min(src.width - 1);
67 let (r, g, b, a) = src.get_pixel(sx, sy);
68 dst.set_pixel(dx, dy, r, g, b, a);
69 }
70 }
71 }
72 ScaleMode::Bilinear => {
73 let scale_x_fp = (src.width as u64 * 256) / dst_width as u64;
76 let scale_y_fp = (src.height as u64 * 256) / dst_height as u64;
77
78 for dy in 0..dst_height {
79 let src_y_fp = (dy as u64 * scale_y_fp) as u32;
80 let sy0 = (src_y_fp >> 8).min(src.height - 1);
81 let sy1 = (sy0 + 1).min(src.height - 1);
82 let fy = src_y_fp & 0xFF; for dx in 0..dst_width {
85 let src_x_fp = (dx as u64 * scale_x_fp) as u32;
86 let sx0 = (src_x_fp >> 8).min(src.width - 1);
87 let sx1 = (sx0 + 1).min(src.width - 1);
88 let fx = src_x_fp & 0xFF;
89
90 let (r00, g00, b00, a00) = src.get_pixel(sx0, sy0);
92 let (r10, g10, b10, a10) = src.get_pixel(sx1, sy0);
93 let (r01, g01, b01, a01) = src.get_pixel(sx0, sy1);
94 let (r11, g11, b11, a11) = src.get_pixel(sx1, sy1);
95
96 let inv_fx = 256 - fx;
98 let inv_fy = 256 - fy;
99
100 let w00 = inv_fx * inv_fy; let w10 = fx * inv_fy;
102 let w01 = inv_fx * fy;
103 let w11 = fx * fy;
104
105 let r = ((r00 as u32 * w00
106 + r10 as u32 * w10
107 + r01 as u32 * w01
108 + r11 as u32 * w11)
109 >> 16) as u8;
110 let g = ((g00 as u32 * w00
111 + g10 as u32 * w10
112 + g01 as u32 * w01
113 + g11 as u32 * w11)
114 >> 16) as u8;
115 let b = ((b00 as u32 * w00
116 + b10 as u32 * w10
117 + b01 as u32 * w01
118 + b11 as u32 * w11)
119 >> 16) as u8;
120 let a = ((a00 as u32 * w00
121 + a10 as u32 * w10
122 + a01 as u32 * w01
123 + a11 as u32 * w11)
124 >> 16) as u8;
125
126 dst.set_pixel(dx, dy, r, g, b, a);
127 }
128 }
129 }
130 }
131
132 dst
133}
134
135pub(crate) fn convert_pixel_format(src: &VideoFrame, dst_format: PixelFormat) -> VideoFrame {
144 let mut dst = VideoFrame::new(src.width, src.height, dst_format);
145 for y in 0..src.height {
146 for x in 0..src.width {
147 let (r, g, b, a) = src.get_pixel(x, y);
148 dst.set_pixel(x, y, r, g, b, a);
149 }
150 }
151 dst
152}
153
154pub(crate) unsafe fn blit_to_framebuffer(
169 frame: &VideoFrame,
170 fb_addr: usize,
171 fb_width: u32,
172 fb_height: u32,
173 fb_stride: u32,
174 x: u32,
175 y: u32,
176) {
177 if frame.width == 0 || frame.height == 0 {
178 return;
179 }
180
181 let fb_ptr = fb_addr as *mut u8;
182
183 let clip_x_start = x;
185 let clip_y_start = y;
186 let clip_x_end = (x + frame.width).min(fb_width);
187 let clip_y_end = (y + frame.height).min(fb_height);
188
189 if clip_x_start >= clip_x_end || clip_y_start >= clip_y_end {
190 return;
191 }
192
193 for fy in 0..(clip_y_end - clip_y_start) {
194 let dst_y = clip_y_start + fy;
195 let dst_row_offset = (dst_y as usize) * (fb_stride as usize);
196
197 for fx in 0..(clip_x_end - clip_x_start) {
198 let (r, g, b, a) = frame.get_pixel(fx, fy);
199 let dst_x = clip_x_start + fx;
200 let dst_off = dst_row_offset + (dst_x as usize) * 4;
202
203 if a == 0xFF {
204 let dst = fb_ptr.add(dst_off);
207 *dst = b;
208 *dst.add(1) = g;
209 *dst.add(2) = r;
210 *dst.add(3) = 0xFF;
211 } else if a > 0 {
212 let dst = fb_ptr.add(dst_off);
215 let bg_b = *dst;
216 let bg_g = *dst.add(1);
217 let bg_r = *dst.add(2);
218 let (br, bg, bb) = alpha_blend(r, g, b, a, bg_r, bg_g, bg_b);
219 *dst = bb;
220 *dst.add(1) = bg;
221 *dst.add(2) = br;
222 *dst.add(3) = 0xFF;
223 }
224 }
226 }
227}
228
229pub(crate) fn blit_to_buffer(
233 frame: &VideoFrame,
234 buffer: &mut [u32],
235 buf_width: u32,
236 buf_height: u32,
237 x: u32,
238 y: u32,
239) {
240 if frame.width == 0 || frame.height == 0 {
241 return;
242 }
243
244 let clip_x_end = (x + frame.width).min(buf_width);
245 let clip_y_end = (y + frame.height).min(buf_height);
246
247 if x >= clip_x_end || y >= clip_y_end {
248 return;
249 }
250
251 for fy in 0..(clip_y_end - y) {
252 let dst_y = y + fy;
253 for fx in 0..(clip_x_end - x) {
254 let dst_x = x + fx;
255 let idx = (dst_y as usize) * (buf_width as usize) + (dst_x as usize);
256 if idx >= buffer.len() {
257 continue;
258 }
259
260 let (r, g, b, a) = frame.get_pixel(fx, fy);
261 if a == 0xFF {
262 buffer[idx] = 0xFF00_0000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
263 } else if a > 0 {
264 let existing = buffer[idx];
265 let bg_r = ((existing >> 16) & 0xFF) as u8;
266 let bg_g = ((existing >> 8) & 0xFF) as u8;
267 let bg_b = (existing & 0xFF) as u8;
268 let (br, bg, bb) = alpha_blend(r, g, b, a, bg_r, bg_g, bg_b);
269 buffer[idx] = 0xFF00_0000 | ((br as u32) << 16) | ((bg as u32) << 8) | (bb as u32);
270 }
271 }
272 }
273}
274
275pub(crate) fn yuv_to_rgb(y: u8, u: u8, v: u8) -> (u8, u8, u8) {
288 let y = y as i32;
289 let cb = u as i32 - 128;
290 let cr = v as i32 - 128;
291
292 let r = y + ((359 * cr) >> 8);
293 let g = y - ((88 * cb + 183 * cr) >> 8);
294 let b = y + ((454 * cb) >> 8);
295
296 (clamp_u8(r), clamp_u8(g), clamp_u8(b))
297}
298
299pub(crate) fn rgb_to_yuv(r: u8, g: u8, b: u8) -> (u8, u8, u8) {
307 let ri = r as i32;
308 let gi = g as i32;
309 let bi = b as i32;
310
311 let y = (77 * ri + 150 * gi + 29 * bi) >> 8;
312 let u = ((-43 * ri - 85 * gi + 128 * bi) >> 8) + 128;
313 let v = ((128 * ri - 107 * gi - 21 * bi) >> 8) + 128;
314
315 (clamp_u8(y), clamp_u8(u), clamp_u8(v))
316}
317
318pub(crate) fn alpha_blend(
327 src_r: u8,
328 src_g: u8,
329 src_b: u8,
330 src_a: u8,
331 dst_r: u8,
332 dst_g: u8,
333 dst_b: u8,
334) -> (u8, u8, u8) {
335 let a = src_a as u16;
336 let inv_a = 255 - a;
337
338 let r = ((src_r as u16 * a + dst_r as u16 * inv_a) / 255) as u8;
339 let g = ((src_g as u16 * a + dst_g as u16 * inv_a) / 255) as u8;
340 let b = ((src_b as u16 * a + dst_b as u16 * inv_a) / 255) as u8;
341
342 (r, g, b)
343}
344
345#[inline]
351fn clamp_u8(val: i32) -> u8 {
352 if val < 0 {
353 0
354 } else if val > 255 {
355 255
356 } else {
357 val as u8
358 }
359}
360
361#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_nearest_neighbor_identity() {
371 let mut src = VideoFrame::new(4, 4, PixelFormat::Argb8888);
372 src.set_pixel(0, 0, 255, 0, 0, 255);
373 src.set_pixel(3, 3, 0, 255, 0, 255);
374
375 let dst = scale_frame(&src, 4, 4, ScaleMode::NearestNeighbor);
376 assert_eq!(dst.get_pixel(0, 0), (255, 0, 0, 255));
377 assert_eq!(dst.get_pixel(3, 3), (0, 255, 0, 255));
378 }
379
380 #[test]
381 fn test_nearest_neighbor_upscale() {
382 let mut src = VideoFrame::new(2, 2, PixelFormat::Argb8888);
383 src.set_pixel(0, 0, 100, 200, 50, 255);
384 src.set_pixel(1, 1, 10, 20, 30, 255);
385
386 let dst = scale_frame(&src, 4, 4, ScaleMode::NearestNeighbor);
387 assert_eq!(dst.get_pixel(0, 0), (100, 200, 50, 255));
389 assert_eq!(dst.get_pixel(3, 3), (10, 20, 30, 255));
391 }
392
393 #[test]
394 fn test_bilinear_identity() {
395 let mut src = VideoFrame::new(4, 4, PixelFormat::Argb8888);
396 src.set_pixel(0, 0, 255, 0, 0, 255);
397
398 let dst = scale_frame(&src, 4, 4, ScaleMode::Bilinear);
399 let (r, _g, _b, _a) = dst.get_pixel(0, 0);
400 assert!(r > 240);
402 }
403
404 #[test]
405 fn test_convert_xrgb_to_rgb888() {
406 let mut src = VideoFrame::new(2, 2, PixelFormat::Xrgb8888);
407 src.set_pixel(0, 0, 0xAA, 0xBB, 0xCC, 0xFF);
408
409 let dst = convert_pixel_format(&src, PixelFormat::Rgb888);
410 assert_eq!(dst.format, PixelFormat::Rgb888);
411 let (r, g, b, a) = dst.get_pixel(0, 0);
412 assert_eq!((r, g, b), (0xAA, 0xBB, 0xCC));
413 assert_eq!(a, 0xFF);
414 }
415
416 #[test]
417 fn test_yuv_rgb_roundtrip() {
418 let r0: u8 = 180;
420 let g0: u8 = 100;
421 let b0: u8 = 60;
422
423 let (y, u, v) = rgb_to_yuv(r0, g0, b0);
424 let (r1, g1, b1) = yuv_to_rgb(y, u, v);
425
426 assert!((r1 as i16 - r0 as i16).unsigned_abs() <= 2);
428 assert!((g1 as i16 - g0 as i16).unsigned_abs() <= 2);
429 assert!((b1 as i16 - b0 as i16).unsigned_abs() <= 2);
430 }
431
432 #[test]
433 fn test_yuv_black_white() {
434 let (r, g, b) = yuv_to_rgb(0, 128, 128);
436 assert_eq!((r, g, b), (0, 0, 0));
437
438 let (r, g, b) = yuv_to_rgb(255, 128, 128);
440 assert_eq!((r, g, b), (255, 255, 255));
441 }
442
443 #[test]
444 fn test_alpha_blend_opaque() {
445 let (r, g, b) = alpha_blend(100, 200, 50, 255, 0, 0, 0);
446 assert_eq!((r, g, b), (100, 200, 50));
447 }
448
449 #[test]
450 fn test_alpha_blend_transparent() {
451 let (r, g, b) = alpha_blend(100, 200, 50, 0, 10, 20, 30);
452 assert_eq!((r, g, b), (10, 20, 30));
453 }
454
455 #[test]
456 fn test_alpha_blend_half() {
457 let (r, g, b) = alpha_blend(200, 100, 0, 128, 0, 0, 200);
458 assert!(r > 90 && r < 110);
461 assert!(b > 90 && b < 110);
462 }
463}