#[allow(unused, reason = "__dbg")]
use crate::{__dbg, slog};
use crate::{Cmp, Digits, NotEnoughSpace, is, lets, slice, unwrap, whilst, write_at};
use crate::{SixelChar, SixelColor, SixelPalette};
#[doc = crate::_tags!(image term)]
#[doc = crate::_doc_meta!{location("media/visual/image")}]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SixelEncoder<const MAX_COLORS: usize> {
width: usize,
height: usize,
palette: SixelPalette<MAX_COLORS>,
}
impl<const MAX_COLORS: usize> Default for SixelEncoder<MAX_COLORS> {
fn default() -> Self {
Self::new()
}
}
__dbg! { slog! {
#[doc = crate::_tags!(debug log image term)]
#[cfg_attr(nightly_doc, doc(cfg(feature = "__dbg")))]
pub new sixel_encoder:64+64
}}
impl<const MAX_COLORS: usize> SixelEncoder<MAX_COLORS> {
pub const fn new() -> Self {
Self { width: 0, height: 0, palette: SixelPalette::new() }
}
pub const fn with_palette(
width: usize,
height: usize,
palette: SixelPalette<MAX_COLORS>,
) -> Self {
Self { width, height, palette }
}
pub const fn palette_mut(&mut self) -> &mut SixelPalette<MAX_COLORS> {
&mut self.palette
}
#[rustfmt::skip]
pub const fn encode_rgb(&mut self, rgb: &[u8], width: usize, height: usize, buf: &mut [u8])
-> Result<usize, NotEnoughSpace> {
__dbg![slog!{clear sixel_encoder:64+64}];
let mut off = 0;
off += unwrap![ok? Self::write_sixel_start_simple(slice![mut buf, off,..])];
self.width = width;
self.height = height;
unwrap![ok? self.build_palette(rgb, width, height)];
off += self.palette.write_definitions(slice![mut buf, off, ..]);
let mut band_y = 0;
while band_y < height {
let band_height = Cmp(height - band_y).min(6);
__dbg![slog!{sixel_encoder:64+64
"Processing band at row ", %band_y, " (height: ", %band_height, ")"}];
let mut iter = self.palette.defined_colors();
while let Some((idx, palette_color)) = iter.next() {
is![idx == 0, continue];
__dbg![slog!{sixel_encoder:64+64 " Rendering color #", %idx, " in band."}];
off += unwrap![ok? Self::write_color_reference(slice![mut buf, off,..], idx)];
lets![mut current_char = None, mut repeat_count = 0];
whilst! { x in 0..width; {
let sixel_bits = self.build_sixel_bits_for_color(rgb, width, x, band_y,
band_height, palette_color);
let sixel_char = SixelChar::from_bitmask(sixel_bits);
let is_same_char = is![let Some(c) = current_char, sixel_char.eq(c), false];
match (is_same_char, repeat_count == 255) {
(true, false) => {
repeat_count += 1;
}
(true, true) => { if let Some(char) = current_char {
off += unwrap![ok? Self::write_rle_run(slice![mut buf, off,..],
char, repeat_count)];
}
repeat_count = 1;
}
(false, _) => { if let Some(char) = current_char {
off += unwrap![ok? Self::write_rle_run(slice![mut buf, off,..],
char, repeat_count)];
}
current_char = Some(sixel_char);
repeat_count = 1;
}
}
}}
if let Some(char) = current_char {
off += unwrap![ok?
Self::write_rle_run(slice![mut buf, off,..], char, repeat_count)];
}
is! { idx != self.palette.len() as u16 - 1, write_at![buf, +=off, b'$'];}
}
band_y += 6;
is! { band_y < height, write_at![buf, +=off, b'-'];} }
off += unwrap![ok? Self::write_sixel_end(slice![mut buf, off,..])];
Ok(off)
}
}
#[rustfmt::skip]
impl<const MAX_COLORS: usize> SixelEncoder<MAX_COLORS> {
const fn build_palette(&mut self, rgb: &[u8], w: usize, h: usize) -> Result<(), NotEnoughSpace> {
self.palette = SixelPalette::new();
unwrap![ok? self.palette.add_color(SixelColor::BLACK)];
whilst! { i in 0..(w * h); {
let idx = i * 3;
let color = SixelColor::from_rgb888(rgb[idx], rgb[idx + 1], rgb[idx + 2]);
if !color.is_black() { let _ = self.palette.find_or_add(color); }
}}
Ok(())
}
const fn build_sixel_bits_for_color(&self, rgb: &[u8], width: usize, x: usize, band_y: usize,
band_height: usize, target_color: SixelColor) -> u8 {
let mut sixel_bits = 0u8;
whilst! { dy in 0..band_height; {
let y = band_y + dy;
let idx = (y * width + x) * 3;
if idx + 2 < rgb.len() {
let pixel_color = SixelColor::from_rgb888(rgb[idx], rgb[idx + 1], rgb[idx + 2]);
if pixel_color.is_similar_to(target_color) { sixel_bits |= 1 << dy }
}
}}
sixel_bits
}
}
impl<const MAX_COLORS: usize> SixelEncoder<MAX_COLORS> {
pub const fn write_sixel_start(
buf: &mut [u8],
width: usize,
height: usize,
) -> Result<usize, NotEnoughSpace> {
let needed = 5 + 6 + 5 + 1 + 5; is![buf.len() < needed, return Err(NotEnoughSpace(Some(needed)))];
let (width_digits, height_digits) = (Digits(width), Digits(height));
let mut offset = 0;
write_at!(
buf, +=offset, b'\x1b', b'P', b';', b'1', b';', b'q', );
write_at!(buf, +=offset, b'"', b'1', b';', b'1', b';'); offset += width_digits.write_digits10(buf, offset);
write_at!(buf, +=offset, b';'); offset += height_digits.write_digits10(buf, offset);
Ok(offset)
}
pub const fn write_sixel_start_simple(buf: &mut [u8]) -> Result<usize, NotEnoughSpace> {
is![buf.len() < 6, return Err(NotEnoughSpace(Some(6)))];
write_at!(
buf, 0, b'\x1b', b'P', b';', b'1', b';', b'q', );
Ok(6)
}
pub const fn write_sixel_end(buf: &mut [u8]) -> Result<usize, NotEnoughSpace> {
is![buf.len() < 2, return Err(NotEnoughSpace(Some(2)))];
write_at!(buf, 0, b'\x1b', b'\\'); Ok(2)
}
const fn write_color_reference(buffer: &mut [u8], index: u16) -> Result<usize, NotEnoughSpace> {
is![buffer.len() < 2, return Err(NotEnoughSpace(Some(2)))];
buffer[0] = b'#';
let digits_written = Digits(index).write_digits10_nonzero(buffer, 1);
is![digits_written == 0, return Err(NotEnoughSpace(Some(2)))];
Ok(1 + digits_written)
}
const fn write_rle_run(
buffer: &mut [u8],
sc: SixelChar,
count: u8,
) -> Result<usize, NotEnoughSpace> {
let mut off = 0;
if count >= 4 {
let digits = Digits(count);
let dcount = digits.count_digits10();
let needed = 1 + dcount as usize;
is![buffer.len() < needed, return Err(NotEnoughSpace(Some(needed)))];
write_at![buffer, +=off, b'!'];
off += digits.write_digits10(buffer, 1);
write_at![buffer, +=off, sc.as_byte()];
__dbg![slog! {sixel_encoder:64+64 " RLE: !", %count, ":", @sc.to_string_box(),
" (saved ", %count-(2+dcount), " bytes)"}];
} else {
__dbg![slog! {sixel_encoder:64+64
" NO RLE: ", %count, " times ", @sc.to_string_box()}];
whilst! { _n in 0..count; {
is![off >= buffer.len(), return Err(NotEnoughSpace(Some(buffer.len())))];
write_at![buffer, +=off, sc.as_byte()];
}}
}
Ok(off)
}
}