Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better images #46

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ enabled = true
path = "images/sample_1.gif"
# This only works if the image feature is passed in the build instructions
# It supports all those formats : https://github.com/image-rs/image/tree/8824ab3375ddab0fd3429fe3915334523d50c532#supported-image-formats
# (even in color, but it will only display in black and white)
# (even in color, but it will only display in black and white)
# (Hint: you can also put a folder, it will cycle through every __file__ in that folder)
87 changes: 71 additions & 16 deletions src/providers/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ use embedded_graphics::geometry::Point;
use futures::Stream;
use linkme::distributed_slice;
use log::info;
use std::fs::File;
use std::{
fs::{self, File},
path::Path,
sync::atomic::{AtomicUsize, Ordering},
};
use tokio::{
time,
time::{Duration, MissedTickBehavior},
Expand All @@ -25,37 +29,88 @@ pub static PROVIDER_INIT: fn(&Config) -> Result<Box<dyn ContentWrapper>> = regis
fn register_callback(config: &Config) -> Result<Box<dyn ContentWrapper>> {
info!("Registering Image display source.");

let image_path = config.get_str("image.path").unwrap_or_else(|_| String::from("images/sample_1.gif"));
let image_file = File::open(&image_path);
let image_path = config
.get_str("image.path")
.unwrap_or_else(|_| String::from("images/sample_1.gif"));
let mut images = Vec::new();

if Path::new(&image_path).is_dir() {
for file in fs::read_dir(image_path).unwrap() {
let file_path = file.unwrap().path();
let image_file = File::open(file_path.clone());

let image = match image_file {
Ok(file) => image::ImageRenderer::new(Point::new(0, 0), Point::new(128, 40), file),
Err(err) => {
log::error!("Failed to open the image '{}': {}", image_path, err);
let image = match image_file {
Ok(file) => image::ImageRenderer::new(Point::new(0, 0), Point::new(128, 40), file),
Err(err) => {
log::error!(
"Failed to open the image '{}': {}",
file_path.display(),
err
);

// Use the `new_error` function to create an error GIF
image::ImageRenderer::new_error(Point::new(0, 0), Point::new(128, 40))
// Use the `new_error` function to create an error GIF
image::ImageRenderer::new_error(Point::new(0, 0), Point::new(128, 40))
}
};
images.push(image);
}
};
} else {
let image_file = File::open(&image_path);

let image = match image_file {
Ok(file) => image::ImageRenderer::new(Point::new(0, 0), Point::new(128, 40), file),
Err(err) => {
log::error!("Failed to open the image '{}': {}", image_path, err);

// Use the `new_error` function to create an error GIF
image::ImageRenderer::new_error(Point::new(0, 0), Point::new(128, 40))
}
};
images.push(image);
}

Ok(Box::new(Image { image }))
Ok(Box::new(Images {
images,
current_image: AtomicUsize::new(0),
}))
}

pub struct Image {
image: image::ImageRenderer,
pub struct Images {
images: Vec<image::ImageRenderer>,
current_image: AtomicUsize,
}

impl Image {
impl Images {
pub fn render(&self) -> Result<FrameBuffer> {
let mut buffer = FrameBuffer::new();

self.image.draw(&mut buffer);
let image = self.current_image.load(Ordering::Relaxed);
//get the data for the specified frame
let image_data = &self.images[image];
//draw and detect if the gif/image has ended
let has_ended = image_data.draw(&mut buffer);

if has_ended && !self.images.len() == 1 {
let mut next_image = image + 1;
let has_no_next_image = next_image >= self.images.len();

if has_no_next_image {
//reset to frame 0
next_image = 0;
} else {
next_image = next_image;
}

self.current_image.store(next_image, Ordering::Relaxed);
let next = &self.images[next_image];
next.set_display_time();
}

Ok(buffer)
}
}

impl ContentProvider for Image {
impl ContentProvider for Images {
type ContentStream<'a> = impl Stream<Item = Result<FrameBuffer>> + 'a;

// This needs to be enabled until full GAT support is here
Expand Down
80 changes: 59 additions & 21 deletions src/render/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use embedded_graphics::{
prelude::Point,
Drawable,
};
use image::{AnimationDecoder, DynamicImage};
use image::{AnimationDecoder, DynamicImage, GenericImageView};

static GIF_MISSING: &[u8] = include_bytes!("./../../assets/gif_missing.gif");
static DISPLAY_HEIGHT: i32 = 40;
Expand Down Expand Up @@ -160,21 +160,45 @@ impl ImageRenderer {
}

pub fn fit_image(image: DynamicImage, size: Point) -> DynamicImage {
if image.height() > size.y as u32 {
let width = image.width() * size.y as u32 / image.height();
let height = size.y as u32;

image.resize(width, height, image::imageops::FilterType::Nearest)
} else if image.width() > size.x as u32 {
let width = size.x as u32;
let height = image.height() * size.x as u32 / image.width();

image.resize(width, height, image::imageops::FilterType::Nearest)
if image.height() > size.y as u32 || image.width() > size.x as u32 {
image.resize(
size.x as u32,
size.y as u32,
image::imageops::FilterType::Nearest,
)
} else {
image
}
}

pub fn center_image(image: DynamicImage, size: Point) -> DynamicImage {
let new_x = (size.x as u32 - image.width()) / 2;
let new_y = (size.y as u32 - image.height()) / 2;

Self::move_image(image, Point::new(new_x as i32, new_y as i32), size)
}

pub fn move_image(image: DynamicImage, offset: Point, size: Point) -> DynamicImage {
let mut buffer = image::RgbaImage::new(size.x as u32, size.y as u32);

for x in 0..image.width() {
let true_x = x as i32 + offset.x;
if true_x < 0 {
continue;
}
for y in 0..image.height() {
let true_y = y as i32 + offset.y;
if true_y < 0 {
continue;
}

buffer.put_pixel(true_x as u32, true_y as u32, image.get_pixel(x, y));
}
}

DynamicImage::from(buffer)
}

pub fn read_dynamic_image(
origin: Point,
stop: Point,
Expand All @@ -190,39 +214,47 @@ impl ImageRenderer {

if let Ok(gif) = image::codecs::gif::GifDecoder::new(&buffer[..]) {
//if the image is a gif
//NOTE we do not check for the size of each frame!
//We can avoid doing so since we have the Self::fit_image which will resize the
// frames correctly.

// We do not need to check for the size of each frame since we have the
// Self::fit_image which will resize the frames correctly.

//we go through each frame
for frame in gif.into_frames() {
//TODO we do not handle if the frame isn't formatted properly!
if let Ok(frame) = frame {
//TODO some gifs do not have delays embedded, we should use a 100 ms in that
// case
delays.push(Duration::from(frame.delay()).as_millis() as u16);
//get the delay between this frame and the next
let mut delay = Duration::from(frame.delay()).as_millis() as u16;
//if no delay is set, default to 16 (to get ~60 fps)
if delay == 0 {
delay = 16;
}

delays.push(delay);
let resized = Self::fit_image(
DynamicImage::ImageRgba8(frame.into_buffer()),
Point::new(DISPLAY_WIDTH, DISPLAY_HEIGHT),
);
let centered =
Self::center_image(resized, Point::new(DISPLAY_WIDTH, DISPLAY_HEIGHT));

decoded_frames.push(Self::read_image(
&resized.into_rgba8(),
&centered.into_rgba8(),
image_height,
image_width,
));
}
}
} else {
let resized = Self::fit_image(image, Point::new(DISPLAY_WIDTH, DISPLAY_HEIGHT));
let centered = Self::center_image(resized, Point::new(DISPLAY_WIDTH, DISPLAY_HEIGHT));
//if the image is a still image
decoded_frames.push(Self::read_image(
&resized.into_rgba8(),
&centered.into_rgba8(),
image_height,
image_width,
));
delays.push(500); // Add a default delay of 500ms for single image
// rendering
delays.push(1500); // Add a default delay of 500ms for single image
// rendering
}

Self {
Expand Down Expand Up @@ -264,6 +296,8 @@ impl ImageRenderer {
}

pub fn draw(&self, target: &mut FrameBuffer) -> bool {
//TODO This runs every 10ms, this doesn't need to run that fast everytime when
// rendering still images (so maybe we can avoid rendering each time)
let frame = self.current_frame.load(Ordering::Relaxed);

//get the data for the specified frame
Expand Down Expand Up @@ -302,4 +336,8 @@ impl ImageRenderer {
}
false
}

pub fn set_display_time(&self) {
*self.time_frame_last_update.borrow_mut() = Instant::now();
}
}