Skip to main content

sixel_image/
sixel_serializer.rs

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                // end of row
126                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 index inside cropped selection, or as part of total if not cropping
225    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}