cursive_extras/views/
image_view.rs

1use crossbeam_channel::{bounded, Receiver};
2use cursive_core::{
3    Printer, Vec2,
4    view::View,
5    theme::{
6        ColorStyle,
7        Color
8    }
9};
10use image::{
11    ImageReader,
12    error::ImageError,
13    imageops::FilterType
14};
15use itertools::Itertools;
16use rust_utils::{chainable, encapsulated};
17use std::{
18    path::PathBuf,
19    thread::{self, JoinHandle}
20};
21
22type Image = Vec<Vec<(Color, Color)>>;
23type ImageResult = Result<Image, ImageError>;
24type ImageRenderThread = JoinHandle<ImageResult>;
25
26
27//TODO: static image view
28// a view that uses a procedural macro that will generate an image
29// to display at compile time
30/// View that can render a low res image as text
31#[encapsulated]
32pub struct ImageView {
33    data: Option<Image>,
34    renderer: Option<ImageRenderThread>,
35    ready_rcv: Option<Receiver<()>>,
36    size: Vec2,
37
38    #[getter(doc = "Gets the path of the image")]
39    path: PathBuf,
40
41    #[setter(doc = "Minimize the view when empty?")]
42    #[chainable]
43    minimize: bool
44}
45
46impl ImageView {
47    /// Create a new empty `ImageView`
48    pub fn new<S: Into<Vec2>>(size: S) -> ImageView {
49        ImageView {
50            data: None,
51            renderer: None,
52            ready_rcv: None,
53            path: PathBuf::new(),
54            minimize: false,
55            size: size.into()
56        }
57    }
58
59    /// Set the image to render
60    #[chainable]
61    pub fn set_image<P: Into<PathBuf>>(&mut self, path: P) {
62        self.path = path.into();
63        self.data = None;
64        self.renderer = None;
65        self.ready_rcv = None;
66    }
67
68    /// Set the rendering size of the image
69    ///
70    /// Size is measured in terminal cells
71    pub fn set_size<S: Into<Vec2>>(&mut self, size: S) {
72        self.size = size.into();
73        self.data = None;
74        self.renderer = None;
75        self.ready_rcv = None;
76    }
77
78    fn render_image_thread(&self) -> (Receiver<()>, ImageRenderThread) {
79        let width = self.size.x as u32;
80        let img_height = self.size.y * 2;
81        let img_reader = ImageReader::open(&self.path);
82        let (snd, rcv) = bounded::<()>(1);
83        
84        (
85            rcv,
86            thread::spawn(move || {
87                let img = img_reader?.with_guessed_format()?.decode()?;
88                let mut img_colors: Vec<Vec<(Color, Color)>> = Vec::new();
89                let img_buffer = img
90                    .adjust_contrast(50.)
91                    .resize_exact(width, img_height as u32, FilterType::Triangle)
92                    .to_rgb8();
93
94                for (cur_row, next_row) in img_buffer.rows().tuples() {
95                    let mut colors: Vec<(Color, Color)> = Vec::new();
96                    for (top_pxl, bottom_pxl) in cur_row.zip(next_row) {
97                        colors.push((
98                            Color::Rgb(bottom_pxl.0[0], bottom_pxl.0[1], bottom_pxl.0[2]),
99                            Color::Rgb(top_pxl.0[0], top_pxl.0[1], top_pxl.0[2])
100                        ));
101                    }
102
103                    img_colors.push(colors);
104                }
105
106                snd.send(()).unwrap_or_default();
107                Ok(img_colors)
108            })
109        )
110    }
111}
112
113impl View for ImageView {
114    fn draw(&self, printer: &Printer) {
115        if let Some(ref image_data) = self.data {
116            for (y, row) in image_data.iter().enumerate() {
117                for (x, style) in row.iter().enumerate() {
118                    printer.with_color(ColorStyle::from(*style), |printer| printer.print((x, y), "▄"))
119                }
120            }
121        }
122    }
123
124    fn required_size(&mut self, _: Vec2) -> Vec2 {
125        if self.minimize && self.data.is_none() && self.path.parent().is_none() {
126            Vec2::new(0, 0)
127        }
128        else {
129            self.size
130        }
131    }
132
133    fn layout(&mut self, _: Vec2) {
134        if self.data.is_none() && self.renderer.is_none() {
135            let (rcv, renderer) = self.render_image_thread();
136            self.ready_rcv = Some(rcv);
137            self.renderer = Some(renderer);
138        }
139        else if self.data.is_none() {
140            if let Some(ready_rcv) = self.ready_rcv.as_ref() {
141                if ready_rcv.is_full() {
142                    if let Some(renderer) = self.renderer.take() {
143                        if let Ok(data) = renderer.join().unwrap() {
144                            self.data = Some(data);
145                        }
146
147                        self.ready_rcv = None;
148                    }
149                }
150            }
151        }
152    }
153}