use std::io::Write;
use anyhow::{Context, Result};
use base64::Engine;
use image::DynamicImage;
use ratatui::{buffer::CellDiffOption, layout::Rect, Frame};
use ratatui_image::{
picker::{Picker, ProtocolType},
protocol::Protocol,
Image, Resize,
};
pub enum CoverImage {
Widget(Box<Protocol>),
Iterm2 { escape: String, drawn: bool },
}
impl CoverImage {
pub fn new(picker: &Picker, img: &DynamicImage, area: Rect) -> Result<Self> {
if picker.protocol_type() == ProtocolType::Iterm2 {
Ok(Self::Iterm2 {
escape: encode_iterm2(img, area)?,
drawn: false,
})
} else {
let protocol = picker
.new_protocol(img.clone(), area.into(), Resize::Fit(None))
.context("encode cover image protocol")?;
Ok(Self::Widget(Box::new(protocol)))
}
}
pub fn render(&mut self, frame: &mut Frame, area: Rect) {
match self {
Self::Widget(protocol) => frame.render_widget(Image::new(protocol.as_ref()), area),
Self::Iterm2 { escape, drawn } => {
reserve_area(frame, area);
if !*drawn {
if let Err(err) = write_iterm2(escape, area) {
tracing::error!("Failed to draw iTerm2 cover image: {err:#}");
} else {
*drawn = true;
}
}
}
}
}
}
fn reserve_area(frame: &mut Frame, area: Rect) {
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
if let Some(cell) = frame.buffer_mut().cell_mut((x, y)) {
cell.set_diff_option(CellDiffOption::Skip);
}
}
}
}
fn encode_iterm2(img: &DynamicImage, area: Rect) -> Result<String> {
let mut png = Vec::new();
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png)
.context("encode cover image to PNG")?;
let data = base64::engine::general_purpose::STANDARD.encode(&png);
Ok(format!(
"\x1b]1337;File=inline=1;preserveAspectRatio=1;size={};width={};height={}:{data}\x07",
png.len(),
area.width,
area.height,
))
}
fn write_iterm2(escape: &str, area: Rect) -> std::io::Result<()> {
let mut out = std::io::stdout().lock();
out.write_all(b"\x1b7")?; for row in area.top()..area.bottom() {
write!(out, "\x1b[{};{}H\x1b[{}X", row + 1, area.x + 1, area.width)?;
}
write!(out, "\x1b[{};{}H{escape}", area.y + 1, area.x + 1)?;
out.write_all(b"\x1b8")?; out.flush()
}