use crate::magic;
use crate::resources::read_url;
use crate::svg::render_svg;
use failure::Error;
use image::{ColorType, FilterType};
use image::{DynamicImage, GenericImageView};
use std::io::Write;
use std::process::{Command, Stdio};
use std::str;
use url::Url;
pub fn is_kitty() -> bool {
std::env::var("TERM")
.map(|value| value == "xterm-kitty")
.unwrap_or(false)
}
fn get_terminal_size() -> std::io::Result<KittyDimension> {
use std::io::{Error, ErrorKind};
let process = Command::new("kitty")
.arg("+kitten")
.arg("icat")
.arg("--print-window-size")
.stdout(Stdio::piped())
.spawn()?;
let output = process.wait_with_output()?;
if output.status.success() {
let terminal_size_str = std::str::from_utf8(&output.stdout).or_else(|_| {
Err(Error::new(
ErrorKind::Other,
"The terminal size could not be read.".to_string(),
))
})?;
let terminal_size = terminal_size_str.split('x').collect::<Vec<&str>>();
let (width, height) = (
terminal_size[0].parse::<u32>().or_else(|_| {
Err(Error::new(
ErrorKind::Other,
format!(
"The terminal width could not be parsed: {}",
terminal_size_str
),
))
})?,
terminal_size[1].parse::<u32>().or_else(|_| {
Err(Error::new(
ErrorKind::Other,
format!(
"The terminal height could not be parsed: {}",
terminal_size_str
),
))
})?,
);
Ok(KittyDimension { width, height })
} else {
Err(Error::new(
ErrorKind::Other,
format!(
"kitty +kitten icat --print-window-size failed with status {}: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
),
))
}
}
pub struct KittyImages;
impl KittyImages {
pub fn write_inline_image<W: Write>(
&self,
writer: &mut W,
image: KittyImage,
) -> Result<(), Error> {
let mut cmd_header: Vec<String> = vec![
"a=T".into(),
"t=d".into(),
format!("f={}", image.format.control_data_value()),
];
if let Some(dimension) = image.dimension {
cmd_header.push(format!("s={}", dimension.width));
cmd_header.push(format!("v={}", dimension.height));
}
let image_data = base64::encode(&image.contents);
let image_data_chunks = image_data.as_bytes().chunks(4096);
let image_data_chunks_length = image_data_chunks.len();
for (i, data) in image_data_chunks.enumerate() {
if i < image_data_chunks_length - 1 {
cmd_header.push("m=1".into());
} else {
cmd_header.push("m=0".into());
}
let cmd = format!(
"\x1b_G{};{}\x1b\\",
cmd_header.join(","),
str::from_utf8(data)?
);
writer.write_all(cmd.as_bytes())?;
writer.flush()?;
cmd_header.clear();
}
Ok(())
}
pub fn read_and_render(&self, url: &Url) -> Result<KittyImage, Error> {
let contents = read_url(url)?;
let mime = magic::detect_mime_type(&contents)?;
let image = if magic::is_svg(&mime) {
let rendered = render_svg(&contents)?;
image::load_from_memory(&rendered)?
} else {
image::load_from_memory(&contents)?
};
let terminal_size = get_terminal_size()?;
let (image_width, image_height) = image.dimensions();
let needs_scaledown =
image_width > terminal_size.width || image_height > terminal_size.height;
if mime.type_() == mime::IMAGE && mime.subtype().as_str() == "png" && !needs_scaledown {
self.render_as_png(contents)
} else {
self.render_as_rgb_or_rgba(image, terminal_size)
}
}
fn render_as_png(&self, contents: Vec<u8>) -> Result<KittyImage, Error> {
Ok(KittyImage {
contents,
format: KittyFormat::PNG,
dimension: None,
})
}
fn render_as_rgb_or_rgba(
&self,
image: DynamicImage,
terminal_size: KittyDimension,
) -> Result<KittyImage, Error> {
let format = match image.color() {
ColorType::RGB(_) => KittyFormat::RGB,
_ => KittyFormat::RGBA,
};
let (image_width, image_height) = image.dimensions();
let image = if image_width > terminal_size.width || image_height > terminal_size.height {
image.resize_to_fill(
terminal_size.width,
terminal_size.height,
FilterType::Nearest,
)
} else {
image
};
Ok(KittyImage {
contents: match format {
KittyFormat::RGB => image.to_rgb().into_raw(),
_ => image.to_rgba().into_raw(),
},
format,
dimension: Some(KittyDimension {
width: image_width,
height: image_height,
}),
})
}
}
pub struct KittyImage {
contents: Vec<u8>,
format: KittyFormat,
dimension: Option<KittyDimension>,
}
enum KittyFormat {
PNG,
RGB,
RGBA,
}
impl KittyFormat {
fn control_data_value(&self) -> &str {
match *self {
KittyFormat::PNG => "100",
KittyFormat::RGB => "24",
KittyFormat::RGBA => "32",
}
}
}
struct KittyDimension {
width: u32,
height: u32,
}