use std::collections::HashMap;
use ratatui::layout::Alignment;
use ratatui::text::Line;
use crate::ImageInfo;
pub use image as img_crate;
pub use ratatui_image::{protocol::Protocol, FontSize, Image, Resize};
pub use ratatui_image::picker::Picker;
pub struct ImagePlacement {
pub url: String,
pub line_start: usize,
pub cell_cols: u16,
pub cell_rows: u16,
pub alignment: Option<Alignment>,
}
pub fn fit_cell_size(
img: &img_crate::DynamicImage,
font_size: &FontSize,
max_cols: u16,
max_rows: u16,
) -> (u16, u16) {
if max_cols == 0 || max_rows == 0 || img.width() == 0 || img.height() == 0 {
return (max_cols.max(1), max_rows.max(1));
}
let cell_w = font_size.width as f64;
let cell_h = font_size.height as f64;
let img_cols = (img.width() as f64 / cell_w).ceil();
let img_rows = (img.height() as f64 / cell_h).ceil();
let scale_x = max_cols as f64 / img_cols;
let scale_y = max_rows as f64 / img_rows;
let scale = scale_x.min(scale_y).min(1.0);
let cols = (img_cols * scale).ceil().max(1.0) as u16;
let rows = (img_rows * scale).ceil().max(1.0) as u16;
(cols.min(max_cols), rows.min(max_rows))
}
pub fn make_protocol(
picker: &Picker,
img: &img_crate::DynamicImage,
cell_cols: u16,
cell_rows: u16,
) -> Option<Protocol> {
let size = ratatui::layout::Size::new(cell_cols, cell_rows);
picker
.new_protocol(img.clone(), size, Resize::Fit(None))
.ok()
}
pub fn make_scrolled_protocol(
picker: &Picker,
img: &img_crate::DynamicImage,
full_size: ratatui::layout::Size,
visible_size: ratatui::layout::Size,
hidden_top: u16,
) -> Option<Protocol> {
let (fw, fh) = (picker.font_size().width as u32, picker.font_size().height as u32);
let fit_w = full_size.width as u32 * fw;
let fit_h = full_size.height as u32 * fh;
let scale = (fit_w as f64 / img.width() as f64)
.min(fit_h as f64 / img.height() as f64)
.min(1.0);
let sw = (img.width() as f64 * scale).round() as u32;
let sh = (img.height() as f64 * scale).round() as u32;
let scaled = img.resize_exact(sw, sh, image::imageops::FilterType::Nearest);
let mut padded =
image::RgbaImage::from_pixel(fit_w, fit_h, image::Rgba([0, 0, 0, 0]));
image::imageops::overlay(&mut padded, &scaled, 0, 0);
let vis_pix_h = visible_size.height as u32 * fh;
let y_off = (hidden_top as u32 * fh).min(padded.height().saturating_sub(vis_pix_h));
let padded_dyn: img_crate::DynamicImage = padded.into();
let cropped = padded_dyn.crop_imm(0, y_off, fit_w, vis_pix_h);
picker.new_protocol(cropped, visible_size, Resize::Fit(None)).ok()
}
pub fn halfblock_picker() -> Picker {
Picker::halfblocks()
}
#[allow(clippy::too_many_arguments)]
pub fn prepare_inline_images(
lines: &mut Vec<Line<'static>>,
images: &[ImageInfo],
cache: &HashMap<String, img_crate::DynamicImage>,
protocol_cache: &mut HashMap<String, Protocol>,
picker: &Picker,
font_size: &FontSize,
max_cols: u16,
max_rows: u16,
) -> Vec<ImagePlacement> {
let mut indexed: Vec<(usize, &ImageInfo)> = images.iter().enumerate().collect();
indexed.sort_by_key(|a| a.1.line_index);
for (_, img) in &indexed {
if cache.contains_key(&img.url) && !protocol_cache.contains_key(&img.url) {
if let Some(dyn_img) = cache.get(&img.url) {
let (cols, rows) = fit_cell_size(dyn_img, font_size, max_cols, max_rows);
if cols > 0 && rows > 0 {
if let Some(protocol) = make_protocol(picker, dyn_img, cols, rows) {
protocol_cache.insert(img.url.clone(), protocol);
}
}
}
}
}
let mut placements = Vec::new();
let mut offset: isize = 0;
let mut cursor: isize = 0;
for (_, img) in &indexed {
let adjusted_line = (img.line_index as isize + offset) as usize;
if !protocol_cache.contains_key(&img.url) {
continue;
}
let Some(dyn_img) = cache.get(&img.url) else {
continue;
};
let (cols, rows) = fit_cell_size(dyn_img, font_size, max_cols, max_rows);
if cols == 0 || rows == 0 {
continue;
}
let insert_at = (adjusted_line as isize).max(cursor) as usize;
if insert_at >= lines.len() {
continue;
}
let alignment = lines[insert_at].alignment;
let empty: Vec<Line<'static>> = (0..rows)
.map(|_| {
let mut l = Line::from("");
l.alignment = alignment;
l
})
.collect();
lines.splice(insert_at..=insert_at, empty);
placements.push(ImagePlacement {
url: img.url.clone(),
line_start: insert_at,
cell_cols: cols,
cell_rows: rows,
alignment,
});
offset += rows as isize - 1;
cursor = insert_at as isize + rows as isize;
}
placements
}