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


//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
#[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 {
    /// 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
    #[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;
    }

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

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