hdim_render/lib.rs
1//! High-performance terminal image renderer.
2//!
3//! This crate handles the translation of [DynamicImage] buffers into ANSI escape sequences
4//! suitable for display in a terminal emulator. It uses "half-block" characters (`â–„`)
5//! to effectively double the vertical resolution of the terminal grid.
6
7pub mod pixel;
8pub mod view;
9
10use anyhow::Result;
11use image::DynamicImage;
12use std::fmt::Write;
13
14use self::pixel::get_average_rgb;
15pub use self::view::View;
16
17/// Renders a portion of an image to an ANSI string using half-block characters.
18///
19/// This function maps the source image pixels to terminal cells based on the provided [View].
20/// It calculates the average color for the top and bottom half of each character cell
21/// to determine the foreground and background colors.
22///
23/// # Arguments
24///
25/// * `image` - The source [DynamicImage] to render.
26/// * `view` - The [View] configuration defining the source region and target dimensions.
27///
28/// # Errors
29///
30/// Returns an error if writing to the output string fails (though this is unlikely with `String`).
31///
32/// # Examples
33///
34/// ```no_run
35/// use hdim_render::{render, View};
36/// use image::DynamicImage;
37///
38/// let img = DynamicImage::new_rgba8(100, 100);
39/// let view = View {
40/// source_x: 0,
41/// source_y: 0,
42/// source_width: 100,
43/// source_height: 100,
44/// target_width: 50,
45/// target_height: 25,
46/// };
47/// let output = render(&img, &view).unwrap();
48/// println!("{}", output);
49/// ```
50pub fn render(image: &DynamicImage, view: &View) -> Result<String> {
51 let mut output = String::new();
52 let (x_ratio, y_ratio) = view.calculate_scaling();
53 let (block_width, block_height) = view.calculate_block_size(x_ratio, y_ratio);
54
55 for y in 0..view.target_height {
56 for x in 0..view.target_width {
57 let (source_x, source_y_top, source_y_bottom) =
58 view.map_to_source(x, y, x_ratio, y_ratio);
59
60 let (top_color, bottom_color) = calculate_cell_colors(
61 image,
62 source_x,
63 source_y_top,
64 source_y_bottom,
65 block_width,
66 block_height,
67 );
68
69 write_ansi_cell(&mut output, top_color, bottom_color)?;
70 }
71 output.push_str("\x1b[0m\n");
72 }
73 Ok(output)
74}
75
76/// Helper to calculate the average RGB values for the top and bottom halves of a character cell.
77fn calculate_cell_colors(
78 image: &DynamicImage,
79 source_x: u32,
80 source_y_top: u32,
81 source_y_bottom: u32,
82 block_width: u32,
83 block_height: u32,
84) -> ([u8; 3], [u8; 3]) {
85 let top_color = get_average_rgb(image, source_x, source_y_top, block_width, block_height);
86 let bottom_color = get_average_rgb(image, source_x, source_y_bottom, block_width, block_height);
87 (top_color, bottom_color)
88}
89
90/// Writes a single ANSI-colored half-block character to the output buffer.
91fn write_ansi_cell(output: &mut String, top: [u8; 3], bottom: [u8; 3]) -> Result<()> {
92 write!(
93 output,
94 "\x1b[48;2;{};{};{}m\x1b[38;2;{};{};{}mâ–„",
95 top[0], top[1], top[2], bottom[0], bottom[1], bottom[2]
96 )?;
97 Ok(())
98}