Skip to content

Commit

Permalink
font-drawing: fix DRY
Browse files Browse the repository at this point in the history
  • Loading branch information
astraw committed Oct 26, 2024
1 parent 4f716a9 commit 89f1d89
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 221 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ members = [
"led-box-standalone",
"machine-vision-shaders",
"media-utils/dump-frame",
"media-utils/font-drawing",
"media-utils/frame-source",
"media-utils/less-avc-wrapper",
"media-utils/mkv-parser-kit",
Expand Down
9 changes: 9 additions & 0 deletions media-utils/font-drawing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "font-drawing"
version = "0.1.0"
edition = "2021"

[dependencies]
machine-vision-formats.workspace = true
rusttype = "0.9.2"
eyre.workspace = true
95 changes: 95 additions & 0 deletions media-utils/font-drawing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use rusttype::{point, Scale};
use machine_vision_formats::{pixel_format, ImageMutStride};
use eyre::Result;

struct Rgba(pub [u8; 4]);

fn put_pixel(image: &mut dyn ImageMutStride<pixel_format::RGB8>, x: u32, y: u32, incoming: Rgba) {
let row_start = image.stride() as usize * y as usize;
let pix_start = row_start + x as usize * 3;

let alpha = incoming.0[3] as f64 / 255.0;
let p = 1.0 - alpha;
let q = alpha;

let old: [u8; 3] = image.image_data()[pix_start..pix_start + 3]
.try_into()
.unwrap();
let new: [u8; 3] = [
(old[0] as f64 * p + incoming.0[0] as f64 * q).round() as u8,
(old[1] as f64 * p + incoming.0[1] as f64 * q).round() as u8,
(old[2] as f64 * p + incoming.0[2] as f64 * q).round() as u8,
];

image.buffer_mut_ref().data[pix_start] = new[0];
image.buffer_mut_ref().data[pix_start + 1] = new[1];
image.buffer_mut_ref().data[pix_start + 2] = new[2];
}

pub fn stamp_frame<'a>(image: &mut dyn ImageMutStride<pixel_format::RGB8>, font: &rusttype::Font<'a>, text: &str) -> Result<()> {
// from https://gitlab.redox-os.org/redox-os/rusttype/blob/master/dev/examples/image.rs

// The font size to use
let scale = Scale::uniform(32.0);

// Use a dark red colour
let colour = (150, 0, 0);

let v_metrics = font.v_metrics(scale);

let x0 = 20.0;
let y0 = 20.0;

// layout the glyphs in a line with 20 pixels padding
let glyphs: Vec<_> = font
.layout(text, scale, point(x0, y0 + v_metrics.ascent))
.collect();

// Find the most visually pleasing width to display
let width = glyphs
.iter()
.rev()
.map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
.next()
.unwrap_or(0.0)
.ceil() as usize;

let x_start = x0.floor() as usize;
let x_end = x_start + width;

let y_start = y0.floor() as usize;
let y_end = y_start + v_metrics.ascent.ceil() as usize;

for x in x_start..x_end {
for y in y_start..y_end {
put_pixel(
image,
// Offset the position by the glyph bounding box
x as u32,
y as u32,
// Turn the coverage into an alpha value
Rgba([255, 255, 255, 255]),
)
}
}

// TODO: clear background

for glyph in glyphs {
if let Some(bounding_box) = glyph.pixel_bounding_box() {
// Draw the glyph into the image per-pixel by using the draw closure
glyph.draw(|x, y, v| {
put_pixel(
image,
// Offset the position by the glyph bounding box
x + bounding_box.min.x as u32,
y + bounding_box.min.y as u32,
// Turn the coverage into an alpha value
Rgba([colour.0, colour.1, colour.2, (v * 255.0) as u8]),
)
});
}
}

Ok(())
}
9 changes: 3 additions & 6 deletions media-utils/less-avc-wrapper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ chrono = { version = "0.4.34", default-features = false }
thiserror = "1"

