use std::io::Write;
use url::Url;
use crate::{ResourceUrlHandler, TerminalSize};
pub trait InlineImageProtocol {
fn write_inline_image(
&self,
writer: &mut dyn Write,
resource_handler: &dyn ResourceUrlHandler,
url: &Url,
terminal_size: TerminalSize,
) -> std::io::Result<()>;
}
#[cfg(feature = "image-processing")]
pub fn fit_image_to_terminal(
image: &image::DynamicImage,
size: TerminalSize,
) -> Option<image::DynamicImage> {
use image::{imageops::FilterType, GenericImageView};
use tracing::{event, Level};
let (image_width, image_height) = image.dimensions();
event!(
Level::DEBUG,
"Terminal size {:?}; image is {:?}",
size,
(image_width, image_height)
);
if let Some(cell) = size.cell.filter(|cell| cell.x > 0 && cell.y > 0) {
let rows = (image_height as f64 / cell.y as f64).ceil().max(1.0);
let mut target_height = (rows * cell.y as f64).round() as u32;
let mut target_width = (image_width as f64 * target_height as f64 / image_height as f64)
.round()
.max(1.0) as u32;
if let Some(win_size) = size.pixels {
if target_width > win_size.x {
target_width = win_size.x.max(1);
target_height = (image_height as f64 * target_width as f64 / image_width as f64)
.round()
.max(1.0) as u32;
}
}
if target_width == image_width && target_height == image_height {
None
} else {
Some(image.resize_exact(target_width, target_height, FilterType::Lanczos3))
}
} else if let Some(win_size) = size.pixels {
if win_size.x < image_width {
Some(image.resize(win_size.x, win_size.y, FilterType::Nearest))
} else {
None
}
} else {
None
}
}
#[cfg(all(test, feature = "image-processing"))]
mod tests {
use super::*;
use crate::terminal::PixelSize;
use image::{DynamicImage, GenericImageView, RgbaImage};
fn image_sized(width: u32, height: u32) -> DynamicImage {
DynamicImage::ImageRgba8(RgbaImage::new(width, height))
}
#[test]
fn no_size_info_leaves_image_untouched() {
let image = image_sized(108, 20);
assert!(fit_image_to_terminal(&image, TerminalSize::default()).is_none());
}
#[test]
fn short_badge_is_scaled_up_to_a_whole_cell_row() {
let image = image_sized(108, 20);
let size = TerminalSize {
pixels: Some(PixelSize { x: 1600, y: 816 }),
cell: Some(PixelSize { x: 10, y: 34 }),
..TerminalSize::default()
};
let resized = fit_image_to_terminal(&image, size).expect("badge should be resized");
let (w, h) = resized.dimensions();
assert_eq!(h, 34);
assert_eq!(w, (108.0_f64 * 34.0 / 20.0).round() as u32);
}
#[test]
fn image_already_row_aligned_is_left_untouched() {
let image = image_sized(100, 34);
let size = TerminalSize {
pixels: Some(PixelSize { x: 1600, y: 816 }),
cell: Some(PixelSize { x: 10, y: 34 }),
..TerminalSize::default()
};
assert!(fit_image_to_terminal(&image, size).is_none());
}
#[test]
fn row_alignment_yields_to_column_limit() {
let image = image_sized(1000, 60);
let size = TerminalSize {
pixels: Some(PixelSize { x: 200, y: 816 }),
cell: Some(PixelSize { x: 10, y: 34 }),
..TerminalSize::default()
};
let resized = fit_image_to_terminal(&image, size).expect("image should be resized");
let (w, _) = resized.dimensions();
assert_eq!(w, 200);
}
#[test]
fn without_cell_size_falls_back_to_column_downsize() {
let image = image_sized(1000, 60);
let size = TerminalSize {
pixels: Some(PixelSize { x: 200, y: 816 }),
cell: None,
..TerminalSize::default()
};
let resized = fit_image_to_terminal(&image, size).expect("image should be resized");
let (w, _) = resized.dimensions();
assert_eq!(w, 200);
}
}