use std::cmp::max;
use std::{fmt, mem};
use crate::config::colors::ColorRgb;
use sugarloaf::{ColorType, GraphicData, GraphicId, MAX_GRAPHIC_DIMENSIONS};
use copa::Params;
use tracing::trace;
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
struct ColorRegister(u16);
pub const MAX_COLOR_REGISTERS: usize = 1024;
const REG_TRANSPARENT: ColorRegister = ColorRegister(u16::MAX);
const MAX_COMMAND_PARAMS: usize = 5;
#[derive(Debug)]
pub enum Error {
TooBigImage {
width: usize,
height: usize,
},
InvalidColorComponent {
register: u16,
component_value: u16,
},
InvalidColorCoordinateSystem {
register: u16,
coordinate_system: u16,
},
NonExistentParser,
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::TooBigImage { width, height } => {
write!(fmt, "The image dimensions are too big ({width}, {height})")
}
Error::InvalidColorComponent {
register,
component_value,
} => {
write!(
fmt,
"Invalid color component {component_value} for register {register}"
)
}
Error::InvalidColorCoordinateSystem {
register,
coordinate_system,
} => {
write!(
fmt,
"Invalid color coordinate system {coordinate_system} for register {register}"
)
}
Error::NonExistentParser => {
write!(fmt, "Parser does not exist",)
}
}
}
}
#[derive(Debug)]
enum SixelCommand {
RepeatIntroducer,
SetRasterAttributes,
ColorIntroducer,
CarriageReturn,
NextLine,
}
#[derive(Debug)]
struct CommandParser {
command: SixelCommand,
params: [u16; MAX_COMMAND_PARAMS],
params_position: usize,
}
impl CommandParser {
fn new(command: SixelCommand) -> CommandParser {
CommandParser {
command,
params: [0; MAX_COMMAND_PARAMS],
params_position: 0,
}
}
fn put(&mut self, byte: u8) {
let pos = self.params_position;
if pos < MAX_COMMAND_PARAMS {
match byte {
b'0'..=b'9' => {
self.params[pos] = self.params[pos]
.saturating_mul(10)
.saturating_add((byte - b'0') as u16);
}
b';' => {
self.params_position += 1;
}
_ => (), }
}
}
fn finish(self, parser: &mut Parser) -> Result<(), Error> {
match self.command {
SixelCommand::RepeatIntroducer => {
parser.repeat_count = self.params[0] as usize;
}
SixelCommand::SetRasterAttributes => {
if self.params_position >= 3 {
let width = self.params[2] as usize;
let height = self.params[3] as usize;
parser.ensure_size(width, height)?;
}
}
SixelCommand::ColorIntroducer => {
let register = ColorRegister(self.params[0]);
if self.params_position >= 4 {
macro_rules! p {
($index:expr, $limit:expr) => {
match self.params[$index] {
x if x <= $limit => x,
x => {
return Err(Error::InvalidColorComponent {
register: register.0,
component_value: x,
})
}
}
};
($index:expr) => {
p!($index, 100)
};
}
let rgb = match self.params[1] {
1 => hls_to_rgb(p!(2, 360), p!(3), p!(4)),
2 => rgb(p!(2), p!(3), p!(4), 100),
x => {
return Err(Error::InvalidColorCoordinateSystem {
register: register.0,
coordinate_system: x,
})
}
};
parser.set_color_register(register, rgb);
}
parser.selected_color_register = register;
}
SixelCommand::CarriageReturn => {
parser.x = 0;
}
SixelCommand::NextLine => {
parser.x = 0;
parser.y += 6;
}
}
Ok(())
}
}
struct Sixel(u8);
impl Sixel {
#[inline]
fn new(byte: u8) -> Sixel {
debug_assert!((0x3F..=0x7E).contains(&byte));
Sixel(byte - 0x3F)
}
#[inline]
fn height(&self) -> usize {
8 - self.0.leading_zeros() as usize
}
#[inline]
fn dots(&self) -> impl Iterator<Item = bool> {
let sixel = self.0;
(0..6).map(move |position| sixel & (1 << position) != 0)
}
}
#[derive(Default, Debug)]
pub struct Parser {
command_parser: Option<CommandParser>,
width: usize,
height: usize,
pixels: Vec<ColorRegister>,
background: ColorRegister,
color_registers: Vec<ColorRgb>,
selected_color_register: ColorRegister,
repeat_count: usize,
x: usize,
y: usize,
}
impl Parser {
pub fn new(params: &Params, shared_palette: Option<Vec<ColorRgb>>) -> Parser {
trace!("Start Sixel parser");
let mut parser = Parser::default();
let ps2 = params
.iter()
.nth(1)
.and_then(|param| param.iter().next().copied())
.unwrap_or(0);
parser.background = if ps2 == 1 {
REG_TRANSPARENT
} else {
ColorRegister(0)
};
if let Some(color_registers) = shared_palette {
parser.color_registers = color_registers;
} else {
init_color_registers(&mut parser);
}
parser
}
pub fn put(&mut self, byte: u8) -> Result<(), Error> {
match byte {
b'!' => self.start_command(SixelCommand::RepeatIntroducer)?,
b'"' => self.start_command(SixelCommand::SetRasterAttributes)?,
b'#' => self.start_command(SixelCommand::ColorIntroducer)?,
b'$' => self.start_command(SixelCommand::CarriageReturn)?,
b'-' => self.start_command(SixelCommand::NextLine)?,
b'0'..=b'9' | b';' => {
if let Some(command_parser) = &mut self.command_parser {
command_parser.put(byte);
}
}
0x3F..=0x7E => self.add_sixel(Sixel::new(byte))?,
_ => {
self.finish_command()?;
}
}
Ok(())
}
#[inline]
fn start_command(&mut self, command: SixelCommand) -> Result<(), Error> {
self.finish_command()?;
self.command_parser = Some(CommandParser::new(command));
Ok(())
}
#[inline]
fn finish_command(&mut self) -> Result<(), Error> {
if let Some(command_parser) = self.command_parser.take() {
command_parser.finish(self)?;
}
Ok(())
}
fn set_color_register(&mut self, register: ColorRegister, rgb: ColorRgb) {
let register = register.0 as usize;
if register >= MAX_COLOR_REGISTERS {
return;
}
if self.color_registers.len() <= register {
self.color_registers
.resize(register + 1, ColorRgb { r: 0, g: 0, b: 0 })
}
self.color_registers[register] = rgb;
}
fn ensure_size(&mut self, width: usize, height: usize) -> Result<(), Error> {
if self.width >= width && self.height >= height {
return Ok(());
}
if width > MAX_GRAPHIC_DIMENSIONS[0] || height > MAX_GRAPHIC_DIMENSIONS[1] {
return Err(Error::TooBigImage { width, height });
}
trace!(
"Set Sixel image dimensions to {}x{}",
max(self.width, width),
max(self.height, height),
);
if self.pixels.is_empty() {
self.width = width;
self.height = height;
self.pixels = vec![self.background; width * height];
return Ok(());
}
if self.width >= width {
self.pixels.resize(height * self.width, self.background);
self.height = height;
return Ok(());
}
let height = usize::max(height, self.height);
self.pixels.resize(height * width, self.background);
for y in (0..self.height).rev() {
for x in (0..self.width).rev() {
let old = y * self.width + x;
let new = y * width + x;
self.pixels.swap(old, new);
}
}
self.width = width;
self.height = height;
Ok(())
}
fn add_sixel(&mut self, sixel: Sixel) -> Result<(), Error> {
self.finish_command()?;
let repeat = max(1, mem::take(&mut self.repeat_count));
self.ensure_size(self.x + repeat, self.y + sixel.height())?;
if sixel.0 != 0 {
let mut index = self.width * self.y + self.x;
for dot in sixel.dots() {
if dot {
for pixel in &mut self.pixels[index..index + repeat] {
*pixel = self.selected_color_register;
}
}
index += self.width;
}
}
self.x += repeat;
Ok(())
}
pub fn finish(mut self) -> Result<(GraphicData, Vec<ColorRgb>), Error> {
self.finish_command()?;
trace!(
"Finish Sixel parser: width={}, height={}, color_registers={}",
self.width,
self.height,
self.color_registers.len()
);
let pixel_count = self.pixels.len();
let mut rgba_pixels = vec![0u8; pixel_count * 4];
let mut is_opaque = true;
let max_reg = self.color_registers.len();
let mut lut = vec![[0u8, 0, 0, 255]; max_reg];
for (i, color) in self.color_registers.iter().enumerate() {
lut[i] = [color.r, color.g, color.b, 255];
}
for (i, ®ister) in self.pixels.iter().enumerate() {
let offset = i * 4;
if register == REG_TRANSPARENT {
is_opaque = false;
} else {
let idx = register.0 as usize;
let pixel = if idx < max_reg {
lut[idx]
} else {
[0, 0, 0, 255]
};
rgba_pixels[offset] = pixel[0];
rgba_pixels[offset + 1] = pixel[1];
rgba_pixels[offset + 2] = pixel[2];
rgba_pixels[offset + 3] = pixel[3];
}
}
let data = GraphicData {
id: GraphicId::new(1),
height: self.height,
width: self.width,
color_type: ColorType::Rgba,
pixels: rgba_pixels,
is_opaque,
resize: None,
display_width: None,
display_height: None,
transmit_time: std::time::Instant::now(),
};
Ok((data, self.color_registers))
}
}
fn hls_to_rgb(hue: u16, lum: u16, sat: u16) -> ColorRgb {
if sat == 0 {
return rgb(lum, lum, lum, 100);
}
let lum = lum as f64;
let c0 = if lum > 50.0 {
((lum * 4.0) / 100.0) - 1.0
} else {
-(2.0 * (lum / 100.0) - 1.0)
};
let c = sat as f64 * (1.0 - c0) / 2.0;
let max = lum + c;
let min = lum - c;
let hue = (hue + 240) % 360;
let h = hue as f64;
let (r, g, b) = match hue / 60 {
0 => (max, min + (max - min) * (h / 60.0), min),
1 => (min + (max - min) * ((120.0 - h) / 60.0), max, min),
2 => (min, max, min + (max - min) * ((h - 120.0) / 60.0)),
3 => (min, min + (max - min) * ((240.0 - h) / 60.0), max),
4 => (min + (max - min) * ((h - 240.0) / 60.0), min, max),
5 => (max, min, min + (max - min) * ((360.0 - h) / 60.0)),
_ => (0., 0., 0.),
};
fn clamp(x: f64) -> u8 {
let x = f64::round(x * 255. / 100.) % 256.;
if x < 0. {
0
} else {
x as u8
}
}
ColorRgb {
r: clamp(r),
g: clamp(g),
b: clamp(b),
}
}
fn init_color_registers(parser: &mut Parser) {
parser.set_color_register(ColorRegister(0), rgb(0, 0, 0, 100));
parser.set_color_register(ColorRegister(1), rgb(20, 20, 80, 100));
parser.set_color_register(ColorRegister(2), rgb(80, 13, 13, 100));
parser.set_color_register(ColorRegister(3), rgb(20, 80, 20, 100));
parser.set_color_register(ColorRegister(4), rgb(80, 20, 80, 100));
parser.set_color_register(ColorRegister(5), rgb(20, 80, 80, 100));
parser.set_color_register(ColorRegister(6), rgb(80, 80, 20, 100));
parser.set_color_register(ColorRegister(7), rgb(53, 53, 53, 100));
parser.set_color_register(ColorRegister(8), rgb(26, 26, 26, 100));
parser.set_color_register(ColorRegister(9), rgb(33, 33, 60, 100));
parser.set_color_register(ColorRegister(10), rgb(60, 26, 26, 100));
parser.set_color_register(ColorRegister(11), rgb(33, 60, 33, 100));
parser.set_color_register(ColorRegister(12), rgb(60, 33, 60, 100));
parser.set_color_register(ColorRegister(13), rgb(33, 60, 60, 100));
parser.set_color_register(ColorRegister(14), rgb(60, 60, 33, 100));
parser.set_color_register(ColorRegister(15), rgb(80, 80, 80, 100));
}
#[inline]
fn rgb(r: u16, g: u16, b: u16, max: u16) -> ColorRgb {
if max == 255 {
ColorRgb {
r: r as u8,
b: b as u8,
g: g as u8,
}
} else {
let r = ((r * 255 + max / 2) / max) as u8;
let g = ((g * 255 + max / 2) / max) as u8;
let b = ((b * 255 + max / 2) / max) as u8;
ColorRgb { r, g, b }
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::Path;
macro_rules! put_bytes {
($parser:expr, $data:expr) => {
#[allow(clippy::string_lit_as_bytes)]
for &byte in $data.as_bytes() {
let _ = $parser.put(byte);
}
};
}
#[test]
fn parse_command_parameters() {
let mut command_parser = CommandParser::new(SixelCommand::ColorIntroducer);
put_bytes!(command_parser, "65535;1;2;3;4;5");
assert_eq!(command_parser.params_position, 5);
assert_eq!(command_parser.params[0], 65535);
assert_eq!(command_parser.params[1], 1);
assert_eq!(command_parser.params[2], 2);
assert_eq!(command_parser.params[3], 3);
assert_eq!(command_parser.params[4], 4);
}
#[test]
fn set_color_registers() {
let mut parser = Parser::default();
put_bytes!(parser, "#1;2;30;100;0#200;1;20;75;50.");
assert!(parser.color_registers.len() >= 200);
assert_eq!(
parser.color_registers[1],
ColorRgb {
r: 77,
g: 255,
b: 0
}
);
assert_eq!(
parser.color_registers[200],
ColorRgb {
r: 213,
g: 255,
b: 128
}
);
assert_eq!(parser.selected_color_register.0, 200);
}
#[test]
fn convert_hls_colors() {
fn abs_diff(x: u8, y: u8) -> u8 {
x.abs_diff(y)
}
macro_rules! assert_color {
($h:expr, $l:expr, $s:expr => $r:expr, $g:expr, $b:expr) => {
let left = hls_to_rgb($h, $l, $s);
let right = rgb($r, $g, $b, 255);
assert!(
abs_diff(left.r, right.r) < 4
&& abs_diff(left.g, right.g) < 4
&& abs_diff(left.b, right.b) < 4,
"Expected {:?} Found {:?}",
right,
left,
);
};
}
assert_color!(282 , 33 , 87 => 10 , 156 , 112);
assert_color!( 45 , 36 , 78 => 128 , 18 , 163);
assert_color!(279 , 9 , 93 => 0 , 43 , 28);
assert_color!(186 , 27 , 54 => 97 , 105 , 31);
assert_color!( 93 , 66 , 75 => 107 , 230 , 173);
assert_color!( 60 , 51 , 90 => 125 , 133 , 125);
assert_color!(141 , 39 , 78 => 176 , 74 , 20);
assert_color!(273 , 30 , 48 => 38 , 112 , 79);
assert_color!(270 , 15 , 57 => 15 , 59 , 38);
assert_color!( 84 , 21 , 99 => 105 , 0 , 64);
assert_color!(162 , 81 , 93 => 59 , 145 , 352);
assert_color!( 96 , 30 , 72 => 130 , 20 , 64);
assert_color!(222 , 21 , 90 => 33 , 99 , 5);
assert_color!(306 , 33 , 39 => 51 , 110 , 115);
assert_color!(144 , 30 , 72 => 130 , 64 , 20);
assert_color!( 27 , 0 , 42 => 0 , 0 , 0);
assert_color!(123 , 10 , 0 => 26 , 26 , 26);
assert_color!(279 , 6 , 93 => 0 , 28 , 18);
assert_color!(270 , 45 , 69 => 33 , 194 , 115);
assert_color!(225 , 39 , 45 => 77 , 143 , 54);
}
#[test]
fn resize_picture() -> Result<(), Error> {
let mut parser = Parser {
background: REG_TRANSPARENT,
..Parser::default()
};
const WIDTH: usize = 30;
const HEIGHT: usize = 20;
put_bytes!(parser, format!("\"1;1;{};{}.", WIDTH, HEIGHT));
assert_eq!(parser.width, WIDTH);
assert_eq!(parser.height, HEIGHT);
assert_eq!(parser.pixels.len(), WIDTH * HEIGHT);
assert!(parser.pixels.iter().all(|&pixel| pixel == REG_TRANSPARENT));
for (n, row) in parser.pixels.chunks_mut(WIDTH).enumerate() {
row.iter_mut()
.for_each(|pixel| *pixel = ColorRegister(n as u16));
}
parser.ensure_size(WIDTH, HEIGHT + 5)?;
assert_eq!(parser.width, WIDTH);
assert_eq!(parser.height, HEIGHT + 5);
assert_eq!(parser.pixels.len(), WIDTH * (HEIGHT + 5));
for (n, row) in parser.pixels.chunks(WIDTH).enumerate() {
let expected = if n < HEIGHT {
ColorRegister(n as u16)
} else {
REG_TRANSPARENT
};
assert!(row.iter().all(|pixel| *pixel == expected));
}
parser.ensure_size(WIDTH + 5, HEIGHT + 10)?;
assert_eq!(parser.width, WIDTH + 5);
assert_eq!(parser.height, HEIGHT + 10);
assert_eq!(parser.pixels.len(), (WIDTH + 5) * (HEIGHT + 10));
for (n, row) in parser.pixels.chunks(WIDTH + 5).enumerate() {
if n < HEIGHT {
assert!(row[..WIDTH]
.iter()
.all(|pixel| *pixel == ColorRegister(n as u16)));
assert!(row[WIDTH..].iter().all(|pixel| *pixel == REG_TRANSPARENT));
} else {
assert!(row.iter().all(|pixel| *pixel == REG_TRANSPARENT));
}
}
let graphics = parser.finish()?.0;
assert!(!graphics.is_opaque);
Ok(())
}
#[test]
fn sixel_height() {
assert_eq!(Sixel(0b000000).height(), 0);
assert_eq!(Sixel(0b000001).height(), 1);
assert_eq!(Sixel(0b000100).height(), 3);
assert_eq!(Sixel(0b000101).height(), 3);
assert_eq!(Sixel(0b101111).height(), 6);
}
#[test]
fn sixel_positions() {
macro_rules! dots {
($sixel:expr) => {
Sixel($sixel).dots().collect::<Vec<_>>()
};
}
assert_eq!(
dots!(0b000000),
&[false, false, false, false, false, false,]
);
assert_eq!(dots!(0b000001), &[true, false, false, false, false, false,]);
assert_eq!(dots!(0b000100), &[false, false, true, false, false, false,]);
assert_eq!(dots!(0b000101), &[true, false, true, false, false, false,]);
assert_eq!(dots!(0b101111), &[true, true, true, true, false, true,]);
}
#[test]
fn load_sixel_files() {
let images_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/sixel"));
let test_images = [
"testimage_im6",
"testimage_libsixel",
"testimage_ppmtosixel",
];
for test_image in &test_images {
let mut sixel = {
let mut path = images_dir.join(test_image);
path.set_extension("sixel");
fs::read(path).unwrap()
};
let dcs_end = sixel.iter().position(|&byte| byte == b'q').unwrap();
sixel.drain(..=dcs_end);
if let Some(pos) = sixel.iter().position(|&b| b == 0x1B || b == 0x9C) {
sixel.truncate(pos);
}
let mut parser = Parser::default();
for byte in sixel {
parser.put(byte).unwrap();
}
let graphics = parser.finish().unwrap().0;
assert_eq!(graphics.width, 64);
assert_eq!(graphics.height, 64);
let expected_rgba = {
let mut path = images_dir.join(test_image);
path.set_extension("rgba");
fs::read(path).unwrap()
};
assert_eq!(graphics.pixels, expected_rgba);
assert!(graphics.is_opaque);
}
}
}