#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubtitlePosition {
Bottom,
Top,
Middle,
Custom(u32, u32),
}
#[derive(Debug, Clone, PartialEq)]
pub struct BurnInConfig {
pub font_size: u32,
pub color: [u8; 4],
pub outline_color: [u8; 4],
pub outline_width: u32,
pub position: SubtitlePosition,
}
impl Default for BurnInConfig {
fn default() -> Self {
Self {
font_size: 16,
color: [255, 255, 255, 255],
outline_color: [0, 0, 0, 200],
outline_width: 1,
position: SubtitlePosition::Bottom,
}
}
}
const FONT_GLYPH_W: u32 = 8;
const FONT_GLYPH_H: u32 = 12;
const FONT_FIRST_CHAR: u8 = 0x20; const FONT_LAST_CHAR: u8 = 0x7E;
static FONT_BITMAP: [[u8; 12]; 95] = [
[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
],
[
0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x28, 0x28, 0x7C, 0x28, 0x28, 0x7C, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x3C, 0x50, 0x50, 0x38, 0x14, 0x14, 0x78, 0x10, 0x00, 0x00, 0x00,
],
[
0x62, 0x64, 0x08, 0x08, 0x10, 0x10, 0x26, 0x46, 0x00, 0x00, 0x00, 0x00,
],
[
0x30, 0x48, 0x48, 0x30, 0x50, 0x88, 0x88, 0x70, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00, 0x00, 0x00,
],
[
0x20, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x10, 0x54, 0x38, 0x54, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x10, 0x20, 0x00, 0x00,
],
[
0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
],
[
0x02, 0x04, 0x04, 0x08, 0x10, 0x20, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x44, 0x54, 0x64, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7C, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x04, 0x18, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x08, 0x18, 0x28, 0x48, 0x7C, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00,
],
[
0x7C, 0x40, 0x40, 0x78, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x1C, 0x20, 0x40, 0x78, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x7C, 0x04, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x44, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x44, 0x44, 0x3C, 0x04, 0x08, 0x70, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x10, 0x20, 0x00, 0x00, 0x00,
],
[
0x04, 0x08, 0x10, 0x20, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x7C, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x40, 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x04, 0x08, 0x10, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x5C, 0x54, 0x5C, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x28, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x78, 0x44, 0x44, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x40, 0x40, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x70, 0x48, 0x44, 0x44, 0x44, 0x44, 0x48, 0x70, 0x00, 0x00, 0x00, 0x00,
],
[
0x7C, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00, 0x00,
],
[
0x7C, 0x40, 0x40, 0x78, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x40, 0x40, 0x5C, 0x44, 0x44, 0x3C, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x48, 0x50, 0x60, 0x50, 0x48, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x6C, 0x54, 0x54, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x64, 0x54, 0x54, 0x4C, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x44, 0x44, 0x44, 0x54, 0x48, 0x34, 0x00, 0x00, 0x00, 0x00,
],
[
0x78, 0x44, 0x44, 0x78, 0x50, 0x48, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x44, 0x40, 0x38, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x44, 0x44, 0x54, 0x54, 0x54, 0x6C, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x44, 0x28, 0x10, 0x10, 0x28, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x44, 0x44, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,
],
[
0x7C, 0x04, 0x08, 0x10, 0x20, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00, 0x00,
],
[
0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x40, 0x20, 0x20, 0x10, 0x08, 0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00,
],
[
0x1C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1C, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00,
],
[
0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x38, 0x04, 0x3C, 0x44, 0x44, 0x3C, 0x00, 0x00, 0x00, 0x00,
],
[
0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x38, 0x44, 0x40, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x04, 0x04, 0x3C, 0x44, 0x44, 0x44, 0x44, 0x3C, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x38, 0x44, 0x7C, 0x40, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x18, 0x24, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x3C, 0x44, 0x44, 0x3C, 0x04, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x30, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x68, 0x54, 0x54, 0x54, 0x54, 0x54, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x3C, 0x44, 0x44, 0x3C, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x58, 0x24, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x38, 0x44, 0x30, 0x0C, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x3C, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x44, 0x44, 0x54, 0x54, 0x6C, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x44, 0x44, 0x3C, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00,
],
[
0x00, 0x00, 0x7C, 0x08, 0x10, 0x20, 0x40, 0x7C, 0x00, 0x00, 0x00, 0x00,
],
[
0x08, 0x10, 0x10, 0x20, 0x10, 0x10, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00,
],
[
0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
],
[
0x20, 0x10, 0x10, 0x08, 0x10, 0x10, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00,
],
[
0x30, 0x49, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
];
fn glyph_for(ch: char) -> &'static [u8; 12] {
let b = ch as u32;
if b >= FONT_FIRST_CHAR as u32 && b <= FONT_LAST_CHAR as u32 {
let idx = (b - FONT_FIRST_CHAR as u32) as usize;
&FONT_BITMAP[idx]
} else {
&FONT_BITMAP[(b'?' - FONT_FIRST_CHAR) as usize]
}
}
pub struct SubtitleBurnIn;
impl SubtitleBurnIn {
pub fn render_frame(
frame: &mut [u8],
width: u32,
height: u32,
text: &str,
config: &BurnInConfig,
) {
if width == 0 || height == 0 || text.is_empty() {
return;
}
let expected = (width as usize) * (height as usize) * 4;
if frame.len() < expected {
return;
}
let scale = (config.font_size / FONT_GLYPH_H).max(1);
let glyph_w = FONT_GLYPH_W * scale;
let glyph_h = FONT_GLYPH_H * scale;
let lines: Vec<&str> = text.lines().collect();
if lines.is_empty() {
return;
}
let max_line_chars = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
let block_w = (max_line_chars as u32) * glyph_w;
let block_h = (lines.len() as u32) * glyph_h;
let (origin_x, origin_y) = compute_origin(
width,
height,
block_w,
block_h,
config.outline_width,
&config.position,
);
if config.outline_width > 0 {
for (line_idx, line) in lines.iter().enumerate() {
let line_y = origin_y + (line_idx as u32) * glyph_h;
render_line_glyphs(
frame,
width,
height,
line,
origin_x,
line_y,
scale,
config.outline_color,
Some(config.outline_width),
);
}
}
for (line_idx, line) in lines.iter().enumerate() {
let line_y = origin_y + (line_idx as u32) * glyph_h;
render_line_glyphs(
frame,
width,
height,
line,
origin_x,
line_y,
scale,
config.color,
None,
);
}
}
}
fn compute_origin(
frame_w: u32,
frame_h: u32,
block_w: u32,
block_h: u32,
outline_w: u32,
position: &SubtitlePosition,
) -> (u32, u32) {
let centre_x = if frame_w > block_w {
(frame_w - block_w) / 2
} else {
0
};
let margin = outline_w + 4;
match position {
SubtitlePosition::Bottom => {
let y = if frame_h > block_h + margin {
frame_h - block_h - margin
} else {
0
};
(centre_x, y)
}
SubtitlePosition::Top => (centre_x, margin),
SubtitlePosition::Middle => {
let y = if frame_h > block_h {
(frame_h - block_h) / 2
} else {
0
};
(centre_x, y)
}
SubtitlePosition::Custom(x, y) => (*x, *y),
}
}
#[allow(clippy::too_many_arguments)]
fn render_line_glyphs(
frame: &mut [u8],
width: u32,
height: u32,
line: &str,
origin_x: u32,
origin_y: u32,
scale: u32,
color: [u8; 4],
outline_radius: Option<u32>,
) {
for (char_idx, ch) in line.chars().enumerate() {
let char_x = origin_x + (char_idx as u32) * FONT_GLYPH_W * scale;
let glyph = glyph_for(ch);
for row in 0..FONT_GLYPH_H {
let bitmap_byte = glyph[row as usize];
for col in 0..FONT_GLYPH_W {
let bit = (bitmap_byte >> (7 - col)) & 1;
if bit == 0 {
continue;
}
let base_px = char_x + col * scale;
let base_py = origin_y + row * scale;
match outline_radius {
Some(r) => {
let sx = base_px.saturating_sub(r);
let ex = (base_px + scale - 1 + r).min(width - 1);
let sy = base_py.saturating_sub(r);
let ey = (base_py + scale - 1 + r).min(height - 1);
for py in sy..=ey {
for px in sx..=ex {
put_pixel(frame, width, height, px, py, color);
}
}
}
None => {
for sy in 0..scale {
for sx in 0..scale {
put_pixel(frame, width, height, base_px + sx, base_py + sy, color);
}
}
}
}
}
}
}
}
#[inline]
fn put_pixel(frame: &mut [u8], width: u32, height: u32, px: u32, py: u32, color: [u8; 4]) {
if px >= width || py >= height {
return;
}
let offset = ((py as usize) * (width as usize) + (px as usize)) * 4;
if offset + 3 >= frame.len() {
return;
}
frame[offset] = color[0];
frame[offset + 1] = color[1];
frame[offset + 2] = color[2];
frame[offset + 3] = color[3];
}
#[cfg(test)]
mod tests {
use super::*;
fn blank_frame(w: u32, h: u32) -> Vec<u8> {
vec![0u8; (w as usize) * (h as usize) * 4]
}
fn any_pixel_set(frame: &[u8], color: [u8; 4]) -> bool {
frame.chunks_exact(4).any(|p| p == color)
}
#[test]
fn default_config_has_bottom_position() {
let cfg = BurnInConfig::default();
assert_eq!(cfg.position, SubtitlePosition::Bottom);
}
#[test]
fn default_config_white_text() {
let cfg = BurnInConfig::default();
assert_eq!(cfg.color[0], 255);
assert_eq!(cfg.color[3], 255);
}
#[test]
fn render_frame_empty_text_no_change() {
let mut frame = blank_frame(320, 240);
let cfg = BurnInConfig::default();
SubtitleBurnIn::render_frame(&mut frame, 320, 240, "", &cfg);
assert!(frame.iter().all(|&b| b == 0));
}
#[test]
fn render_frame_writes_pixels() {
let mut frame = blank_frame(320, 240);
let cfg = BurnInConfig {
font_size: 12,
color: [255, 0, 0, 255],
outline_color: [0, 0, 0, 255],
outline_width: 0,
position: SubtitlePosition::Bottom,
};
SubtitleBurnIn::render_frame(&mut frame, 320, 240, "Hello", &cfg);
assert!(any_pixel_set(&frame, [255, 0, 0, 255]));
}
#[test]
fn render_frame_bottom_position() {
let w = 320u32;
let h = 240u32;
let mut frame = blank_frame(w, h);
let cfg = BurnInConfig {
font_size: 12,
color: [255, 255, 255, 255],
outline_color: [0, 0, 0, 255],
outline_width: 0,
position: SubtitlePosition::Bottom,
};
SubtitleBurnIn::render_frame(&mut frame, w, h, "Hi", &cfg);
let bottom_start = (h * 3 / 4) as usize * w as usize * 4;
let bottom_pixels = &frame[bottom_start..];
assert!(bottom_pixels.chunks_exact(4).any(|p| p[3] > 0));
}
#[test]
fn render_frame_top_position() {
let w = 320u32;
let h = 240u32;
let mut frame = blank_frame(w, h);
let cfg = BurnInConfig {
font_size: 12,
color: [255, 255, 255, 255],
outline_color: [0, 0, 0, 255],
outline_width: 0,
position: SubtitlePosition::Top,
};
SubtitleBurnIn::render_frame(&mut frame, w, h, "Hi", &cfg);
let top_end = (h / 4) as usize * w as usize * 4;
let top_pixels = &frame[..top_end];
assert!(top_pixels.chunks_exact(4).any(|p| p[3] > 0));
}
#[test]
fn render_frame_middle_position() {
let w = 320u32;
let h = 240u32;
let mut frame = blank_frame(w, h);
let cfg = BurnInConfig {
font_size: 12,
color: [255, 255, 255, 255],
outline_color: [0, 0, 0, 255],
outline_width: 0,
position: SubtitlePosition::Middle,
};
SubtitleBurnIn::render_frame(&mut frame, w, h, "M", &cfg);
let mid_start = (h / 4) as usize * w as usize * 4;
let mid_end = (h * 3 / 4) as usize * w as usize * 4;
let mid_pixels = &frame[mid_start..mid_end];
assert!(mid_pixels.chunks_exact(4).any(|p| p[3] > 0));
}
#[test]
fn render_frame_custom_position() {
let w = 640u32;
let h = 480u32;
let mut frame = blank_frame(w, h);
let px = 100u32;
let py = 200u32;
let cfg = BurnInConfig {
font_size: 12,
color: [0, 255, 0, 255],
outline_color: [0, 0, 0, 255],
outline_width: 0,
position: SubtitlePosition::Custom(px, py),
};
SubtitleBurnIn::render_frame(&mut frame, w, h, "A", &cfg);
let region_start = (py as usize * w as usize + px as usize) * 4;
let region_end = (region_start + FONT_GLYPH_H as usize * w as usize * 4).min(frame.len());
let region = &frame[region_start..region_end];
assert!(region.chunks_exact(4).any(|p| p[3] > 0));
}
#[test]
fn render_frame_with_outline() {
let mut frame = blank_frame(320, 240);
let cfg = BurnInConfig {
font_size: 12,
color: [255, 255, 255, 255],
outline_color: [0, 0, 0, 200],
outline_width: 2,
position: SubtitlePosition::Bottom,
};
SubtitleBurnIn::render_frame(&mut frame, 320, 240, "Outline", &cfg);
assert!(any_pixel_set(&frame, [255, 255, 255, 255]));
assert!(any_pixel_set(&frame, [0, 0, 0, 200]));
}
#[test]
fn render_frame_zero_width_no_panic() {
let mut frame = blank_frame(1, 1);
let cfg = BurnInConfig::default();
SubtitleBurnIn::render_frame(&mut frame, 0, 240, "Hi", &cfg);
}
#[test]
fn render_frame_zero_height_no_panic() {
let mut frame = blank_frame(1, 1);
let cfg = BurnInConfig::default();
SubtitleBurnIn::render_frame(&mut frame, 320, 0, "Hi", &cfg);
}
#[test]
fn render_frame_undersized_buffer_no_panic() {
let mut frame = vec![0u8; 8];
let cfg = BurnInConfig::default();
SubtitleBurnIn::render_frame(&mut frame, 320, 240, "Hi", &cfg);
}
#[test]
fn render_frame_multiline_text() {
let mut frame = blank_frame(320, 240);
let cfg = BurnInConfig {
font_size: 12,
color: [128, 128, 128, 255],
outline_color: [0, 0, 0, 255],
outline_width: 0,
position: SubtitlePosition::Bottom,
};
SubtitleBurnIn::render_frame(&mut frame, 320, 240, "Line 1\nLine 2", &cfg);
assert!(any_pixel_set(&frame, [128, 128, 128, 255]));
}
#[test]
fn render_frame_large_font_scale() {
let mut frame = blank_frame(800, 600);
let cfg = BurnInConfig {
font_size: 36,
color: [200, 100, 50, 255],
outline_color: [0, 0, 0, 255],
outline_width: 1,
position: SubtitlePosition::Bottom,
};
SubtitleBurnIn::render_frame(&mut frame, 800, 600, "Big", &cfg);
assert!(any_pixel_set(&frame, [200, 100, 50, 255]));
}
#[test]
fn glyph_for_space_is_blank() {
let g = glyph_for(' ');
assert!(g.iter().all(|&b| b == 0));
}
#[test]
fn glyph_for_non_ascii_returns_question_mark() {
let g = glyph_for('Ω');
let q = glyph_for('?');
assert_eq!(g, q);
}
}