1use std::{
4 collections::hash_map::DefaultHasher,
5 fmt::Write,
6 hash::{Hash, Hasher},
7};
8
9use image::{DynamicImage, ImageBuffer, Rgba, imageops};
10use ratatui::{
11 buffer::Buffer,
12 layout::{Rect, Size},
13};
14
15use self::{
16 halfblocks::Halfblocks,
17 iterm2::Iterm2,
18 kitty::{Kitty, StatefulKitty},
19 sixel::Sixel,
20};
21use crate::{FontSize, ResizeEncodeRender, Result};
22
23use super::Resize;
24
25pub mod halfblocks;
26pub mod iterm2;
27pub mod kitty;
28pub mod sixel;
29
30pub(crate) trait ProtocolTrait: Send + Sync {
31 fn render(&self, area: Rect, buf: &mut Buffer);
33
34 fn size(&self) -> Size;
36}
37
38trait StatefulProtocolTrait: ProtocolTrait {
39 fn resize_encode(&mut self, img: DynamicImage, size: Size) -> Result<()>;
44}
45
46#[derive(Clone)]
48pub enum Protocol {
49 Halfblocks(Halfblocks),
50 Sixel(Sixel),
51 Kitty(Kitty),
52 ITerm2(Iterm2),
53}
54
55impl Protocol {
56 pub(crate) fn render(&self, area: Rect, buf: &mut Buffer) {
57 let inner: &dyn ProtocolTrait = match self {
58 Self::Halfblocks(halfblocks) => halfblocks,
59 Self::Sixel(sixel) => sixel,
60 Self::Kitty(kitty) => kitty,
61 Self::ITerm2(iterm2) => iterm2,
62 };
63 inner.render(area, buf);
64 }
65 pub fn size(&self) -> Size {
67 let inner: &dyn ProtocolTrait = match self {
68 Self::Halfblocks(halfblocks) => halfblocks,
69 Self::Sixel(sixel) => sixel,
70 Self::Kitty(kitty) => kitty,
71 Self::ITerm2(iterm2) => iterm2,
72 };
73 inner.size()
74 }
75
76 pub fn needs_placeholder(&self, area: Rect) -> Option<Rect> {
83 let image_size = self.size();
84 if area.width < image_size.width
85 || area.height < image_size.height
86 && (matches!(self, Self::Sixel(_)) || matches!(self, Self::Halfblocks(_)))
87 {
88 let mut placeholder_area = area;
89 placeholder_area.width = placeholder_area.width.min(image_size.width);
90 placeholder_area.height = placeholder_area.height.min(image_size.height);
91 return Some(placeholder_area);
92 }
93 None
95 }
96}
97
98pub struct StatefulProtocol {
103 source: ImageSource,
104 font_size: FontSize,
105 hash: u64,
106 protocol_type: StatefulProtocolType,
107 last_encoding_result: Option<Result<()>>,
108}
109
110#[derive(Clone)]
111pub enum StatefulProtocolType {
112 Halfblocks(Halfblocks),
113 Sixel(Sixel),
114 Kitty(StatefulKitty),
115 ITerm2(Iterm2),
116}
117
118impl StatefulProtocolType {
119 fn inner_trait(&self) -> &dyn StatefulProtocolTrait {
120 match self {
121 Self::Halfblocks(halfblocks) => halfblocks,
122 Self::Sixel(sixel) => sixel,
123 Self::Kitty(kitty) => kitty,
124 Self::ITerm2(iterm2) => iterm2,
125 }
126 }
127 fn inner_trait_mut(&mut self) -> &mut dyn StatefulProtocolTrait {
128 match self {
129 Self::Halfblocks(halfblocks) => halfblocks,
130 Self::Sixel(sixel) => sixel,
131 Self::Kitty(kitty) => kitty,
132 Self::ITerm2(iterm2) => iterm2,
133 }
134 }
135}
136
137impl StatefulProtocol {
138 pub fn new(
139 image: DynamicImage,
140 font_size: FontSize,
141 background_color: Option<Rgba<u8>>,
142 protocol_type: StatefulProtocolType,
143 ) -> Self {
144 let source = ImageSource::new(image, font_size, background_color);
145 Self {
146 source,
147 font_size,
148 hash: u64::default(),
149 protocol_type,
150 last_encoding_result: None,
151 }
152 }
153
154 pub fn size_for(&self, resize: Resize, size: Size) -> Size {
156 resize.size_for(&self.source.image, self.font_size, size)
157 }
158
159 pub fn protocol_type(&self) -> &StatefulProtocolType {
160 &self.protocol_type
161 }
162
163 pub fn protocol_type_owned(self) -> StatefulProtocolType {
164 self.protocol_type
165 }
166
167 pub fn last_encoding_result(&mut self) -> Option<Result<()>> {
169 self.last_encoding_result.take()
170 }
171
172 pub fn background_color(&self) -> Option<Rgba<u8>> {
174 self.source.background_color
175 }
176
177 fn last_encoding_area(&self) -> Size {
178 self.protocol_type.inner_trait().size()
179 }
180}
181
182impl ResizeEncodeRender for StatefulProtocol {
183 fn resize_encode(&mut self, resize: &Resize, size: Size) {
184 if size.width == 0 || size.height == 0 {
185 return;
186 }
187
188 let img = resize.resize(
189 &self.source.image,
190 self.font_size,
191 size,
192 self.background_color(),
193 );
194
195 let result = self
197 .protocol_type
198 .inner_trait_mut()
199 .resize_encode(img, size);
200
201 if result.is_ok() {
202 self.hash = self.source.hash
203 }
204
205 self.last_encoding_result = Some(result)
206 }
207
208 fn render(&mut self, area: Rect, buf: &mut Buffer) {
209 self.protocol_type.inner_trait_mut().render(area, buf);
210 }
211
212 fn needs_resize(&self, resize: &Resize, size: Size) -> Option<Size> {
213 resize.needs_resize(
214 &self.source.image,
215 Some(self.source.desired),
216 self.font_size,
217 Some(self.last_encoding_area()),
218 size,
219 self.source.hash != self.hash,
220 )
221 }
222}
223
224#[derive(Clone)]
225struct ImageSource {
241 pub image: DynamicImage,
243 pub desired: Size,
245 pub hash: u64,
247 pub background_color: Option<Rgba<u8>>,
249}
250
251impl ImageSource {
252 pub fn new(
254 mut image: DynamicImage,
255 font_size: FontSize,
256 background_color: Option<Rgba<u8>>,
257 ) -> ImageSource {
258 let desired = Resize::round_pixel_size_to_cells(image.width(), image.height(), font_size);
259
260 let mut state = DefaultHasher::new();
261 image.as_bytes().hash(&mut state);
262 let hash = state.finish();
263
264 if let Some(background_color) = background_color
266 && background_color.0[3] != 0
267 {
268 let mut bg: DynamicImage =
269 ImageBuffer::from_pixel(image.width(), image.height(), background_color).into();
270 imageops::overlay(&mut bg, &image, 0, 0);
271 image = bg;
272 }
273
274 ImageSource {
275 image,
276 desired,
277 hash,
278 background_color,
279 }
280 }
281}
282
283pub(crate) fn clear_area(data: &mut String, escape: &str, width: u16, height: u16) {
288 if height == 1 {
289 write!(data, "{escape}[{width}X").unwrap();
291 } else {
292 for _ in 0..height {
293 write!(data, "{escape}[{width}X{escape}[1B").unwrap();
294 }
295 write!(data, "{escape}[{height}A").unwrap();
296 }
297}