Refactor media for strict Clippy checks

This commit is contained in:
Dylan M. Taylor
2026-10-28 02:05:37 +00:00
parent 1117dc3ee9
commit aae601f46b
3 changed files with 64 additions and 35 deletions
+25 -6
View File
@@ -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);
+37 -27
View File
@@ -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<u8> {
/// 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<u8> {
let Element { descriptor, bits, packing } = element;
let mut f = vec![0u8; 2048];
let p32 = |f: &mut Vec<u8>, 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<u8>, 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<u8> = [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());
}
}
+2 -2
View File
@@ -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] {