1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
use image::{DynamicImage, GenericImageView, ImageBuffer};
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};

use crate::util::transpose_vec;

#[inline(always)]
fn scale_pixel(pixel: &f32, min: f32, max: f32) -> f32 {
    (pixel - min) * (1. / (max - min))
}

/// Contrast stretch (normalize) a given image.
pub fn apply_histogram_stretch(image: &DynamicImage) -> DynamicImage {
    let (width, height) = image.dimensions();

    let image_buffer = image.to_rgb32f();
    let image_buffer = image_buffer.as_raw();

    let pixel_wise_channels = image_buffer
        .chunks_exact(3)
        .map(|chunk| -> Vec<f32> { chunk.to_vec() })
        .collect();

    let channel_wise_pixels = transpose_vec(&pixel_wise_channels);

    let channel_wise_pixels: Vec<Vec<f32>> = channel_wise_pixels
        .par_iter()
        .map(|channel| {
            let input_max_value = channel.iter().copied().reduce(f32::max).unwrap();
            let input_min_value = channel.iter().copied().reduce(f32::min).unwrap();

            let pixels: Vec<f32> = channel
                .iter()
                .map(|pixel| scale_pixel(pixel, input_min_value, input_max_value))
                .collect();

            pixels
        })
        .collect();

    let pixels_buf: Vec<f32> = transpose_vec(&channel_wise_pixels)
        .iter()
        .flatten()
        .copied()
        .collect();

    DynamicImage::ImageRgb32F(ImageBuffer::from_vec(width, height, pixels_buf).unwrap())
}