1use std::{
4 collections::hash_map::DefaultHasher,
5 hash::{Hash, Hasher},
6};
7
8use image::{imageops, DynamicImage, ImageBuffer, Rgba};
9use ratatui::{buffer::Buffer, layout::Rect};
10
11use self::{
12 halfblocks::Halfblocks,
13 iterm2::Iterm2,
14 kitty::{Kitty, StatefulKitty},
15 sixel::Sixel,
16};
17use crate::{FontSize, Result};
18
19use super::Resize;
20
21pub mod halfblocks;
22pub mod iterm2;
23pub mod kitty;
24pub mod sixel;
25
26trait ProtocolTrait: Send + Sync {
27 fn render(&mut self, area: Rect, buf: &mut Buffer);
29
30 #[allow(dead_code)]
32 fn area(&self) -> Rect;
33}
34
35trait StatefulProtocolTrait: ProtocolTrait {
36 fn resize_encode(&mut self, img: DynamicImage, area: Rect) -> Result<()>;
41}
42
43#[derive(Clone)]
45pub enum Protocol {
46 Halfblocks(Halfblocks),
47 Sixel(Sixel),
48 Kitty(Kitty),
49 ITerm2(Iterm2),
50}
51
52impl Protocol {
53 pub(crate) fn render(&mut self, area: Rect, buf: &mut Buffer) {
54 let inner: &mut dyn ProtocolTrait = match self {
55 Self::Halfblocks(halfblocks) => halfblocks,
56 Self::Sixel(sixel) => sixel,
57 Self::Kitty(kitty) => kitty,
58 Self::ITerm2(iterm2) => iterm2,
59 };
60 inner.render(area, buf);
61 }
62 pub fn area(&self) -> Rect {
63 let inner: &dyn ProtocolTrait = match self {
64 Self::Halfblocks(halfblocks) => halfblocks,
65 Self::Sixel(sixel) => sixel,
66 Self::Kitty(kitty) => kitty,
67 Self::ITerm2(iterm2) => iterm2,
68 };
69 inner.area()
70 }
71}
72
73pub struct StatefulProtocol {
78 source: ImageSource,
79 font_size: FontSize,
80 hash: u64,
81 protocol_type: StatefulProtocolType,
82 last_encoding_result: Option<Result<()>>,
83}
84
85#[derive(Clone)]
86pub enum StatefulProtocolType {
87 Halfblocks(Halfblocks),
88 Sixel(Sixel),
89 Kitty(StatefulKitty),
90 ITerm2(Iterm2),
91}
92
93impl StatefulProtocolType {
94 fn inner_trait(&self) -> &dyn StatefulProtocolTrait {
95 match self {
96 Self::Halfblocks(halfblocks) => halfblocks,
97 Self::Sixel(sixel) => sixel,
98 Self::Kitty(kitty) => kitty,
99 Self::ITerm2(iterm2) => iterm2,
100 }
101 }
102 fn inner_trait_mut(&mut self) -> &mut dyn StatefulProtocolTrait {
103 match self {
104 Self::Halfblocks(halfblocks) => halfblocks,
105 Self::Sixel(sixel) => sixel,
106 Self::Kitty(kitty) => kitty,
107 Self::ITerm2(iterm2) => iterm2,
108 }
109 }
110}
111
112impl StatefulProtocol {
113 pub fn new(
114 source: ImageSource,
115 font_size: FontSize,
116 protocol_type: StatefulProtocolType,
117 ) -> Self {
118 Self {
119 source,
120 font_size,
121 hash: u64::default(),
122 protocol_type,
123 last_encoding_result: None,
124 }
125 }
126
127 pub fn protocol_type(&self) -> &StatefulProtocolType {
128 &self.protocol_type
129 }
130
131 pub fn protocol_type_owned(self) -> StatefulProtocolType {
132 self.protocol_type
133 }
134
135 pub fn last_encoding_result(&mut self) -> Option<Result<()>> {
137 self.last_encoding_result.take()
138 }
139
140 pub fn background_color(&self) -> Rgba<u8> {
142 self.source.background_color
143 }
144
145 pub fn resize_encode_render(&mut self, resize: &Resize, area: Rect, buf: &mut Buffer) {
149 if let Some(rect) = self.needs_resize(resize, area) {
150 self.resize_encode(resize, rect);
151 }
152 self.render(area, buf);
153 }
154
155 pub fn needs_resize(&mut self, resize: &Resize, area: Rect) -> Option<Rect> {
161 resize.needs_resize(
162 &self.source,
163 self.font_size,
164 self.last_encoding_area(),
165 area,
166 self.source.hash != self.hash,
167 )
168 }
169
170 pub fn resize_encode(&mut self, resize: &Resize, area: Rect) {
175 if area.width == 0 || area.height == 0 {
176 return;
177 }
178
179 let img = resize.resize(&self.source, self.font_size, area, self.background_color());
180
181 let result = self
183 .protocol_type
184 .inner_trait_mut()
185 .resize_encode(img, area);
186
187 if result.is_ok() {
188 self.hash = self.source.hash
189 }
190
191 self.last_encoding_result = Some(result)
192 }
193
194 pub fn render(&mut self, area: Rect, buf: &mut Buffer) {
196 self.protocol_type.inner_trait_mut().render(area, buf);
197 }
198
199 pub fn size_for(&self, resize: &Resize, area: Rect) -> Rect {
200 resize.render_area(&self.source, self.font_size, area)
201 }
202
203 fn last_encoding_area(&self) -> Rect {
204 self.protocol_type.inner_trait().area()
205 }
206}
207
208#[derive(Clone)]
209pub struct ImageSource {
225 pub image: DynamicImage,
227 pub desired: Rect,
229 pub hash: u64,
231 pub background_color: Rgba<u8>,
233}
234
235impl ImageSource {
236 pub fn new(
238 mut image: DynamicImage,
239 font_size: FontSize,
240 background_color: Rgba<u8>,
241 ) -> ImageSource {
242 let desired =
243 ImageSource::round_pixel_size_to_cells(image.width(), image.height(), font_size);
244
245 let mut state = DefaultHasher::new();
246 image.as_bytes().hash(&mut state);
247 let hash = state.finish();
248
249 if background_color.0[3] != 0 {
251 let mut bg: DynamicImage =
252 ImageBuffer::from_pixel(image.width(), image.height(), background_color).into();
253 imageops::overlay(&mut bg, &image, 0, 0);
254 image = bg;
255 }
256
257 ImageSource {
258 image,
259 desired,
260 hash,
261 background_color,
262 }
263 }
264 pub fn round_pixel_size_to_cells(
266 img_width: u32,
267 img_height: u32,
268 (char_width, char_height): FontSize,
269 ) -> Rect {
270 let width = (img_width as f32 / char_width as f32).ceil() as u16;
271 let height = (img_height as f32 / char_height as f32).ceil() as u16;
272 Rect::new(0, 0, width, height)
273 }
274}