mirror of
https://github.com/storytold/filmcraft.git
synced 2026-11-02 18:09:21 +00:00
Refactor media for strict Clippy checks
This commit is contained in:
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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] {
|
||||
|
||||
Reference in New Issue
Block a user