1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use crossbeam_channel::{bounded, Receiver};
use cursive_core::{
    Printer, Vec2,
    view::View,
    theme::{
        ColorStyle,
        Color
    }
};
use image::{
    error::ImageError,
    imageops::FilterType,
    io::Reader as ImageReader
};
use itertools::Itertools;
use std::{
    path::PathBuf,
    thread::{self, JoinHandle}
};

type Image = Vec<Vec<(Color, Color)>>;
type ImageResult = Result<Image, ImageError>;
type ImageRenderThread = JoinHandle<ImageResult>;


//TODO: static image view
// a view that uses a procedural macro that will generate an image
// to display at compile time

/// View that can render a low res image as text
///
/// It is highly recommended to use `buffered_backend_root()` to create the cursive root, otherwise this view may mess up the terminal color scheme
pub struct ImageView {
    data: Option<Image>,
    renderer: Option<ImageRenderThread>,
    ready_rcv: Option<Receiver<()>>,
    width: usize,
    height: usize,
    path: PathBuf,
    minimize: bool
}

impl ImageView {
    /// Create a new empty `ImageView`
    pub fn new(width: usize, height: usize) -> ImageView {
        ImageView {
            data: None,
            renderer: None,
            ready_rcv: None,
            width,
            height,
            path: PathBuf::new(),
            minimize: false
        }
    }

    /// Set the image to render
    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;
    }

    /// Set the image to render
    ///
    /// Chainable version
    pub fn image<P: Into<PathBuf>>(mut self, path: P) -> ImageView {
        self.set_image(path);
        self
    }

    /// Minimize the view when empty?
    ///
    /// Chainable version
    pub fn minimize(mut self, minimize: bool) -> Self {
        self.set_minimize(minimize);
        self
    }

    /// Minimize the view when empty?
    pub fn set_minimize(&mut self, minimize: bool) {
        self.minimize = minimize;
    }

    /// Set the rendering size of the image
    ///
    /// Size is measured in terminal cells
    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;
    }

    /// Gets the path of the image
    pub fn get_path(&self) -> &PathBuf { &self.path }

    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;
                    }
                }
            }
        }
    }
}