From aae601f46ba69aa861ea9d24ad03fca1adced219 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 28 Oct 2026 02:05:37 +0000 Subject: [PATCH] Refactor media for strict Clippy checks --- crates/media/src/digits.rs | 31 ++++++++++++---- crates/media/src/dpx.rs | 64 ++++++++++++++++++++-------------- crates/media/src/generators.rs | 4 +-- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/crates/media/src/digits.rs b/crates/media/src/digits.rs index a853b243b..41fe00b26 100644 --- a/crates/media/src/digits.rs +++ b/crates/media/src/digits.rs @@ -3,8 +3,24 @@ /// Segment masks for 0-9, then ':' (10), '-' (11), ';' (12). const SEGS: [u8; 10] = [0b0111111, 0b0000110, 0b1011011, 0b1001111, 0b1100110, 0b1101101, 0b1111101, 0b0000111, 0b1111111, 0b1101111]; +/// A mutable RGBA8 pixel buffer with its dimensions in pixels. +pub struct Canvas<'a> { + pub buf: &'a mut [u8], + pub width: usize, + pub height: usize, +} + +/// A rectangle in pixel coordinates (may extend outside the canvas; it is clipped when drawn). +#[derive(Clone, Copy)] +pub struct Rect { + pub x: i32, + pub y: i32, + pub w: i32, + pub h: i32, +} + /// Draw `text` (digits, ':' ';' '-' and ' ') into an RGBA8 buffer at (x, y) with digit height `h`. -pub fn draw_text(buf: &mut [u8], width: usize, height: usize, x: i32, y: i32, h: i32, text: &str, color: [u8; 4]) { +pub fn draw_text(canvas: &mut Canvas, x: i32, y: i32, h: i32, text: &str, color: [u8; 4]) { let w = h / 2; let t = (h / 10).max(1); let mut cx = x; @@ -24,19 +40,19 @@ pub fn draw_text(buf: &mut [u8], width: usize, height: usize, x: i32, y: i32, h: ]; for (i, r) in segs.iter().enumerate() { if m & (1 << i) != 0 { - fill_rect(buf, width, height, r.0, r.1, r.2, r.3, color); + fill_rect(canvas, Rect { x: r.0, y: r.1, w: r.2, h: r.3 }, color); } } cx += w + t * 2; } ':' | ';' => { - fill_rect(buf, width, height, cx, y + h / 4, t, t, color); + fill_rect(canvas, Rect { x: cx, y: y + h / 4, w: t, h: t }, color); let y2 = y + h * 3 / 4 - t; - fill_rect(buf, width, height, cx, y2, t, if ch == ';' { t * 2 } else { t }, color); + fill_rect(canvas, Rect { x: cx, y: y2, w: t, h: if ch == ';' { t * 2 } else { t } }, color); cx += t * 3; } '-' => { - fill_rect(buf, width, height, cx, y + h / 2 - t / 2, w, t, color); + fill_rect(canvas, Rect { x: cx, y: y + h / 2 - t / 2, w, h: t }, color); cx += w + t * 2; } _ => cx += w + t * 2, @@ -51,7 +67,10 @@ pub fn text_width(h: i32, text: &str) -> i32 { text.chars().map(|c| if c == ':' || c == ';' { t * 3 } else { w + t * 2 }).sum() } -pub fn fill_rect(buf: &mut [u8], width: usize, height: usize, x: i32, y: i32, w: i32, h: i32, c: [u8; 4]) { +pub fn fill_rect(canvas: &mut Canvas, rect: Rect, c: [u8; 4]) { + let Rect { x, y, w, h } = rect; + let Canvas { buf, width, height } = canvas; + let (width, height) = (*width, *height); let x0 = x.max(0) as usize; let y0 = y.max(0) as usize; let x1 = ((x + w).max(0) as usize).min(width); diff --git a/crates/media/src/dpx.rs b/crates/media/src/dpx.rs index 474d1236e..31d208ac8 100644 --- a/crates/media/src/dpx.rs +++ b/crates/media/src/dpx.rs @@ -271,7 +271,15 @@ mod tests { use super::*; /// A minimal DPX file (header + `data`) in the given byte order. - fn file(big: bool, w: u32, h: u32, descriptor: u8, bits: u8, packing: u16, orientation: u16, data: &[u8]) -> Vec { + /// Image element fields of a test DPX header. + struct Element { + descriptor: u8, + bits: u8, + packing: u16, + } + + fn file(big: bool, w: u32, h: u32, element: Element, orientation: u16, data: &[u8]) -> Vec { + let Element { descriptor, bits, packing } = element; let mut f = vec![0u8; 2048]; let p32 = |f: &mut Vec, at: usize, v: u32| f[at..at + 4].copy_from_slice(&if big { v.to_be_bytes() } else { v.to_le_bytes() }); let p16 = |f: &mut Vec, at: usize, v: u16| f[at..at + 2].copy_from_slice(&if big { v.to_be_bytes() } else { v.to_le_bytes() }); @@ -296,13 +304,13 @@ mod tests { let mut data = Vec::new(); data.extend_from_slice(&word(1023, 0, 512, 2)); data.extend_from_slice(&word(4, 1019, 0, 2)); - let r = decode("a.dpx", &file(true, 2, 1, 50, 10, 1, 0, &data)).unwrap(); + let r = decode("a.dpx", &file(true, 2, 1, Element { descriptor: 50, bits: 10, packing: 1 }, 0, &data)).unwrap(); assert_eq!(r.rgba, [255, 0, 128, 255, 1, 254, 0, 255]); assert!(!r.has_alpha); let mut data = Vec::new(); data.extend_from_slice(&word(1023, 0, 512, 0)); data.extend_from_slice(&word(4, 1019, 0, 0)); - let r = decode("a.dpx", &file(true, 2, 1, 50, 10, 2, 0, &data)).unwrap(); + let r = decode("a.dpx", &file(true, 2, 1, Element { descriptor: 50, bits: 10, packing: 2 }, 0, &data)).unwrap(); assert_eq!(r.rgba, [255, 0, 128, 255, 1, 254, 0, 255]); } @@ -315,7 +323,7 @@ mod tests { let get = |i: usize| c.get(i).copied().unwrap_or(0); data.extend_from_slice(&((get(0) << 22) | (get(1) << 12) | (get(2) << 2)).to_le_bytes()); } - let r = decode("a.dpx", &file(false, 2, 1, 51, 10, 1, 0, &data)).unwrap(); + let r = decode("a.dpx", &file(false, 2, 1, Element { descriptor: 51, bits: 10, packing: 1 }, 0, &data)).unwrap(); assert_eq!(r.rgba, [255, 0, 0, 255, 0, 255, 0, 0]); assert!(r.has_alpha); } @@ -323,12 +331,13 @@ mod tests { #[test] fn eight_bit_lines_pad_to_words_and_orientation_flips() { // 1 × 2 RGB: each 3-byte line padded to 4; bottom-to-top, right-to-left orientation 3 - let r = decode("a.dpx", &file(false, 1, 2, 50, 8, 0, 3, &[1, 2, 3, 0, 4, 5, 6, 0])).unwrap(); + let r = decode("a.dpx", &file(false, 1, 2, Element { descriptor: 50, bits: 8, packing: 0 }, 3, &[1, 2, 3, 0, 4, 5, 6, 0])).unwrap(); assert_eq!(r.rgba, [4, 5, 6, 255, 1, 2, 3, 255]); // 2 × 1 luma, 16-bit big-endian; ABGR, 12-bit method A - let r = decode("a.dpx", &file(true, 2, 1, 6, 16, 0, 0, &[0xFF, 0xFF, 0x80, 0x00])).unwrap(); + let r = decode("a.dpx", &file(true, 2, 1, Element { descriptor: 6, bits: 16, packing: 0 }, 0, &[0xFF, 0xFF, 0x80, 0x00])).unwrap(); assert_eq!(r.rgba, [255, 255, 255, 255, 128, 128, 128, 255]); - let r = decode("a.dpx", &file(true, 1, 1, 52, 12, 1, 0, &[0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0])).unwrap(); + let r = + decode("a.dpx", &file(true, 1, 1, Element { descriptor: 52, bits: 12, packing: 1 }, 0, &[0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0])).unwrap(); assert_eq!(r.rgba, [255, 0, 0, 128]); } @@ -360,12 +369,12 @@ mod tests { // 2 × 2 RGB 10-bit: 6 components (60 bits) per line, the fourth straddles two words let lines: [&[u32]; 2] = [&[1023, 0, 512, 4, 1019, 1023], &[0, 1023, 0, 1023, 1023, 1023]]; for big in [true, false] { - let r = decode("a.dpx", &file(big, 2, 2, 50, 10, 0, 0, &packed(&lines, 10, big))).unwrap(); + let r = decode("a.dpx", &file(big, 2, 2, Element { descriptor: 50, bits: 10, packing: 0 }, 0, &packed(&lines, 10, big))).unwrap(); assert_eq!(r.rgba, [255, 0, 128, 255, 1, 254, 255, 255, 0, 255, 0, 255, 255, 255, 255, 255]); } // 3 × 1 RGB 12-bit: 9 components, 108 bits in 4 words let line: [&[u32]; 1] = [&[4095, 0, 2048, 16, 4079, 0, 0, 0, 4095]]; - let r = decode("a.dpx", &file(true, 3, 1, 50, 12, 0, 0, &packed(&line, 12, true))).unwrap(); + let r = decode("a.dpx", &file(true, 3, 1, Element { descriptor: 50, bits: 12, packing: 0 }, 0, &packed(&line, 12, true))).unwrap(); assert_eq!(r.rgba, [255, 0, 128, 255, 1, 254, 0, 255, 0, 0, 255, 255]); } @@ -373,29 +382,30 @@ mod tests { fn ycbcr_descriptors_convert_to_rgb() { // 8-bit CbYCrY, video range, BT.709 (colorimetric 6): black, white, then a red pixel pair // (the 709 code triple of red is rounded, hence green 1) - let mut f = file(true, 4, 1, 100, 8, 0, 0, &[128, 16, 128, 235, 102, 63, 240, 63]); + let mut f = file(true, 4, 1, Element { descriptor: 100, bits: 8, packing: 0 }, 0, &[128, 16, 128, 235, 102, 63, 240, 63]); f[ELEMENT + 22] = 6; let r = decode("a.dpx", &f).unwrap(); assert_eq!(r.rgba, [0, 0, 0, 255, 255, 255, 255, 255, 255, 1, 0, 255, 255, 1, 0, 255]); assert!(!r.has_alpha); // red in BT.601 (colorimetric 7): the 601 code triple, rounded - let mut f = file(true, 2, 1, 100, 8, 0, 0, &[90, 81, 240, 81]); + let mut f = file(true, 2, 1, Element { descriptor: 100, bits: 8, packing: 0 }, 0, &[90, 81, 240, 81]); f[ELEMENT + 22] = 7; assert_eq!(decode("a.dpx", &f).unwrap().rgba, [254, 0, 0, 255, 254, 0, 0, 255]); // 10-bit CbYCr filled (method A), full range from the reference codes: mid grey let word = |a: u32, b: u32, c: u32| ((a << 22) | (b << 12) | (c << 2)).to_be_bytes(); - let mut f = file(true, 1, 1, 102, 10, 1, 0, &word(512, 512, 512)); + let mut f = file(true, 1, 1, Element { descriptor: 102, bits: 10, packing: 1 }, 0, &word(512, 512, 512)); f[ELEMENT + 4..ELEMENT + 8].copy_from_slice(&0u32.to_be_bytes()); f[ELEMENT + 12..ELEMENT + 16].copy_from_slice(&1023u32.to_be_bytes()); assert_eq!(decode("a.dpx", &f).unwrap().rgba, [128, 128, 128, 255]); // 16-bit CbYCrA: white at half opacity let v = |x: u16| x.to_be_bytes(); let data: Vec = [v(32768), v(60160), v(32768), v(32768)].concat(); - let r = decode("a.dpx", &file(true, 1, 1, 103, 16, 0, 0, &data)).unwrap(); + let r = decode("a.dpx", &file(true, 1, 1, Element { descriptor: 103, bits: 16, packing: 0 }, 0, &data)).unwrap(); assert_eq!(r.rgba, [255, 255, 255, 128]); assert!(r.has_alpha); // 8-bit CbYACrYA, 3 pixels: the last pixel has no Cr and is neutral - let r = decode("a.dpx", &file(true, 3, 1, 101, 8, 0, 0, &[128, 235, 255, 128, 16, 0, 128, 235, 255, 0, 0, 0])).unwrap(); + let r = decode("a.dpx", &file(true, 3, 1, Element { descriptor: 101, bits: 8, packing: 0 }, 0, &[128, 235, 255, 128, 16, 0, 128, 235, 255, 0, 0, 0])) + .unwrap(); assert_eq!(r.rgba, [255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255]); assert!(r.has_alpha); } @@ -403,23 +413,23 @@ mod tests { #[test] fn hostile_headers_fail_cleanly() { // huge dimensions with no data, truncated data, unsupported layouts - assert!(decode("a.dpx", &file(true, u32::MAX, u32::MAX, 50, 10, 1, 0, &[])).is_err()); - assert!(decode("a.dpx", &file(true, 4096, 4096, 50, 10, 1, 0, &[0; 64])).is_err()); - assert!(decode("a.dpx", &file(true, 2, 2, 50, 10, 1, 0, &[0; 12])).is_err()); - assert!(decode("a.dpx", &file(true, 1, 1, 150, 8, 0, 0, &[0; 4])).is_err()); - assert!(decode("a.dpx", &file(true, 1, 1, 50, 10, 3, 0, &[0; 4])).is_err()); - assert!(decode("a.dpx", &file(true, 1, 1, 50, 16, 0, 0, &[0; 4])).is_err()); - assert!(decode("a.dpx", &file(true, 3, 3, 100, 12, 0, 0, &[0; 20])).is_err()); - assert!(decode("a.dpx", &file(true, 1, 1, 50, 32, 0, 0, &[0; 16])).is_err()); - assert!(decode("a.dpx", &file(true, 1, 1, 50, 8, 0, 5, &[0; 4])).is_err()); - assert!(decode("a.dpx", &file(true, 0, 1, 50, 8, 0, 0, &[0; 4])).is_err()); + assert!(decode("a.dpx", &file(true, u32::MAX, u32::MAX, Element { descriptor: 50, bits: 10, packing: 1 }, 0, &[])).is_err()); + assert!(decode("a.dpx", &file(true, 4096, 4096, Element { descriptor: 50, bits: 10, packing: 1 }, 0, &[0; 64])).is_err()); + assert!(decode("a.dpx", &file(true, 2, 2, Element { descriptor: 50, bits: 10, packing: 1 }, 0, &[0; 12])).is_err()); + assert!(decode("a.dpx", &file(true, 1, 1, Element { descriptor: 150, bits: 8, packing: 0 }, 0, &[0; 4])).is_err()); + assert!(decode("a.dpx", &file(true, 1, 1, Element { descriptor: 50, bits: 10, packing: 3 }, 0, &[0; 4])).is_err()); + assert!(decode("a.dpx", &file(true, 1, 1, Element { descriptor: 50, bits: 16, packing: 0 }, 0, &[0; 4])).is_err()); + assert!(decode("a.dpx", &file(true, 3, 3, Element { descriptor: 100, bits: 12, packing: 0 }, 0, &[0; 20])).is_err()); + assert!(decode("a.dpx", &file(true, 1, 1, Element { descriptor: 50, bits: 32, packing: 0 }, 0, &[0; 16])).is_err()); + assert!(decode("a.dpx", &file(true, 1, 1, Element { descriptor: 50, bits: 8, packing: 0 }, 5, &[0; 4])).is_err()); + assert!(decode("a.dpx", &file(true, 0, 1, Element { descriptor: 50, bits: 8, packing: 0 }, 0, &[0; 4])).is_err()); assert!(decode("a.dpx", b"SDPX").is_err()); - let mut f = file(true, 1, 1, 50, 8, 0, 0, &[0; 4]); + let mut f = file(true, 1, 1, Element { descriptor: 50, bits: 8, packing: 0 }, 0, &[0; 4]); f[4..8].copy_from_slice(&u32::MAX.to_be_bytes()); assert!(decode("a.dpx", &f).is_err()); - let mut f = file(true, 1, 1, 50, 8, 0, 0, &[0; 4]); + let mut f = file(true, 1, 1, Element { descriptor: 50, bits: 8, packing: 0 }, 0, &[0; 4]); f[ELEMENT + 32..ELEMENT + 36].copy_from_slice(&(u32::MAX - 1).to_be_bytes()); - assert!(decode("a.dpx", &file(true, 1, 2, 50, 8, 0, 0, &[0; 4])).is_err()); + assert!(decode("a.dpx", &file(true, 1, 2, Element { descriptor: 50, bits: 8, packing: 0 }, 0, &[0; 4])).is_err()); assert!(decode("a.dpx", &f).is_ok()); } } diff --git a/crates/media/src/generators.rs b/crates/media/src/generators.rs index 482652438..891c1d2b0 100644 --- a/crates/media/src/generators.rs +++ b/crates/media/src/generators.rs @@ -16,7 +16,7 @@ use filmcraft_time::{FrameRate, TICKS_PER_SECOND, Tick}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; -use crate::digits::{draw_text, text_width}; +use crate::digits::{Canvas, draw_text, text_width}; use crate::{AudioStreamInfo, FrameCache, FrameRequest, MediaError, MediaInfo, MediaKind, MediaSource, Result, VideoStreamInfo}; /// Generator frames are RGBA8 and cached. Keep one project-requested frame within the media @@ -416,7 +416,7 @@ fn leader(px: &mut [u8], w: usize, h: usize, t: f32, _frame: i64, _rate: FrameRa let dh = (h as i32) / 3; let s = n.to_string(); let tw = text_width(dh, &s); - draw_text(px, w, h, (w as i32 - tw) / 2, (h as i32 - dh) / 2, dh, &s, [20, 20, 20, 255]); + draw_text(&mut Canvas { buf: px, width: w, height: h }, (w as i32 - tw) / 2, (h as i32 - dh) / 2, dh, &s, [20, 20, 20, 255]); } fn demo_pixel(scene: DemoScene, u: f32, v: f32, aspect: f32, t: f32) -> [f32; 3] {