use crate::config::Config;
use crate::grid::{Cell, Rgb};
use crate::ramp::RampType;
#[inline]
pub fn get_pixel_rgb(img_buf: &[u8], img_width: u32, x: u32, y: u32, channels: usize) -> Rgb {
let offset = ((y * img_width + x) as usize) * channels;
if offset + 2 < img_buf.len() {
Rgb::new(img_buf[offset], img_buf[offset + 1], img_buf[offset + 2])
} else {
Rgb::BLACK
}
}
#[inline]
pub fn map_block_geometry(
col: u32,
row: u32,
grid_width: u32,
grid_height: u32,
img_width: u32,
img_height: u32,
) -> (u32, u32, u32, u32) {
let x_start = (col as u64 * img_width as u64 / grid_width as u64) as u32;
let mut x_end = (((col + 1) as u64 * img_width as u64) / grid_width as u64) as u32;
x_end = x_end.max(x_start + 1).min(img_width);
let y_start = (row as u64 * img_height as u64 / grid_height as u64) as u32;
let mut y_end = (((row + 1) as u64 * img_height as u64) / grid_height as u64) as u32;
y_end = y_end.max(y_start + 1).min(img_height);
(x_start, x_end, y_start, y_end)
}
#[inline]
pub fn average_block(
img_buf: &[u8],
img_width: u32,
channels: usize,
x_start: u32,
x_end: u32,
y_start: u32,
y_end: u32,
) -> Rgb {
let mut sum_r: u64 = 0;
let mut sum_g: u64 = 0;
let mut sum_b: u64 = 0;
for py in y_start..y_end {
for px in x_start..x_end {
let pixel = get_pixel_rgb(img_buf, img_width, px, py, channels);
sum_r += pixel.r as u64;
sum_g += pixel.g as u64;
sum_b += pixel.b as u64;
}
}
let count = ((x_end - x_start) * (y_end - y_start)) as u64;
if count == 0 {
return Rgb::BLACK;
}
Rgb::new(
(sum_r / count) as u8,
(sum_g / count) as u8,
(sum_b / count) as u8,
)
}
pub fn fill_row(
row_idx: u32,
row_cells: &mut [Cell],
img_buf: &[u8],
img_width: u32,
img_height: u32,
channels: usize,
grid_width: u32,
grid_height: u32,
config: &Config,
ramp_chars: &[char],
) {
let is_light = matches!(config.ramp, RampType::Light(_));
let max_idx = ramp_chars.len().saturating_sub(1) as f32;
for col in 0..grid_width {
let (x_start, x_end, y_start, y_end) =
map_block_geometry(col, row_idx, grid_width, grid_height, img_width, img_height);
let avg_rgb = average_block(img_buf, img_width, channels, x_start, x_end, y_start, y_end);
let mut lum = config
.luminance_method
.calculate(avg_rgb.r, avg_rgb.g, avg_rgb.b);
if is_light {
lum = 1.0 - lum;
}
let lum = lum.clamp(0.0, 1.0);
let idx = (lum * max_idx).round() as usize;
let ch = ramp_chars.get(idx).copied().unwrap_or(' ');
if let Some(cell) = row_cells.get_mut(col as usize) {
*cell = Cell::new(ch, avg_rgb);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pixel_and_block_sampling() {
let img_buf = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255];
let oob = get_pixel_rgb(&img_buf, 2, 5, 5, 3);
assert_eq!(oob, Rgb::BLACK);
let (x_start, x_end, y_start, y_end) = map_block_geometry(0, 0, 1, 1, 2, 2);
assert_eq!((x_start, x_end, y_start, y_end), (0, 2, 0, 2));
let avg = average_block(&img_buf, 2, 3, x_start, x_end, y_start, y_end);
assert_eq!(avg, Rgb::new(127, 127, 127));
}
}