use std::collections::{BTreeMap, HashMap};
use crate::{Pixel, SixelColor, DCS, RA};
pub struct SixelSerializer<'a> {
dcs: &'a DCS,
ra: &'a Option<RA>,
color_registers: &'a BTreeMap<u16, SixelColor>,
pixels: &'a Vec<Vec<Pixel>>,
}
impl<'a> SixelSerializer<'a> {
pub fn new(
dcs: &'a DCS,
ra: &'a Option<RA>,
color_registers: &'a BTreeMap<u16, SixelColor>,
pixels: &'a Vec<Vec<Pixel>>,
) -> Self {
SixelSerializer {
dcs,
ra,
color_registers,
pixels,
}
}
pub fn serialize(&self) -> String {
let serialized_image = String::new();
let serialized_image = self.serialize_dcs(serialized_image);
let serialized_image = self.serialize_ra(serialized_image);
let serialized_image = self.serialize_color_registers(serialized_image);
let serialized_image = self.serialize_pixels(serialized_image, None, None, None, None);
let serialized_image = self.serialize_end_event(serialized_image);
serialized_image
}
pub fn serialize_range(
&self,
start_x_index: usize,
start_y_index: usize,
width: usize,
height: usize,
) -> String {
let serialized_image = String::new();
let serialized_image = self.serialize_dcs(serialized_image);
let serialized_image = self.serialize_ra(serialized_image);
let serialized_image = self.serialize_color_registers(serialized_image);
let serialized_image = self.serialize_pixels(
serialized_image,
Some(start_x_index),
Some(start_y_index),
Some(width),
Some(height),
);
let serialized_image = self.serialize_end_event(serialized_image);
serialized_image
}
fn serialize_dcs(&self, mut append_to: String) -> String {
append_to.push_str(&format!(
"\u{1b}P{mp};{bg};0q",
mp = self.dcs.macro_parameter,
bg = if self.dcs.transparent_bg { 1 } else { 0 }
));
append_to
}
fn serialize_ra(&self, mut append_to: String) -> String {
if let Some(ra) = self.ra {
if let (Some(ph), Some(pv)) = (ra.ph, ra.pv) {
append_to.push_str(&format!(
"\"{pan};{pad};{ph};{pv}",
pan = ra.pan,
pad = ra.pad,
ph = ph,
pv = pv
));
} else {
append_to.push_str(&format!("\"{pan};{pad};", pan = ra.pan, pad = ra.pad));
}
}
append_to
}
fn serialize_color_registers(&self, mut append_to: String) -> String {
for (color_register, sixel_color_code) in &*self.color_registers {
match sixel_color_code {
SixelColor::Hsl(x, y, z) => {
append_to.push_str(&format!("#{};1;{};{};{}", color_register, x, y, z))
}
SixelColor::Rgb(x, y, z) => {
append_to.push_str(&format!("#{};2;{};{};{}", color_register, x, y, z))
}
}
}
append_to
}
fn serialize_pixels(
&self,
mut append_to: String,
start_x_index: Option<usize>,
start_y_index: Option<usize>,
width: Option<usize>,
height: Option<usize>,
) -> String {
let start_y_index = start_y_index.unwrap_or(0);
let start_x_index = start_x_index.unwrap_or(0);
let max_x_index = width.map(|width| (start_x_index + width).saturating_sub(1));
let max_y_index = height.map(|height| (start_y_index + height).saturating_sub(1));
let mut current_line_index = start_y_index;
let mut current_column_index = start_x_index;
let mut color_index_to_sixel_data_string: BTreeMap<u16, String> = BTreeMap::new();
let max_lines = std::cmp::min(height.unwrap_or(self.pixels.len()), self.pixels.len());
loop {
let relative_column_index = current_column_index - start_x_index;
let relative_line_index = current_line_index - start_y_index;
let continue_serializing = SixelColumn::new(
current_line_index,
current_column_index,
max_x_index,
max_y_index,
&self.pixels,
)
.map(|mut sixel_column| {
sixel_column
.serialize(&mut color_index_to_sixel_data_string, relative_column_index);
current_column_index += 1;
})
.or_else(|| {
SixelLine::new(
&mut append_to,
relative_line_index,
relative_column_index,
max_lines,
)
.as_mut()
.map(|sixel_line| {
sixel_line.serialize(&mut color_index_to_sixel_data_string);
current_line_index += 6;
current_column_index = start_x_index;
})
})
.is_some();
if !continue_serializing {
break;
}
}
append_to
}
fn serialize_end_event(&self, mut append_to: String) -> String {
append_to.push_str("\u{1b}\\");
append_to
}
}
struct SixelColumn {
color_index_to_byte: HashMap<u16, u8>,
}
impl SixelColumn {
pub fn new(
absolute_line_index: usize,
absolute_column_index: usize,
max_x_index: Option<usize>,
max_y_index: Option<usize>,
pixels: &Vec<Vec<Pixel>>,
) -> Option<Self> {
let mut empty_rows = 0;
let mut color_index_to_byte = HashMap::new();
if let Some(max_x_index) = max_x_index {
if max_x_index < absolute_column_index {
return None;
}
}
if let Some(max_y_index) = max_y_index {
if max_y_index < absolute_line_index {
return None;
}
}
let pixels_in_column = max_y_index
.map(|max_y_index| {
std::cmp::min(max_y_index.saturating_sub(absolute_line_index) + 1, 6)
})
.unwrap_or(6);
for i in 0..pixels_in_column {
let pixel_at_current_position = pixels
.get(absolute_line_index + i)
.map(|current_line| current_line.get(absolute_column_index));
match pixel_at_current_position {
Some(Some(pixel)) => {
if pixel.on {
let color_char = color_index_to_byte.entry(pixel.color).or_insert(0);
let mask = 1 << i;
*color_char += mask;
}
}
_ => empty_rows += 1,
}
}
let row_ended = empty_rows == 6;
if row_ended {
None
} else {
Some(SixelColumn {
color_index_to_byte,
})
}
}
fn serialize(
&mut self,
color_index_to_character_string: &mut BTreeMap<u16, String>,
current_index: usize,
) {
for (color_index, char_representation) in self.color_index_to_byte.iter_mut() {
let color_chars = color_index_to_character_string
.entry(*color_index)
.or_insert(String::new());
for _ in color_chars.len()..current_index {
color_chars.push('?');
}
color_chars.push(char::from(*char_representation + 0x3f));
}
}
}
struct SixelLine<'a> {
append_to: &'a mut String,
relative_line_index: usize, line_length: usize,
}
impl<'a> SixelLine<'a> {
pub fn new(
append_to: &'a mut String,
relative_line_index: usize,
relative_column_index: usize,
max_lines: usize,
) -> Option<Self> {
if relative_line_index >= max_lines {
None
} else {
Some(SixelLine {
append_to,
relative_line_index,
line_length: relative_column_index,
})
}
}
pub fn serialize(&mut self, color_index_to_character_string: &'a mut BTreeMap<u16, String>) {
let mut is_first = true;
if self.relative_line_index != 0 {
self.append_to.push('-');
}
for (color_index, sixel_chars) in color_index_to_character_string.iter_mut() {
if !is_first {
self.append_to.push('$');
}
is_first = false;
self.pad_sixel_string(sixel_chars, self.line_length);
self.serialize_color_introducer(color_index);
self.group_identical_characters(sixel_chars);
}
color_index_to_character_string.clear();
}
fn serialize_one_or_more_sixel_characters(
&mut self,
character_occurrences: usize,
character: char,
) {
if character_occurrences > 2 {
self.append_to
.push_str(&format!("!{}{}", character_occurrences, character));
} else {
for _ in 0..character_occurrences {
self.append_to.push(character);
}
}
}
fn group_identical_characters(&mut self, sixel_chars: &mut String) {
let mut current_character = None;
let mut current_character_occurrences = 0;
for character in sixel_chars.drain(..) {
if current_character.is_none() {
current_character = Some(character);
current_character_occurrences = 1;
} else if current_character == Some(character) {
current_character_occurrences += 1;
} else {
self.serialize_one_or_more_sixel_characters(
current_character_occurrences,
current_character.unwrap(),
);
current_character_occurrences = 1;
current_character = Some(character);
}
}
self.serialize_one_or_more_sixel_characters(
current_character_occurrences,
current_character.unwrap(),
);
}
fn serialize_color_introducer(&mut self, color_index: &u16) {
self.append_to.push_str(&format!("#{}", color_index));
}
fn pad_sixel_string(&self, sixel_chars: &mut String, desired_length: usize) {
for _ in sixel_chars.len()..desired_length {
sixel_chars.push('?');
}
}
}