implement text color in image rendering, implement wrapping of text, print runtime errors on device

This commit is contained in:
Fl1tzi 2023-12-25 23:14:56 +01:00
parent 4536e0a8cd
commit 1f1c388eb3
No known key found for this signature in database
GPG key ID: 06B333727810C686
3 changed files with 76 additions and 44 deletions

View file

@ -46,6 +46,7 @@ pub struct ImageBuilder {
scale: f32, scale: f32,
font_size: f32, font_size: f32,
text: Option<String>, text: Option<String>,
text_color: [u8; 3],
image: Option<DynamicImage>, image: Option<DynamicImage>,
} }
@ -59,6 +60,8 @@ impl Default for ImageBuilder {
scale: 60.0, scale: 60.0,
font_size: 16.0, font_size: 16.0,
text: None, text: None,
// black
text_color: [255, 255, 255],
image: None, image: None,
} }
} }
@ -92,6 +95,12 @@ impl ImageBuilder {
self self
} }
#[allow(dead_code)]
pub fn set_text_color(mut self, text_color: [u8; 3]) -> Self {
self.text_color = text_color;
self
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn set_image(mut self, image: DynamicImage) -> Self { pub fn set_image(mut self, image: DynamicImage) -> Self {
self.image = Some(image); self.image = Some(image);
@ -108,6 +117,7 @@ impl ImageBuilder {
image: self.image.unwrap(), image: self.image.unwrap(),
scale: self.scale, scale: self.scale,
font_size: self.font_size, font_size: self.font_size,
text_color: self.text_color,
text: self.text.unwrap(), text: self.text.unwrap(),
}; };
return c.render(); return c.render();
@ -116,6 +126,7 @@ impl ImageBuilder {
height: self.height, height: self.height,
width: self.width, width: self.width,
font_size: self.font_size, font_size: self.font_size,
text_color: self.text_color,
text, text,
}; };
return c.render(); return c.render();
@ -170,6 +181,7 @@ struct TextComponent {
height: usize, height: usize,
width: usize, width: usize,
font_size: f32, font_size: f32,
text_color: [u8; 3],
text: String, text: String,
} }
@ -177,27 +189,10 @@ impl Component for TextComponent {
fn render(&self) -> DynamicImage { fn render(&self) -> DynamicImage {
let mut image = RgbImage::new(self.width as u32, self.height as u32); let mut image = RgbImage::new(self.width as u32, self.height as u32);
let scale = Scale::uniform(self.font_size); let font_scale = Scale::uniform(self.font_size);
let font = &GLOBAL_FONT.get().unwrap(); let text = wrap_text(self.height as u32, font_scale, &self.text);
let v_metrics = font.v_metrics(scale); draw_text_on_image(&text, &mut image, Rgb(self.text_color), font_scale);
let height = (v_metrics.ascent - v_metrics.descent + v_metrics.line_gap).round() as i32;
// start at y = 10
let mut y_pos = 10;
for line in self.text.split("\n") {
draw_text_mut(
&mut image,
Rgb([255, 255, 255]),
10,
y_pos,
scale,
&GLOBAL_FONT.get().unwrap(),
&line,
);
y_pos += height;
}
image::DynamicImage::ImageRgb8(image) image::DynamicImage::ImageRgb8(image)
} }
@ -210,6 +205,7 @@ struct ImageTextComponent {
image: DynamicImage, image: DynamicImage,
scale: f32, scale: f32,
font_size: f32, font_size: f32,
text_color: [u8; 3],
text: String, text: String,
} }
@ -224,19 +220,9 @@ impl Component for ImageTextComponent {
let mut base_image = RgbImage::new(self.height as u32, self.width as u32); let mut base_image = RgbImage::new(self.height as u32, self.width as u32);
let font = &GLOBAL_FONT.get().unwrap();
let font_scale = Scale::uniform(self.font_size); let font_scale = Scale::uniform(self.font_size);
let text = wrap_text(self.height as u32, font_scale, &self.text);
// TODO: allow new line draw_text_on_image(&text, &mut base_image, Rgb(self.text_color), font_scale);
draw_text_mut(
&mut base_image,
Rgb([255, 255, 255]),
0,
0,
font_scale,
font,
&self.text,
);
// position at the middle // position at the middle
let free_space = self.width - image.width() as usize; let free_space = self.width - image.width() as usize;
// TODO: allow padding to be manually set // TODO: allow padding to be manually set
@ -250,3 +236,37 @@ impl Component for ImageTextComponent {
image::DynamicImage::ImageRgb8(base_image) image::DynamicImage::ImageRgb8(base_image)
} }
} }
fn draw_text_on_image(text: &String, image: &mut RgbImage, color: Rgb<u8>, font_scale: Scale) {
let font = &GLOBAL_FONT.get().unwrap();
let v_metrics = font.v_metrics(font_scale);
let line_height = (v_metrics.ascent - v_metrics.descent + v_metrics.line_gap).round() as i32;
let mut y_pos = 0;
for line in text.split('\n') {
draw_text_mut(image, color, 0, y_pos, font_scale, font, &line);
y_pos += line_height
}
}
/// This functions adds '\n' to the line endings. It does not wrap
/// words but characters.
pub fn wrap_text(max_width: u32, font_size: Scale, text: &String) -> String {
let font = &GLOBAL_FONT.get().unwrap();
let mut new_text: Vec<char> = Vec::new();
let mut line_size = 0.0;
for character in text.chars() {
let h_size = font.glyph(character).scaled(font_size).h_metrics();
let complete_width = h_size.advance_width + h_size.left_side_bearing;
if (line_size + complete_width) as u32 > max_width {
new_text.push('\n');
line_size = 0.0;
}
new_text.push(character);
line_size += h_size.advance_width + h_size.left_side_bearing;
}
String::from_iter(new_text)
}