[dev-dependencies]
anyhow = "1"
eyre.workspace = true
log = "0.4"
env_logger = { workspace = true }
h264-reader = "0.7.0"
Expand All @@ -21,10 +21,7 @@ ttf-firacode = "0.1"
image = { workspace = true, features = ["jpeg", "tiff", "png"] }

simple-frame = { path = "../../simple-frame" }
font-drawing = { path = "../font-drawing" }

[features]
backtrace = [
"less-avc/backtrace",
"convert-image/backtrace",
"anyhow/backtrace",
]
backtrace = ["less-avc/backtrace", "convert-image/backtrace"]
9 changes: 6 additions & 3 deletions media-utils/less-avc-wrapper/examples/formats-and-sizes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2022-2023 Andrew D. Straw.

fn main() -> anyhow::Result<()> {
fn main() -> eyre::Result<()> {
let n_frames = 1;

let start = chrono::DateTime::from_timestamp(61, 0).unwrap();
Expand Down Expand Up @@ -104,8 +104,11 @@ fn main() -> anyhow::Result<()> {
let png_fname = format!("frame-{}-{}x{}.png", pixfmt_str, width, height);
let opts = convert_image::ImageOptions::Png;
use basic_frame::{match_all_dynamic_fmts, DynamicFrame};
let png_buf =
match_all_dynamic_fmts!(&image, x, convert_image::frame_to_encoded_buffer(x, opts))?;
let png_buf = match_all_dynamic_fmts!(
&image,
x,
convert_image::frame_to_encoded_buffer(x, opts)
)?;
let mut fd = std::fs::File::create(png_fname)?;
use std::io::Write;
fd.write_all(&png_buf)?;
Expand Down
107 changes: 5 additions & 102 deletions media-utils/less-avc-wrapper/examples/save-animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,112 +4,15 @@ extern crate log;

use machine_vision_formats::{
pixel_format::{Mono8, RGB8},
Stride,
ImageMutStride, Stride,
};

use font_drawing::stamp_frame;
use simple_frame::SimpleFrame;

use rusttype::{point, Font, Scale};
use rusttype::Font;

struct Rgba(pub [u8; 4]);

fn put_pixel(self_: &mut SimpleFrame<RGB8>, x: u32, y: u32, incoming: Rgba) {
use machine_vision_formats::{ImageData, ImageMutData};

let row_start = self_.stride as usize * y as usize;
let pix_start = row_start + x as usize * 3;

let alpha = incoming.0[3] as f64 / 255.0;
let p = 1.0 - alpha;
let q = alpha;

let old: [u8; 3] = self_.image_data()[pix_start..pix_start + 3]
.try_into()
.unwrap();
let new: [u8; 3] = [
(old[0] as f64 * p + incoming.0[0] as f64 * q).round() as u8,
(old[1] as f64 * p + incoming.0[1] as f64 * q).round() as u8,
(old[2] as f64 * p + incoming.0[2] as f64 * q).round() as u8,
];

self_.buffer_mut_ref().data[pix_start] = new[0];
self_.buffer_mut_ref().data[pix_start + 1] = new[1];
self_.buffer_mut_ref().data[pix_start + 2] = new[2];
}

fn stamp_frame<'a>(
image: &mut SimpleFrame<RGB8>,
font: &rusttype::Font<'a>,
text: &str,
) -> Result<(), anyhow::Error> {
// from https://gitlab.redox-os.org/redox-os/rusttype/blob/master/dev/examples/image.rs

// The font size to use
let scale = Scale::uniform(32.0);

// Use a dark red colour
let colour = (150, 0, 0);

let v_metrics = font.v_metrics(scale);

let x0 = 20.0;
let y0 = 20.0;

// layout the glyphs in a line with 20 pixels padding
let glyphs: Vec<_> = font
.layout(text, scale, point(x0, y0 + v_metrics.ascent))
.collect();

// Find the most visually pleasing width to display
let width = glyphs
.iter()
.rev()
.map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
.next()
.unwrap_or(0.0)
.ceil() as usize;

let x_start = x0.floor() as usize;
let x_end = x_start + width;

let y_start = y0.floor() as usize;
let y_end = y_start + v_metrics.ascent.ceil() as usize;

for x in x_start..x_end {
for y in y_start..y_end {
put_pixel(
image,
// Offset the position by the glyph bounding box
x as u32,
y as u32,
// Turn the coverage into an alpha value
Rgba([255, 255, 255, 255]),
)
}
}

// TODO: clear background

for glyph in glyphs {
if let Some(bounding_box) = glyph.pixel_bounding_box() {
// Draw the glyph into the image per-pixel by using the draw closure
glyph.draw(|x, y, v| {
put_pixel(
image,
// Offset the position by the glyph bounding box
x + bounding_box.min.x as u32,
y + bounding_box.min.y as u32,
// Turn the coverage into an alpha value
Rgba([colour.0, colour.1, colour.2, (v * 255.0) as u8]),
)
});
}
}

Ok(())
}

fn main() -> anyhow::Result<()> {
fn main() -> eyre::Result<()> {
env_logger::init();

let image = image::load_from_memory(&include_bytes!("bee.jpg")[..])?;
Expand Down Expand Up @@ -156,7 +59,7 @@ fn main() -> anyhow::Result<()> {
let text = format!("{}", count);
let mut frame = rgb.clone();

stamp_frame(&mut frame, &font, &text)?;
stamp_frame(&mut frame as &mut dyn ImageMutStride<_>, &font, &text)?;
count += 1;

let opts = convert_image::ImageOptions::Png;
Expand Down
12 changes: 7 additions & 5 deletions media-utils/mp4-writer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ thiserror = "1.0.33"
machine-vision-formats.workspace = true
bitvec = "1.0.1"
h264-reader = "0.7.0"
less-avc = "0.1.4"

nvenc = { path = "../../nvenc" }
dynlink-cuda = { path = "../../nvenc/dynlink-cuda" }
Expand All @@ -27,7 +28,6 @@ dynlink-nvidia-encode = { path = "../../nvenc/dynlink-nvidia-encode" }
ci2-remote-control = { path = "../../ci2-remote-control" }
convert-image = { path = "../../convert-image" }
basic-frame = { path = "../../basic-frame", features = ["convert-image"] }
less-avc = "0.1.4"
less-avc-wrapper = { path = "../less-avc-wrapper" }
frame-source = { path = "../frame-source" }
image-iter = { path = "../../image-iter" }
Expand All @@ -36,15 +36,17 @@ serde_json = "1.0.89"

[dev-dependencies]
env_logger = { workspace = true }
anyhow = "1"
ci2-remote-control = { path = "../../ci2-remote-control" }
rusttype = "0.8.1"
eyre.workspace = true
image = { workspace = true, features = ["jpeg", "tiff", "png"] }
ttf-firacode = "0.1"
simple-frame = { path = "../../simple-frame" }
rusttype = "0.9.2"
tempfile = "3.4.0"
clap = { version = "4.0.10", features = ["derive"] }

ci2-remote-control = { path = "../../ci2-remote-control" }
simple-frame = { path = "../../simple-frame" }
font-drawing = { path = "../font-drawing" }

[features]
openh264-encode = ["openh264"]
backtrace = [
Expand Down
2 changes: 1 addition & 1 deletion media-utils/mp4-writer/examples/formats-and-sizes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn next16(x: IType) -> IType {
div_ceil(x, v) * 16
}

fn main() -> Result<(), anyhow::Error> {
fn main() -> eyre::Result<()> {
let n_frames = 1;

let start = chrono::DateTime::from_timestamp(61, 0).unwrap();
Expand Down
Loading

0 comments on commit 89f1d89

Please sign in to comment.