use super::{frames::FrameIterator, image_pipeline::ImagePipeline};
use crate::{common::errors::MyError, pipeline::char_maps::*, StringInfo};
use crossbeam_channel::{select, Receiver, Sender};
use image::DynamicImage;
use std::{thread, time::Duration};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum State {
Running,
Paused,
Stopped,
}
pub struct Runner {
pipeline: ImagePipeline,
media: FrameIterator,
fps: f64,
state: State,
tx_frames: Sender<Option<StringInfo>>,
rx_controls: Receiver<Control>,
w_mod: u32,
char_maps: Vec<Vec<char>>,
last_frame: Option<DynamicImage>,
}
#[derive(Debug, PartialEq)]
pub enum Control {
PauseContinue,
Exit,
SetCharMap(u32),
Resize(u16, u16),
SetGrayscale(bool),
}
impl Runner {
pub fn new(
pipeline: ImagePipeline,
media: FrameIterator,
fps: f64,
tx_frames: Sender<Option<StringInfo>>,
rx_controls: Receiver<Control>,
w_mod: u32,
) -> Self {
let char_maps: Vec<Vec<char>> = vec![
pipeline.char_map.clone(),
CHARS1.to_string().chars().collect(),
CHARS2.to_string().chars().collect(),
CHARS3.to_string().chars().collect(),
SOLID.to_string().chars().collect(),
DOTTED.to_string().chars().collect(),
GRADIENT.to_string().chars().collect(),
BLACKWHITE.to_string().chars().collect(),
BW_DOTTED.to_string().chars().collect(),
BRAILLE.to_string().chars().collect(),
];
Self {
pipeline,
media,
fps,
state: State::Running,
tx_frames,
rx_controls,
w_mod,
char_maps,
last_frame: None,
}
}
pub fn run(
&mut self,
barrier: std::sync::Arc<std::sync::Barrier>,
allow_frame_skip: bool,
) -> Result<(), MyError> {
barrier.wait();
let mut time_count = std::time::Instant::now();
while self.state != State::Stopped {
let frame_needs_refresh = self.process_control_commands();
let (should_process_frame, frames_to_skip) = self.should_process_frame(&mut time_count);
if should_process_frame {
if frames_to_skip > 0 && allow_frame_skip {
self.media.skip_frames(frames_to_skip);
}
let frame = self.get_current_frame();
select! {
send(self.tx_frames, None) -> _ => {
let string_info = self.process_current_frame(frame.as_ref(), frame_needs_refresh);
let _ = self.tx_frames.try_send(string_info);
},
default(Duration::from_millis(5)) => {
}
}
} else {
thread::yield_now();
}
}
Ok(())
}
fn process_frame(&mut self, frame: &DynamicImage) -> Result<StringInfo, MyError> {
let procimage = self.pipeline.resize(frame)?;
let grayimage = procimage.clone().into_luma8();
let rgb_info = procimage.into_rgb8().to_vec();
if self.pipeline.new_lines {
let mut rgb_info_newline =
Vec::with_capacity(rgb_info.len() + 6 * self.pipeline.target_resolution.0 as usize);
for (i, pixel) in rgb_info.chunks(3).enumerate() {
rgb_info_newline.extend_from_slice(pixel);
if (i + 1) % self.pipeline.target_resolution.0 as usize == 0 {
rgb_info_newline.extend_from_slice(&[0, 0, 0, 0, 0, 0]);
}
}
return Ok((self.pipeline.to_ascii(&grayimage), rgb_info_newline));
}
Ok((self.pipeline.to_ascii(&grayimage), rgb_info))
}
fn process_control_commands(&mut self) -> bool {
let mut needs_refresh = false;
while let Ok(control) = self.rx_controls.recv_timeout(Duration::from_millis(1)) {
needs_refresh = true;
match control {
Control::PauseContinue => self.toggle_pause(),
Control::Exit => self.state = State::Stopped,
Control::Resize(width, height) => {
self.resize_pipeline(width, height);
}
Control::SetCharMap(char_map) => {
self.set_char_map(char_map);
}
Control::SetGrayscale(_) => { }
}
}
needs_refresh
}
fn toggle_pause(&mut self) {
match self.state {
State::Running => self.state = State::Paused,
State::Paused => self.state = State::Running,
_ => {}
}
}
fn resize_pipeline(&mut self, width: u16, height: u16) {
let _ = self
.pipeline
.set_target_resolution((width / self.w_mod as u16).into(), height.into());
}
fn set_char_map(&mut self, char_map: u32) {
self.pipeline.char_map =
self.char_maps[(char_map % self.char_maps.len() as u32) as usize].clone();
}
fn should_process_frame(&self, time_count: &mut std::time::Instant) -> (bool, usize) {
let (time_to_send_next_frame, frames_to_skip) = self.time_to_send_next_frame(time_count);
if time_to_send_next_frame && (self.state == State::Running || self.state == State::Paused)
{
(true, frames_to_skip)
} else {
(false, 0)
}
}
fn time_to_send_next_frame(&self, time_count: &mut std::time::Instant) -> (bool, usize) {
let target_frame_duration = Duration::from_nanos(1_000_000_000_u64 / self.fps as u64);
let elapsed_time = time_count.elapsed();
if elapsed_time >= target_frame_duration {
let frames_to_skip =
(elapsed_time.as_nanos() / target_frame_duration.as_nanos()) as usize - 1;
*time_count += target_frame_duration * (frames_to_skip as u32 + 1);
(true, frames_to_skip)
} else {
(false, 0)
}
}
fn get_current_frame(&mut self) -> Option<DynamicImage> {
match self.state {
State::Running => self.media.next(),
State::Paused | State::Stopped => self.last_frame.clone(),
}
}
fn process_current_frame(
&mut self,
frame: Option<&DynamicImage>,
refresh: bool,
) -> Option<StringInfo> {
match frame {
Some(frame) => {
self.last_frame = Some(frame.clone());
if let Ok(string_info) = self.process_frame(frame) {
return Some(string_info);
}
None
}
None => {
if self.last_frame.is_some() && refresh {
if let Ok(string_info) = self.process_frame(
&self
.last_frame
.clone()
.expect("Last frame should be available"),
) {
return Some(string_info);
}
}
None
}
}
}
}