px2ansi_rs/lib.rs
1#![allow(clippy::multiple_crate_versions)]
2use image::{DynamicImage, GenericImageView, Rgba};
3use std::io::Write;
4
5/// This function iterates over the image pixels, processing them in vertical pairs
6/// to utilize the "Upper Half Block" (▀) character.
7/// # Errors
8/// Returns an error if writing to the output stream fails
9pub fn write_ansi_art<W: Write>(img: &DynamicImage, out: &mut W) -> std::io::Result<()> {
10 let (width, height) = img.dimensions();
11
12 // Iterate 2 rows at a time because each ANSI block character represents 2 vertical pixels.
13 for y in (0..height).step_by(2) {
14 for x in 0..width {
15 let px1 = img.get_pixel(x, y);
16
17 // Handle edge case: odd height images have no bottom pixel on the last row
18 let px2 = if y + 1 < height {
19 img.get_pixel(x, y + 1)
20 } else {
21 Rgba([0, 0, 0, 0]) // Treat as transparent
22 };
23
24 write_pixels(out, px1, px2)?;
25 }
26 // Reset color at the end of the row and add a newline
27 writeln!(out, "\x1b[0m")?;
28 }
29
30 Ok(())
31}
32
33/// Helper to write the ANSI sequence for a single character block (2 vertical pixels).
34fn write_pixels<W: Write>(out: &mut W, top: Rgba<u8>, bot: Rgba<u8>) -> std::io::Result<()> {
35 let top_alpha = top[3];
36 let bot_alpha = bot[3];
37
38 if top_alpha > 0 {
39 // CASE 1: Top pixel is visible
40 // Set foreground color to Top Pixel
41 write!(out, "\x1b[38;2;{};{};{}m", top[0], top[1], top[2])?;
42
43 if bot_alpha > 0 {
44 // CASE 1.1: Both pixels visible
45 // Set background to Bottom Pixel and print "Upper Half Block" (▀).
46 write!(out, "\x1b[48;2;{};{};{}m▀", bot[0], bot[1], bot[2])
47 } else {
48 // CASE 1.2: Only top visible
49 // Reset background (transparent) and print "Upper Half Block" (▀).
50 write!(out, "\x1b[49m▀")
51 }
52 } else {
53 // CASE 2: Top pixel is transparent
54 if bot_alpha > 0 {
55 // CASE 2.1: Only bottom visible
56 // Set foreground to Bottom Pixel and print "Lower Half Block" (▄).
57 // We use reset background (\x1b[49m) to ensure top half is transparent.
58 write!(out, "\x1b[38;2;{};{};{}m\x1b[49m▄", bot[0], bot[1], bot[2])
59 } else {
60 // CASE 2.2: Both transparent
61 // Print a space with reset colors
62 write!(out, "\x1b[0m ")
63 }
64 }
65}