Skip to main content

pulldown_cmark_mdcat/terminal/capabilities/
sixel.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Sixel image support as thin wrapper on top of icy_sixel.
4
5use std::io::{self, Write};
6
7use crate::resources::image::*;
8use icy_sixel::SixelImage;
9use image::GenericImageView;
10
11use crate::resources::MimeData;
12use crate::terminal::size::TerminalSize;
13
14/// Sixel graphics protocol implementation.
15#[derive(Debug, Copy, Clone)]
16pub struct SixelProtocol;
17
18impl SixelProtocol {
19    fn load_image(mime: MimeData) -> io::Result<image::DynamicImage> {
20        match mime.mime_type_essence() {
21            Some("image/svg+xml") => {
22                let png = crate::resources::svg::render_svg_to_png(&mime.data)?;
23
24                image::load_from_memory_with_format(&png, image::ImageFormat::Png)
25                    .map_err(io::Error::other)
26            }
27
28            Some("image/png") => {
29                image::load_from_memory_with_format(&mime.data, image::ImageFormat::Png)
30                    .map_err(io::Error::other)
31            }
32
33            _ => image::load_from_memory(&mime.data).map_err(io::Error::other),
34        }
35    }
36
37    fn render_sixel(writer: &mut dyn Write, img: image::DynamicImage) -> io::Result<()> {
38        let (w, h) = img.dimensions();
39        let rgba = img.to_rgba8();
40
41        let sixel = SixelImage::try_from_rgba(rgba.into_raw(), w as usize, h as usize)
42            .map_err(io::Error::other)?
43            .encode()
44            .map_err(io::Error::other)?;
45        write!(writer, "{sixel}")
46    }
47
48    /// Write raw PNG bytes inline to the terminal.
49    pub(crate) fn write_png_data(&self, writer: &mut dyn Write, png_data: &[u8]) -> io::Result<()> {
50        let img = image::load_from_memory_with_format(png_data, image::ImageFormat::Png)
51            .map_err(io::Error::other)?;
52
53        Self::render_sixel(writer, img)
54    }
55}
56
57impl crate::resources::InlineImageProtocol for SixelProtocol {
58    fn write_inline_image(
59        &self,
60        writer: &mut dyn Write,
61        resource_handler: &dyn crate::resources::ResourceUrlHandler,
62        url: &url::Url,
63        terminal_size: TerminalSize,
64    ) -> io::Result<()> {
65        let mime = resource_handler.read_resource(url)?;
66        let image = SixelProtocol::load_image(mime)?;
67
68        let image = if let Some(downsized) = downsize_to_columns(&image, terminal_size) {
69            downsized
70        } else {
71            image
72        };
73
74        SixelProtocol::render_sixel(writer, image)
75    }
76}