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