1use failure::Error;
2use image::{imageops::resize, imageops::FilterType, RgbaImage};
3use std::cmp::{max, min};
4use tui::buffer::Buffer;
5use tui::layout::{Alignment, Rect};
6use tui::style::{Color, Style};
7use tui::widgets::{Block, Widget};
8
9pub enum ColorMode {
10 Luma,
11 Rgb,
12}
13
14const BLOCK_LIGHT: char = '\u{2591}';
15const BLOCK_MEDIUM: char = '\u{2592}';
16const BLOCK_DARK: char = '\u{2593}';
17const BLOCK_FULL: char = '\u{2588}';
18
19pub struct Image<'a> {
21 block: Option<Block<'a>>,
23 style: Style,
25 img: Option<RgbaImage>,
27 img_fn: Option<Box<dyn Fn(usize, usize) -> Result<RgbaImage, Error>>>,
29 color_mode: ColorMode,
31 alignment: Alignment,
33}
34
35impl<'a> Image<'a> {
36 pub fn with_img(img: RgbaImage) -> Image<'a> {
38 Image {
39 block: None,
40 style: Default::default(),
41 img: Some(img),
42 img_fn: None,
43 color_mode: ColorMode::Luma,
44 alignment: Alignment::Center,
45 }
46 }
47
48 pub fn with_img_fn(
50 img_fn: impl Fn(usize, usize) -> Result<RgbaImage, Error> + 'static,
51 ) -> Image<'a> {
52 Image {
53 block: None,
54 style: Default::default(),
55 img: None,
56 img_fn: Some(Box::new(img_fn)),
57 color_mode: ColorMode::Luma,
58 alignment: Alignment::Center,
59 }
60 }
61
62 pub fn block(mut self, block: Block<'a>) -> Image<'a> {
64 self.block = Some(block);
65 self
66 }
67
68 pub fn color_mode(mut self, color_mode: ColorMode) -> Image<'a> {
70 self.color_mode = color_mode;
71 self
72 }
73
74 pub fn style(mut self, style: Style) -> Image<'a> {
76 self.style = style;
77 self
78 }
79
80 pub fn alignment(mut self, alignment: Alignment) -> Image<'a> {
82 self.alignment = alignment;
83 self
84 }
85
86 fn draw_img(&self, area: Rect, buf: &mut Buffer, img: &RgbaImage) {
87 let bg_rgb = match self.style.bg {
89 Some(Color::Black) => vec![0f32, 0f32, 0f32],
90 Some(Color::White) => vec![1f32, 1f32, 1f32],
91 Some(Color::Rgb(r, g, b)) => {
92 vec![r as f32 / 255f32, g as f32 / 255f32, b as f32 / 255f32]
93 }
94 _ => vec![0f32, 0f32, 0f32],
95 };
96
97 let ox = max(
100 0,
101 min(
102 area.width as i32 - 1,
103 match self.alignment {
104 Alignment::Center => (area.width as i32 - img.width() as i32) / 2i32,
105 Alignment::Left => 0i32,
106 Alignment::Right => area.width as i32 - img.width() as i32,
107 },
108 ),
109 ) as u16;
110 let oy = max(
111 0,
112 min(
113 area.height - 1,
114 (area.height - (img.height() / 2) as u16) / 2,
115 ),
116 ) as u16;
117
118 for y in oy..(oy + min((img.height() / 2) as u16, area.height - 1)) {
121 for x in ox..min(ox + img.width() as u16, area.width - 1) {
122 let p = img.get_pixel((x - ox) as u32, 2 * (y - oy) as u32);
123
124 let a = p[3] as f32 / 255.0;
126 let r = p[0] as f32 * a / 255.0 + bg_rgb[0] * (1f32 - a);
127 let g = p[1] as f32 * a / 255.0 + bg_rgb[1] * (1f32 - a);
128 let b = p[2] as f32 * a / 255.0 + bg_rgb[2] * (1f32 - a);
129
130 let cell = buf.get_mut(area.left() + x, area.top() + y);
131
132 match self.color_mode {
133 ColorMode::Luma => {
134 let luma = r * 0.3 + g * 0.59 + b * 0.11;
135 let luma_u8 = (5.0 * luma) as u8;
136 if luma_u8 == 0 {
137 continue;
138 }
139
140 cell.set_char(match luma_u8 {
141 1 => BLOCK_LIGHT,
142 2 => BLOCK_MEDIUM,
143 3 => BLOCK_DARK,
144 _ => BLOCK_FULL,
145 });
146 }
147 ColorMode::Rgb => {
148 cell.set_char(BLOCK_FULL).set_fg(Color::Rgb(
149 (255.0 * r) as u8,
150 (255.0 * g) as u8,
151 (255.0 * b) as u8,
152 ));
153 }
154 }
155 }
156 }
157 }
158}
159
160impl<'a> Widget for Image<'a> {
161 fn render(mut self, area: Rect, buf: &mut Buffer) {
162 let area = match self.block.take() {
163 Some(b) => {
164 let inner_area = b.inner(area);
165 b.render(area, buf);
166 inner_area
167 }
168 None => area,
169 };
170
171 if area.width < 1 || area.height < 1 {
172 return;
173 }
174
175 buf.set_style(area, self.style);
176
177 if let Some(ref img) = self.img {
178 if img.width() > area.width as u32 || img.height() / 2 > area.height as u32 {
179 let scaled = resize(
180 img,
181 area.width as u32,
182 2 * area.height as u32,
183 FilterType::Nearest,
184 );
185 self.draw_img(area, buf, &scaled)
186 } else {
187 self.draw_img(area, buf, img)
188 }
189 } else if let Some(ref img_fn) = self.img_fn {
190 if let Ok(img) = img_fn(area.width as usize, 2 * area.height as usize) {
191 self.draw_img(area, buf, &img);
192 }
193 }
194 }
195}