cursive_extras/views/
image_view.rs1use 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#[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 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 #[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 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}