termimage/ops/
mod.rs

1//! Main functions doing actual work.
2//!
3//! Use `guess_format()` to get the image format from a path,
4//! then read the image using `load_image()` to the size given by `image_resized_size()`,
5//! resize it to terminal size with `resize_image()`
6//! and display it with `write_[no_]ansi[_truecolor]()`,
7//! or display it yourself with approximations from `create_colourtable()`.
8
9
10use self::super::util::{ANSI_BG_COLOUR_ESCAPES, ANSI_RESET_ATTRIBUTES, ANSI_COLOUR_ESCAPES, JPEG_MAGIC, BMP_MAGIC, ICO_MAGIC, GIF_MAGIC, PNG_MAGIC,
11                        closest_colour, bg_colours_for};
12use image::{self, GenericImageView, DynamicImage, ImageFormat, Pixel};
13use std::io::{BufReader, Write, Read};
14use image::imageops::FilterType;
15use self::super::Error;
16use std::path::PathBuf;
17use std::ops::Index;
18use std::fs::File;
19
20mod no_ansi;
21
22pub use self::no_ansi::write_no_ansi;
23
24
25/// Guess the image format from its extension or magic.
26///
27/// # Examples
28///
29/// Correct:
30///
31/// ```
32/// # extern crate image;
33/// # extern crate termimage;
34/// # use image::ImageFormat;
35/// # use std::path::PathBuf;
36/// # use termimage::ops::guess_format;
37/// # fn main() {
38/// assert_eq!(guess_format(&(String::new(), PathBuf::from("img.png"))), Ok(ImageFormat::Png));
39/// assert_eq!(guess_format(&(String::new(), PathBuf::from("img.jpg"))), Ok(ImageFormat::Jpeg));
40/// assert_eq!(guess_format(&(String::new(), PathBuf::from("img.gif"))), Ok(ImageFormat::Gif));
41/// assert_eq!(guess_format(&(String::new(), PathBuf::from("img.bmp"))), Ok(ImageFormat::Bmp));
42/// assert_eq!(guess_format(&(String::new(), PathBuf::from("img.ico"))), Ok(ImageFormat::Ico));
43/// # }
44/// ```
45///
46/// Incorrect:
47///
48/// ```
49/// # use std::path::PathBuf;
50/// # use termimage::Error;
51/// # use termimage::ops::guess_format;
52/// assert_eq!(guess_format(&("src/ops.rs".to_string(), PathBuf::from("src/ops/mod.rs"))),
53/// Err(Error::GuessingFormatFailed("src/ops.rs".to_string())));
54/// ```
55pub fn guess_format(file: &(String, PathBuf)) -> Result<ImageFormat, Error> {
56    file.1
57        .extension()
58        .and_then(|ext| match &ext.to_str().unwrap().to_lowercase()[..] {
59            "png" => Some(Ok(ImageFormat::Png)),
60            "jpg" | "jpeg" | "jpe" | "jif" | "jfif" | "jfi" => Some(Ok(ImageFormat::Jpeg)),
61            "gif" => Some(Ok(ImageFormat::Gif)),
62            "webp" => Some(Ok(ImageFormat::WebP)),
63            "ppm" => Some(Ok(ImageFormat::Pnm)),
64            "tiff" | "tif" => Some(Ok(ImageFormat::Tiff)),
65            "tga" => Some(Ok(ImageFormat::Tga)),
66            "bmp" | "dib" => Some(Ok(ImageFormat::Bmp)),
67            "ico" => Some(Ok(ImageFormat::Ico)),
68            "hdr" => Some(Ok(ImageFormat::Hdr)),
69            _ => None,
70        })
71        .unwrap_or_else(|| {
72            let mut buf = [0; 32];
73            let read = File::open(&file.1).map_err(|_| Error::OpeningImageFailed(file.0.clone()))?.read(&mut buf).unwrap();
74            let buf = &buf[..read];
75
76            if buf.len() >= PNG_MAGIC.len() && &buf[..PNG_MAGIC.len()] == PNG_MAGIC {
77                Ok(ImageFormat::Png)
78            } else if buf.len() >= JPEG_MAGIC.len() && &buf[..JPEG_MAGIC.len()] == JPEG_MAGIC {
79                Ok(ImageFormat::Jpeg)
80            } else if buf.len() >= GIF_MAGIC.len() && &buf[..GIF_MAGIC.len()] == GIF_MAGIC {
81                Ok(ImageFormat::Gif)
82            } else if buf.len() >= BMP_MAGIC.len() && &buf[..BMP_MAGIC.len()] == BMP_MAGIC {
83                Ok(ImageFormat::Bmp)
84            } else if buf.len() >= ICO_MAGIC.len() && &buf[..ICO_MAGIC.len()] == ICO_MAGIC {
85                Ok(ImageFormat::Ico)
86            } else {
87                Err(Error::GuessingFormatFailed(file.0.clone()))
88            }
89        })
90}
91
92/// Load an image from the specified file as the specified format.
93///
94/// Get the image fromat with `guess_format()`.
95pub fn load_image(file: &(String, PathBuf), format: ImageFormat) -> Result<DynamicImage, Error> {
96    Ok(image::load(BufReader::new(File::open(&file.1).map_err(|_| Error::OpeningImageFailed(file.0.clone()))?),
97                   format)
98        .unwrap())
99}
100
101/// Get the image size to downscale to, given its size, the terminal's size and whether to preserve its aspect.
102///
103/// The resulting image size is twice as tall as the terminal size because we print two pixels per cell (height-wise).
104pub fn image_resized_size(size: (u32, u32), term_size: (u32, u32), preserve_aspect: bool) -> (u32, u32) {
105    if !preserve_aspect {
106        return (term_size.0, term_size.1 * 2);
107    }
108
109    let nwidth = term_size.0;
110    let nheight = term_size.1 * 2;
111    let (width, height) = size;
112
113    let ratio = width as f32 / height as f32;
114    let nratio = nwidth as f32 / nheight as f32;
115
116    let scale = if nratio > ratio {
117        nheight as f32 / height as f32
118    } else {
119        nwidth as f32 / width as f32
120    };
121
122    ((width as f32 * scale) as u32, (height as f32 * scale) as u32)
123}
124
125/// Resize the specified image to the specified size.
126pub fn resize_image(img: &DynamicImage, size: (u32, u32)) -> DynamicImage {
127    img.resize_exact(size.0, size.1, FilterType::Nearest)
128}
129
130/// Create a line-major table of (upper, lower) colour approximation indices given the supported colours therefor.
131///
132/// # Examples
133///
134/// Approximate `img` to ANSI and display it to stdout.
135///
136/// ```
137/// # extern crate termimage;
138/// # extern crate image;
139/// # use termimage::util::{ANSI_COLOURS_WHITE_BG, ANSI_COLOUR_ESCAPES, ANSI_BG_COLOUR_ESCAPES, bg_colours_for};
140/// # use termimage::ops::create_colourtable;
141/// # fn main() {
142/// # let img = image::DynamicImage::new_rgb8(16, 16);
143/// for line in create_colourtable(&img, &ANSI_COLOURS_WHITE_BG, &bg_colours_for(&ANSI_COLOURS_WHITE_BG)) {
144///     for (upper_clr, lower_clr) in line {
145///         print!("{}{}\u{2580}", // ▀
146///                ANSI_COLOUR_ESCAPES[upper_clr],
147///                ANSI_BG_COLOUR_ESCAPES[lower_clr]);
148///     }
149///     println!("{}{}", ANSI_COLOUR_ESCAPES[15], ANSI_BG_COLOUR_ESCAPES[0]);
150/// }
151/// # }
152/// ```
153pub fn create_colourtable<C: Index<usize, Output = u8>>(img: &DynamicImage, upper_colours: &[C], lower_colours: &[C]) -> Vec<Vec<(usize, usize)>> {
154    let (width, height) = img.dimensions();
155    let term_h = height / 2;
156
157    (0..term_h)
158        .map(|y| {
159            let upper_y = y * 2;
160            let lower_y = upper_y + 1;
161
162            (0..width)
163                .map(|x| (closest_colour(img.get_pixel(x, upper_y).to_rgb(), upper_colours), closest_colour(img.get_pixel(x, lower_y).to_rgb(), lower_colours)))
164                .collect()
165        })
166        .collect()
167}
168
169/// Display the specified image approximating it to the specified colours in the default console using ANSI escape codes.
170pub fn write_ansi<W: Write, C: Index<usize, Output = u8>>(out: &mut W, img: &DynamicImage, foreground_colours: &[C]) {
171    for line in create_colourtable(img, foreground_colours, &bg_colours_for(foreground_colours)) {
172        for (upper_clr, lower_clr) in line {
173            write!(out,
174                   "{}{}\u{2580}", // ▀
175                   ANSI_COLOUR_ESCAPES[upper_clr],
176                   ANSI_BG_COLOUR_ESCAPES[lower_clr])
177                .unwrap();
178        }
179        writeln!(out, "{}", ANSI_RESET_ATTRIBUTES).unwrap();
180    }
181}
182
183/// Display the specified image in the default console using ANSI 24-bit escape colour codes.
184pub fn write_ansi_truecolor<W: Write>(out: &mut W, img: &DynamicImage) {
185    let (width, height) = img.dimensions();
186    let term_h = height / 2;
187
188    for y in 0..term_h {
189        let upper_y = y * 2;
190        let lower_y = upper_y + 1;
191
192        for x in 0..width {
193            let upper_pixel = img.get_pixel(x, upper_y).to_rgb();
194            let lower_pixel = img.get_pixel(x, lower_y).to_rgb();
195
196            write!(out,
197                   "\x1B[38;2;{};{};{}m\
198                    \x1B[48;2;{};{};{}m\u{2580}", // ▀
199                   upper_pixel[0],
200                   upper_pixel[1],
201                   upper_pixel[2],
202                   lower_pixel[0],
203                   lower_pixel[1],
204                   lower_pixel[2])
205                .unwrap();
206        }
207        writeln!(out, "{}", ANSI_RESET_ATTRIBUTES).unwrap();
208    }
209}