1use std::{
4 collections::hash_map::DefaultHasher,
5 hash::{Hash, Hasher},
6};
7
8use image::{DynamicImage, ImageBuffer, Rgba, imageops};
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, ResizeEncodeRender, 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(&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(&self, area: Rect, buf: &mut Buffer) {
54 let inner: &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 size_for(&self, resize: Resize, area: Rect) -> Rect {
129 resize.render_area(&self.source, self.font_size, area)
130 }
131
132 pub fn protocol_type(&self) -> &StatefulProtocolType {
133 &self.protocol_type
134 }
135
136 pub fn protocol_type_owned(self) -> StatefulProtocolType {
137 self.protocol_type
138 }
139
140 pub fn last_encoding_result(&mut self) -> Option<Result<()>> {
142 self.last_encoding_result.take()
143 }
144
145 pub fn background_color(&self) -> Rgba<u8> {
147 self.source.background_color
148 }
149
150 fn last_encoding_area(&self) -> Rect {
151 self.protocol_type.inner_trait().area()
152 }
153}
154
155impl ResizeEncodeRender for StatefulProtocol {
156 fn resize_encode(&mut self, resize: &Resize, area: Rect) {
157 if area.width == 0 || area.height == 0 {
158 return;
159 }
160
161 let img = resize.resize(&self.source, self.font_size, area, self.background_color());
162
163 let result = self
165 .protocol_type
166 .inner_trait_mut()
167 .resize_encode(img, area);
168
169 if result.is_ok() {
170 self.hash = self.source.hash
171 }
172
173 self.last_encoding_result = Some(result)
174 }
175
176 fn render(&mut self, area: Rect, buf: &mut Buffer) {
177 self.protocol_type.inner_trait_mut().render(area, buf);
178 }
179
180 fn needs_resize(&self, resize: &Resize, area: Rect) -> Option<Rect> {
181 resize.needs_resize(
182 &self.source,
183 self.font_size,
184 self.last_encoding_area(),
185 area,
186 self.source.hash != self.hash,
187 )
188 }
189}
190#[derive(Clone)]
191pub struct ImageSource {
207 pub image: DynamicImage,
209 pub desired: Rect,
211 pub hash: u64,
213 pub background_color: Rgba<u8>,
215}
216
217impl ImageSource {
218 pub fn new(
220 mut image: DynamicImage,
221 font_size: FontSize,
222 background_color: Rgba<u8>,
223 ) -> ImageSource {
224 let desired =
225 ImageSource::round_pixel_size_to_cells(image.width(), image.height(), font_size);
226
227 let mut state = DefaultHasher::new();
228 image.as_bytes().hash(&mut state);
229 let hash = state.finish();
230
231 if background_color.0[3] != 0 {
233 let mut bg: DynamicImage =
234 ImageBuffer::from_pixel(image.width(), image.height(), background_color).into();
235 imageops::overlay(&mut bg, &image, 0, 0);
236 image = bg;
237 }
238
239 ImageSource {
240 image,
241 desired,
242 hash,
243 background_color,
244 }
245 }
246 pub fn round_pixel_size_to_cells(
248 img_width: u32,
249 img_height: u32,
250 (char_width, char_height): FontSize,
251 ) -> Rect {
252 let width = (img_width as f32 / char_width as f32).ceil() as u16;
253 let height = (img_height as f32 / char_height as f32).ceil() as u16;
254 Rect::new(0, 0, width, height)
255 }
256}