use crossbeam_channel::{bounded, Receiver};
use cursive_core::{
Printer, Vec2,
view::View,
theme::{
ColorStyle,
Color
}
};
use image::{
ImageReader,
error::ImageError,
imageops::FilterType
};
use itertools::Itertools;
use rust_utils::{chainable, encapsulated};
use std::{
path::PathBuf,
thread::{self, JoinHandle}
};
type Image = Vec<Vec<(Color, Color)>>;
type ImageResult = Result<Image, ImageError>;
type ImageRenderThread = JoinHandle<ImageResult>;
#[encapsulated]
pub struct ImageView {
data: Option<Image>,
renderer: Option<ImageRenderThread>,
ready_rcv: Option<Receiver<()>>,
width: usize,
height: usize,
#[getter(doc = "Gets the path of the image")]
path: PathBuf,
#[setter(doc = "/// Minimize the view when empty?")]
#[chainable]
minimize: bool
}
impl ImageView {
pub fn new(width: usize, height: usize) -> ImageView {
ImageView {
data: None,
renderer: None,
ready_rcv: None,
width,
height,
path: PathBuf::new(),
minimize: false
}
}
#[chainable]
pub fn set_image<P: Into<PathBuf>>(&mut self, path: P) {
self.path = path.into();
self.data = None;
self.renderer = None;
self.ready_rcv = None;
}
pub fn set_size(&mut self, new_width: usize, new_height: usize) {
self.width = new_width;
self.height = new_height;
self.data = None;
self.renderer = None;
self.ready_rcv = None;
}
fn render_image_thread(&self) -> (Receiver<()>, ImageRenderThread) {
let width = self.width as u32;
let img_height = self.height * 2;
let img_reader = ImageReader::open(&self.path);
let (snd, rcv) = bounded::<()>(1);
(
rcv,
thread::spawn(move || {
let img = img_reader?.with_guessed_format()?.decode()?;
let mut img_colors: Vec<Vec<(Color, Color)>> = Vec::new();
let img_buffer = img
.adjust_contrast(50.)
.resize_exact(width, img_height as u32, FilterType::Triangle)
.to_rgb8();
for (cur_row, next_row) in img_buffer.rows().tuples() {
let mut colors: Vec<(Color, Color)> = Vec::new();
for (top_pxl, bottom_pxl) in cur_row.zip(next_row) {
colors.push((
Color::Rgb(bottom_pxl.0[0], bottom_pxl.0[1], bottom_pxl.0[2]),
Color::Rgb(top_pxl.0[0], top_pxl.0[1], top_pxl.0[2])
));
}
img_colors.push(colors);
}
snd.send(()).unwrap_or_default();
Ok(img_colors)
})
)
}
}
impl View for ImageView {
fn draw(&self, printer: &Printer) {
if let Some(ref image_data) = self.data {
for (y, row) in image_data.iter().enumerate() {
for (x, style) in row.iter().enumerate() {
printer.with_color(ColorStyle::from(*style), |printer| printer.print((x, y), "▄"))
}
}
}
}
fn required_size(&mut self, _: Vec2) -> Vec2 {
if self.minimize && self.data.is_none() && self.path.parent().is_none() {
Vec2::new(0, 0)
}
else {
Vec2::new(self.width, self.height)
}
}
fn layout(&mut self, _: Vec2) {
if self.data.is_none() && self.renderer.is_none() {
let (rcv, renderer) = self.render_image_thread();
self.ready_rcv = Some(rcv);
self.renderer = Some(renderer);
}
else if self.data.is_none() {
if let Some(ready_rcv) = self.ready_rcv.as_ref() {
if ready_rcv.is_full() {
if let Some(renderer) = self.renderer.take() {
if let Ok(data) = renderer.join().unwrap() {
self.data = Some(data);
}
self.ready_rcv = None;
}
}
}
}
}
}