View file

@ -11,7 +11,7 @@ use self::space::Space;
// other things // other things
use crate::config::{Button, ButtonConfigError}; use crate::config::{Button, ButtonConfigError};
use crate::device::ImageCache; use crate::device::ImageCache;
use crate::image_rendering::load_image; use crate::image_rendering::{load_image, ImageBuilder};
use ::image::imageops::FilterType; use ::image::imageops::FilterType;
use ::image::DynamicImage; use ::image::DynamicImage;
use async_trait::async_trait; use async_trait::async_trait;
@ -91,13 +91,13 @@ pub async fn start_module(
button.index, button.index,
device.kind().key_image_format().size, device.kind().key_image_format().size,
); );
let da = DeviceAccess::new(device, button.index).await; let da = DeviceAccess::new(device.clone(), button.index).await;
// init // init
// //
// This function should be called after the config was checked, // This function should be called after the config was checked,
// otherwise it will panic and the module wont be started. // otherwise it will panic and the module wont be started.
let mut module = match module_init_function(button, mc).await { let mut module = match module_init_function(button.clone(), mc).await {
Ok(m) => m, Ok(m) => m,
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
}; };
@ -105,7 +105,19 @@ pub async fn start_module(
// then run module // then run module
match module.run(da, br).await { match module.run(da, br).await {
Ok(_) => debug!("RETURNED"), Ok(_) => debug!("RETURNED"),
Err(e) => error!("RETURNED_ERROR: {}", e), // TODO: maybe find calculation for font size
// print error on display
Err(e) => {
error!("{e}");
let da = DeviceAccess::new(device, button.index).await;
let res = da.resolution();
let image = ImageBuilder::new(res.0, res.1)
.set_text(format!("E: {}", e))
.set_font_size(12.0)
.set_text_color([255, 0, 0])
.build();
da.write_img(image).await.unwrap();
}
} }
} }
@ -236,8 +248,6 @@ pub trait Module: Sync + Send {
Self: Sized; Self: Sized;
/// Function for actually running the module and interacting with the device. Errors that /// Function for actually running the module and interacting with the device. Errors that
/// happen here should be mostly prevented. /// happen here should be mostly prevented.
///
/// TODO: The return error is not sent anywhere and is just a panic
async fn run( async fn run(
&mut self, &mut self,
device: DeviceAccess, device: DeviceAccess,

View file

@ -7,6 +7,7 @@ use super::{
ReturnError, ReturnError,
}; };
use crate::image_rendering::wrap_text;
use crate::GLOBAL_FONT; use crate::GLOBAL_FONT;
use image::{DynamicImage, Rgb, RgbImage}; use image::{DynamicImage, Rgb, RgbImage};
use imageproc::drawing::draw_text_mut; use imageproc::drawing::draw_text_mut;
@ -96,18 +97,19 @@ fn render_text(
let scale = Scale::uniform(title_size); let scale = Scale::uniform(title_size);
let font = &GLOBAL_FONT.get().unwrap(); let font = &GLOBAL_FONT.get().unwrap();
let v_metrics = font.v_metrics(scale); let v_metrics = font.v_metrics(scale);
let height = (v_metrics.ascent - v_metrics.descent + v_metrics.line_gap).round() as i32; let height = (v_metrics.ascent - v_metrics.descent + v_metrics.line_gap).round() as i32;
// start at y = 10 let text = wrap_text(image.width(), scale, &title);
let mut y_pos = 10;
for line in title.split("\n") { // start at y = 0
let mut y_pos = 0;
for line in text.split("\n") {
draw_text_mut( draw_text_mut(
&mut image, &mut image,
Rgb([255, 255, 255]), Rgb([255, 255, 255]),
10, 0,
y_pos, y_pos,
Scale::uniform(title_size), Scale::uniform(title_size),
&GLOBAL_FONT.get().unwrap(), &GLOBAL_FONT.get().unwrap(),
@ -119,7 +121,7 @@ fn render_text(
draw_text_mut( draw_text_mut(
&mut image, &mut image,
Rgb([255, 255, 255]), Rgb([255, 255, 255]),
10, 0,
y_pos, y_pos,
Scale::uniform(number_size), Scale::uniform(number_size),
&GLOBAL_FONT.get().unwrap(), &GLOBAL_FONT.get().unwrap(),