use image::GrayImage;
use crate::error::{OcrError, Result};
use crate::types::Image;
const RED_WEIGHT: u32 = 4899;
const GREEN_WEIGHT: u32 = 9617;
const BLUE_WEIGHT: u32 = 1868;
const FIXED_SHIFT: u32 = 14;
const ROUND_BIAS: u32 = 1 << (FIXED_SHIFT - 1);
const RGB_CHANNELS: usize = 3;
pub(crate) fn to_grayscale(image: &Image) -> Result<GrayImage> {
let rgb = image.as_rgb8();
let mut luma = Vec::with_capacity(rgb.len() / RGB_CHANNELS);
for pixel in rgb.chunks_exact(RGB_CHANNELS) {
let (red, green, blue) = (pixel[0] as u32, pixel[1] as u32, pixel[2] as u32);
let gray = (red * RED_WEIGHT + green * GREEN_WEIGHT + blue * BLUE_WEIGHT + ROUND_BIAS) >> FIXED_SHIFT;
luma.push(gray as u8);
}
GrayImage::from_raw(image.width(), image.height(), luma)
.ok_or_else(|| OcrError::image("grayscale buffer does not match the image dimensions"))
}
#[cfg(test)]
mod tests {
use super::*;
fn rgb_image(width: u32, height: u32, pixels: Vec<u8>) -> Image {
Image::from_rgb8(width, height, pixels).expect("valid rgb8 buffer")
}
#[test]
fn should_produce_one_luma_byte_per_pixel() {
let image = rgb_image(2, 2, vec![255u8; 2 * 2 * 3]);
let grey = to_grayscale(&image).expect("grayscale succeeds");
assert_eq!(grey.dimensions(), (2, 2));
assert_eq!(grey.as_raw().len(), 4);
}
#[test]
fn should_map_white_and_black_to_extremes() {
let image = rgb_image(2, 1, vec![255, 255, 255, 0, 0, 0]);
let grey = to_grayscale(&image).expect("grayscale succeeds");
assert_eq!(grey.get_pixel(0, 0)[0], 255);
assert_eq!(grey.get_pixel(1, 0)[0], 0);
}
#[test]
fn should_weight_green_above_red_above_blue() {
let image = rgb_image(3, 1, vec![255, 0, 0, 0, 255, 0, 0, 0, 255]);
let grey = to_grayscale(&image).expect("grayscale succeeds");
let (red, green, blue) = (
grey.get_pixel(0, 0)[0],
grey.get_pixel(1, 0)[0],
grey.get_pixel(2, 0)[0],
);
assert!(green > red && red > blue, "green {green} > red {red} > blue {blue}");
}
#[test]
fn should_match_opencv_fixed_point_luma_for_known_pixel() {
let image = rgb_image(1, 1, vec![100, 150, 200]);
let grey = to_grayscale(&image).expect("grayscale succeeds");
assert_eq!(grey.get_pixel(0, 0)[0], 141);
}
}