Skip to main content

trueno_image/
histogram.rs

1//! Histogram computation for grayscale images (NPP parity).
2
3use crate::error::ImageError;
4
5/// Compute histogram of a grayscale image with values in [0, 1].
6///
7/// Returns a histogram with `bins` buckets. Values outside [0, 1] are clamped.
8///
9/// # Errors
10///
11/// Returns error if dimensions don't match buffer or bins is zero.
12pub fn histogram(
13    image: &[f32],
14    width: usize,
15    height: usize,
16    bins: usize,
17) -> Result<Vec<u32>, ImageError> {
18    if image.len() != width * height {
19        return Err(ImageError::BufferLengthMismatch {
20            expected: width * height,
21            got: image.len(),
22            width,
23            height,
24        });
25    }
26    if bins == 0 {
27        return Err(ImageError::InvalidKernelSize { kw: bins, kh: 0 });
28    }
29
30    let mut hist = vec![0u32; bins];
31    let scale = bins as f32;
32    for &pixel in image {
33        let clamped = pixel.clamp(0.0, 1.0 - f32::EPSILON);
34        let bucket = (clamped * scale) as usize;
35        hist[bucket] += 1;
36    }
37
38    Ok(hist)
39}
40
41/// Compute cumulative histogram (CDF).
42pub fn cumulative_histogram(hist: &[u32]) -> Vec<u32> {
43    let mut cdf = vec![0u32; hist.len()];
44    if hist.is_empty() {
45        return cdf;
46    }
47    cdf[0] = hist[0];
48    for i in 1..hist.len() {
49        cdf[i] = cdf[i - 1] + hist[i];
50    }
51    cdf
52}
53
54/// Histogram equalization: remap image to uniform histogram.
55///
56/// # Errors
57///
58/// Returns error if dimensions don't match.
59pub fn equalize(
60    image: &[f32],
61    width: usize,
62    height: usize,
63    bins: usize,
64) -> Result<Vec<f32>, ImageError> {
65    let hist = histogram(image, width, height, bins)?;
66    let cdf = cumulative_histogram(&hist);
67    let total = (width * height) as f32;
68
69    let scale = bins as f32;
70    let mut output = vec![0.0f32; image.len()];
71    for (i, &pixel) in image.iter().enumerate() {
72        let clamped = pixel.clamp(0.0, 1.0 - f32::EPSILON);
73        let bucket = (clamped * scale) as usize;
74        output[i] = cdf[bucket] as f32 / total;
75    }
76
77    Ok(output)
78}