use std::io::Write;
use crate::widgets::ImageData;
use crate::widgets::image::PicturePlacement;
const CHUNK: usize = 4096;
#[derive(Debug, Default)]
pub(crate) struct KittyPictures {
sent: Vec<u32>,
shown: Vec<PicturePlacement>,
place_all: bool,
send_all: bool,
}
impl KittyPictures {
pub(crate) fn commands(&mut self, placements: &[PicturePlacement], painted: &[u64]) -> Vec<u8> {
let mut out = Vec::new();
let mut kept: Vec<u32> = placements.iter().map(|placement| number(&placement.data)).collect();
kept.extend(painted.iter().map(|id| number_of(*id)));
self.sent.retain(|sent| {
let keep = kept.contains(sent);
if !keep {
let _ = write!(out, "\x1b_Ga=d,d=I,i={sent},q=2\x1b\\");
}
keep
});
for old in &self.shown {
let image = number(&old.data);
let still = placements.iter().any(|new| number(&new.data) == image && new.number == old.number);
if kept.contains(&image) && !still {
let _ = write!(out, "\x1b_Ga=d,d=i,i={image},p={},q=2\x1b\\", old.number);
}
}
if self.send_all {
self.sent.clear();
}
let mut sent_now = Vec::new();
for placement in placements {
let image = number(&placement.data);
if !self.sent.contains(&image) {
transmit(&mut out, &placement.data, image);
self.sent.push(image);
sent_now.push(image);
}
}
for placement in placements {
let again = self.place_all || sent_now.contains(&number(&placement.data));
if again || !self.shown.contains(placement) {
place(&mut out, placement);
}
}
self.shown = placements.to_vec();
self.place_all = false;
self.send_all = false;
out
}
pub(crate) fn release(&mut self) -> Vec<u8> {
let mut out = Vec::new();
for sent in self.sent.drain(..) {
let _ = write!(out, "\x1b_Ga=d,d=I,i={sent},q=2\x1b\\");
}
self.shown.clear();
out
}
pub(crate) fn forget(&mut self) {
self.place_all = true;
self.send_all = true;
}
pub(crate) fn resized(&mut self) {
self.place_all = true;
}
}
pub(super) fn number(data: &ImageData) -> u32 {
number_of(data.id())
}
fn number_of(id: u64) -> u32 {
let wrapped = (id.saturating_sub(1) % u64::from(u32::MAX)) + 1;
u32::try_from(wrapped).unwrap_or(u32::MAX)
}
fn transmit(out: &mut Vec<u8>, data: &ImageData, image: u32) {
let rgb: Vec<u8> = data.pixels().iter().flat_map(|pixel| [pixel.r, pixel.g, pixel.b]).collect();
let (payload, compressed) = match compress(&rgb) {
Some(zipped) => (zipped, ",o=z"),
None => (rgb, ""),
};
let encoded = base64(&payload);
let chunks: Vec<&[u8]> = encoded.as_bytes().chunks(CHUNK).collect();
let (width, height) = (data.width(), data.height());
for (index, chunk) in chunks.iter().enumerate() {
let more = u8::from(index + 1 < chunks.len());
if index == 0 {
let _ = write!(out, "\x1b_Ga=t,f=24{compressed},s={width},v={height},i={image},q=2,m={more};");
} else {
let _ = write!(out, "\x1b_Gm={more},q=2;");
}
out.extend_from_slice(chunk);
out.extend_from_slice(b"\x1b\\");
}
}
fn place(out: &mut Vec<u8>, placement: &PicturePlacement) {
let (column, row, columns, rows) = placement.cells;
let (x, y, width, height) = placement.crop;
let image = number(&placement.data);
let _ = write!(
out,
"\x1b[{};{}H\x1b_Ga=p,i={image},p={},x={x},y={y},w={width},h={height},c={columns},r={rows},z=-1,C=1,q=2\x1b\\",
u32::from(row) + 1,
u32::from(column) + 1,
placement.number,
);
}
fn compress(bytes: &[u8]) -> Option<Vec<u8>> {
let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
encoder.write_all(bytes).ok()?;
encoder.finish().ok()
}
fn base64(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
for group in bytes.chunks(3) {
let joined =
group.iter().enumerate().fold(0u32, |all, (index, byte)| all | u32::from(*byte) << (16 - 8 * index));
for index in 0..4 {
if index <= group.len() {
out.push(char::from(ALPHABET[(joined >> (18 - 6 * index) & 63) as usize]));
} else {
out.push('=');
}
}
}
out
}