1use std::collections::{BTreeMap, HashMap};
2
3use crate::{Pixel, SixelColor, DCS, RA};
4
5pub struct SixelSerializer<'a> {
6 dcs: &'a DCS,
7 ra: &'a Option<RA>,
8 color_registers: &'a BTreeMap<u16, SixelColor>,
9 pixels: &'a Vec<Vec<Pixel>>,
10}
11
12impl<'a> SixelSerializer<'a> {
13 pub fn new(
14 dcs: &'a DCS,
15 ra: &'a Option<RA>,
16 color_registers: &'a BTreeMap<u16, SixelColor>,
17 pixels: &'a Vec<Vec<Pixel>>,
18 ) -> Self {
19 SixelSerializer {
20 dcs,
21 ra,
22 color_registers,
23 pixels,
24 }
25 }
26 pub fn serialize(&self) -> String {
27 let serialized_image = String::new();
28 let serialized_image = self.serialize_dcs(serialized_image);
29 let serialized_image = self.serialize_ra(serialized_image);
30 let serialized_image = self.serialize_color_registers(serialized_image);
31 let serialized_image = self.serialize_pixels(serialized_image, None, None, None, None);
32 let serialized_image = self.serialize_end_event(serialized_image);
33 serialized_image
34 }
35 pub fn serialize_range(
36 &self,
37 start_x_index: usize,
38 start_y_index: usize,
39 width: usize,
40 height: usize,
41 ) -> String {
42 let serialized_image = String::new();
43 let serialized_image = self.serialize_dcs(serialized_image);
44 let serialized_image = self.serialize_ra(serialized_image);
45 let serialized_image = self.serialize_color_registers(serialized_image);
46 let serialized_image = self.serialize_pixels(
47 serialized_image,
48 Some(start_x_index),
49 Some(start_y_index),
50 Some(width),
51 Some(height),
52 );
53 let serialized_image = self.serialize_end_event(serialized_image);
54 serialized_image
55 }
56 fn serialize_dcs(&self, mut append_to: String) -> String {
57 append_to.push_str(&format!(
58 "\u{1b}P{mp};{bg};0q",
59 mp = self.dcs.macro_parameter,
60 bg = if self.dcs.transparent_bg { 1 } else { 0 }
61 ));
62 append_to
63 }
64 fn serialize_ra(&self, mut append_to: String) -> String {
65 if let Some(ra) = self.ra {
66 if let (Some(ph), Some(pv)) = (ra.ph, ra.pv) {
67 append_to.push_str(&format!(
68 "\"{pan};{pad};{ph};{pv}",
69 pan = ra.pan,
70 pad = ra.pad,
71 ph = ph,
72 pv = pv
73 ));
74 } else {
75 append_to.push_str(&format!("\"{pan};{pad};", pan = ra.pan, pad = ra.pad));
76 }
77 }
78 append_to
79 }
80 fn serialize_color_registers(&self, mut append_to: String) -> String {
81 for (color_register, sixel_color_code) in &*self.color_registers {
82 match sixel_color_code {
83 SixelColor::Hsl(x, y, z) => {
84 append_to.push_str(&format!("#{};1;{};{};{}", color_register, x, y, z))
85 }
86 SixelColor::Rgb(x, y, z) => {
87 append_to.push_str(&format!("#{};2;{};{};{}", color_register, x, y, z))
88 }
89 }
90 }
91 append_to
92 }
93 fn serialize_pixels(
94 &self,
95 mut append_to: String,
96 start_x_index: Option<usize>,
97 start_y_index: Option<usize>,
98 width: Option<usize>,
99 height: Option<usize>,
100 ) -> String {
101 let start_y_index = start_y_index.unwrap_or(0);
102 let start_x_index = start_x_index.unwrap_or(0);
103 let max_x_index = width.map(|width| (start_x_index + width).saturating_sub(1));
104 let max_y_index = height.map(|height| (start_y_index + height).saturating_sub(1));
105 let mut current_line_index = start_y_index;
106 let mut current_column_index = start_x_index;
107 let mut color_index_to_sixel_data_string: BTreeMap<u16, String> = BTreeMap::new();
108 let max_lines = std::cmp::min(height.unwrap_or(self.pixels.len()), self.pixels.len());
109 loop {
110 let relative_column_index = current_column_index - start_x_index;
111 let relative_line_index = current_line_index - start_y_index;
112 let continue_serializing = SixelColumn::new(
113 current_line_index,
114 current_column_index,
115 max_x_index,
116 max_y_index,
117 &self.pixels,
118 )
119 .map(|mut sixel_column| {
120 sixel_column
121 .serialize(&mut color_index_to_sixel_data_string, relative_column_index);
122 current_column_index += 1;
123 })
124 .or_else(|| {
125 SixelLine::new(
127 &mut append_to,
128 relative_line_index,
129 relative_column_index,
130 max_lines,
131 )
132 .as_mut()
133 .map(|sixel_line| {
134 sixel_line.serialize(&mut color_index_to_sixel_data_string);
135 current_line_index += 6;
136 current_column_index = start_x_index;
137 })
138 })
139 .is_some();
140 if !continue_serializing {
141 break;
142 }
143 }
144 append_to
145 }
146 fn serialize_end_event(&self, mut append_to: String) -> String {
147 append_to.push_str("\u{1b}\\");
148 append_to
149 }
150}
151
152struct SixelColumn {
153 color_index_to_byte: HashMap<u16, u8>,
154}
155
156impl SixelColumn {
157 pub fn new(
158 absolute_line_index: usize,
159 absolute_column_index: usize,
160 max_x_index: Option<usize>,
161 max_y_index: Option<usize>,
162 pixels: &Vec<Vec<Pixel>>,
163 ) -> Option<Self> {
164 let mut empty_rows = 0;
165 let mut color_index_to_byte = HashMap::new();
166 if let Some(max_x_index) = max_x_index {
167 if max_x_index < absolute_column_index {
168 return None;
169 }
170 }
171 if let Some(max_y_index) = max_y_index {
172 if max_y_index < absolute_line_index {
173 return None;
174 }
175 }
176 let pixels_in_column = max_y_index
177 .map(|max_y_index| {
178 std::cmp::min(max_y_index.saturating_sub(absolute_line_index) + 1, 6)
179 })
180 .unwrap_or(6);
181 for i in 0..pixels_in_column {
182 let pixel_at_current_position = pixels
183 .get(absolute_line_index + i)
184 .map(|current_line| current_line.get(absolute_column_index));
185 match pixel_at_current_position {
186 Some(Some(pixel)) => {
187 if pixel.on {
188 let color_char = color_index_to_byte.entry(pixel.color).or_insert(0);
189 let mask = 1 << i;
190 *color_char += mask;
191 }
192 }
193 _ => empty_rows += 1,
194 }
195 }
196 let row_ended = empty_rows == 6;
197 if row_ended {
198 None
199 } else {
200 Some(SixelColumn {
201 color_index_to_byte,
202 })
203 }
204 }
205 fn serialize(
206 &mut self,
207 color_index_to_character_string: &mut BTreeMap<u16, String>,
208 current_index: usize,
209 ) {
210 for (color_index, char_representation) in self.color_index_to_byte.iter_mut() {
211 let color_chars = color_index_to_character_string
212 .entry(*color_index)
213 .or_insert(String::new());
214 for _ in color_chars.len()..current_index {
215 color_chars.push('?');
216 }
217 color_chars.push(char::from(*char_representation + 0x3f));
218 }
219 }
220}
221
222struct SixelLine<'a> {
223 append_to: &'a mut String,
224 relative_line_index: usize, line_length: usize,
226}
227
228impl<'a> SixelLine<'a> {
229 pub fn new(
230 append_to: &'a mut String,
231 relative_line_index: usize,
232 relative_column_index: usize,
233 max_lines: usize,
234 ) -> Option<Self> {
235 if relative_line_index >= max_lines {
236 None
237 } else {
238 Some(SixelLine {
239 append_to,
240 relative_line_index,
241 line_length: relative_column_index,
242 })
243 }
244 }
245 pub fn serialize(&mut self, color_index_to_character_string: &'a mut BTreeMap<u16, String>) {
246 let mut is_first = true;
247 if self.relative_line_index != 0 {
248 self.append_to.push('-');
249 }
250 for (color_index, sixel_chars) in color_index_to_character_string.iter_mut() {
251 if !is_first {
252 self.append_to.push('$');
253 }
254 is_first = false;
255 self.pad_sixel_string(sixel_chars, self.line_length);
256 self.serialize_color_introducer(color_index);
257 self.group_identical_characters(sixel_chars);
258 }
259 color_index_to_character_string.clear();
260 }
261 fn serialize_one_or_more_sixel_characters(
262 &mut self,
263 character_occurrences: usize,
264 character: char,
265 ) {
266 if character_occurrences > 2 {
267 self.append_to
268 .push_str(&format!("!{}{}", character_occurrences, character));
269 } else {
270 for _ in 0..character_occurrences {
271 self.append_to.push(character);
272 }
273 }
274 }
275 fn group_identical_characters(&mut self, sixel_chars: &mut String) {
276 let mut current_character = None;
277 let mut current_character_occurrences = 0;
278 for character in sixel_chars.drain(..) {
279 if current_character.is_none() {
280 current_character = Some(character);
281 current_character_occurrences = 1;
282 } else if current_character == Some(character) {
283 current_character_occurrences += 1;
284 } else {
285 self.serialize_one_or_more_sixel_characters(
286 current_character_occurrences,
287 current_character.unwrap(),
288 );
289 current_character_occurrences = 1;
290 current_character = Some(character);
291 }
292 }
293 self.serialize_one_or_more_sixel_characters(
294 current_character_occurrences,
295 current_character.unwrap(),
296 );
297 }
298 fn serialize_color_introducer(&mut self, color_index: &u16) {
299 self.append_to.push_str(&format!("#{}", color_index));
300 }
301 fn pad_sixel_string(&self, sixel_chars: &mut String, desired_length: usize) {
302 for _ in sixel_chars.len()..desired_length {
303 sixel_chars.push('?');
304 }
305 }
306}