trueno_image/
histogram.rs1use crate::error::ImageError;
4
5pub 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
41pub 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
54pub 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}