#[allow(unused, reason = "__dbg")]
use crate::{__dbg, slog};
use crate::{
Cmp, Digits, NotEnoughSpace, SixelChar, SixelColor, SixelPalette, is, lets, slice, unwrap,
write_at,
};
#[doc = crate::_tags!(image term)]
#[doc = crate::_doc_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 x = 0, mut current_char = None, mut repeat_count = 0];
while x < 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;
}
}
x += 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)
}
}
impl<const MAX_COLORS: usize> SixelEncoder<MAX_COLORS> {
#[rustfmt::skip]
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)];
lets![mut i = 0, total_pixels = w * h];
while i < total_pixels {
let idx = i * 3;
let color = SixelColor::from_rgb888(rgb[idx], rgb[idx + 1], rgb[idx + 2]);
is![!color.is_black(), { let _ = self.palette.find_or_add(color); }];
i += 1;
}
Ok(())
}
#[rustfmt::skip]
const fn build_sixel_bits_for_color(&self, rgb: &[u8], width: usize, x: usize, band_y: usize,
band_height: usize, target_color: SixelColor) -> u8 {
lets![mut dy = 0, mut sixel_bits = 0u8];
while dy < 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]);
is![pixel_color.is_similar_to(target_color), sixel_bits |= 1 << dy];
}
dy += 1;
}
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_omit0(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()}];
let mut _n = 0;
while _n < count {
is![off >= buffer.len(), return Err(NotEnoughSpace(Some(buffer.len())))];
write_at![buffer, +=off, sc.as_byte()];
_n += 1;
}
}
Ok(off)
}
}