Last active
January 19, 2025 22:52
-
-
Save awxkee/6bf5ba717365dcfeb32d82486b0dfc08 to your computer and use it in GitHub Desktop.
Dynamic Image Histogram
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use image::{DynamicImage, ImageReader}; | |
| use num_traits::AsPrimitive; | |
| use std::fmt::Debug; | |
| #[derive(Clone, Debug)] | |
| pub struct ImageStatistics<A: Clone + Debug> { | |
| pub hist_bins: Vec<Vec<A>>, | |
| pub distinct_colors: u64, | |
| } | |
| trait ArgumentReducer<A, V> { | |
| fn reduce(&self, a: A) -> V; | |
| } | |
| #[derive(Clone, Default)] | |
| struct ArgumentReducerUnsigned8 {} | |
| #[derive(Clone, Default)] | |
| struct ArgumentReducerUnsigned16<const BIT_DEPTH: usize> {} | |
| #[derive(Clone, Default)] | |
| struct ArgumentReducerUnsigned16ToU8<const BIT_DEPTH: usize> {} | |
| #[derive(Clone, Default)] | |
| struct ArgumentReducerFloat32 {} | |
| #[derive(Clone, Default)] | |
| struct ArgumentReducerFloat32ToU8 {} | |
| impl ArgumentReducer<u8, u8> for ArgumentReducerUnsigned8 { | |
| #[inline(always)] | |
| fn reduce(&self, a: u8) -> u8 { | |
| a | |
| } | |
| } | |
| impl<const BIT_DEPTH: usize> ArgumentReducer<u16, u16> for ArgumentReducerUnsigned16<BIT_DEPTH> { | |
| #[inline(always)] | |
| fn reduce(&self, a: u16) -> u16 { | |
| if BIT_DEPTH == 16 { | |
| a | |
| } else { | |
| let max_colors = (1 << BIT_DEPTH) - 1; | |
| a.min(max_colors) | |
| } | |
| } | |
| } | |
| impl ArgumentReducer<f32, u16> for ArgumentReducerFloat32 { | |
| #[inline(always)] | |
| fn reduce(&self, a: f32) -> u16 { | |
| (a * (u16::MAX as f32)) as u16 | |
| } | |
| } | |
| impl<const BIT_DEPTH: usize> ArgumentReducer<u16, u8> for ArgumentReducerUnsigned16ToU8<BIT_DEPTH> { | |
| #[inline(always)] | |
| fn reduce(&self, a: u16) -> u8 { | |
| a as u8 | |
| } | |
| } | |
| impl ArgumentReducer<f32, u8> for ArgumentReducerFloat32ToU8 { | |
| #[inline(always)] | |
| fn reduce(&self, a: f32) -> u8 { | |
| (a * 255.) as u8 | |
| } | |
| } | |
| fn calculate_statistics_impl< | |
| A: Clone + Debug + Copy, | |
| V: Default, | |
| const CN: usize, | |
| const USEFUL_CN: usize, | |
| const BINS_DEPTH: usize, | |
| >( | |
| image: &[A], | |
| stride: usize, | |
| width: usize, | |
| reducer: /* Static dispatch is important here*/ impl ArgumentReducer<A, V>, | |
| distinct_colors_reducer: impl ArgumentReducer<A, u8>, | |
| ) -> ImageStatistics<u64> | |
| where | |
| V: AsPrimitive<usize> + AsPrimitive<u32>, | |
| { | |
| assert!(USEFUL_CN >= 1 && USEFUL_CN <= 3); | |
| assert!(CN >= 1 && CN <= 4); | |
| let mut working_row = vec![V::default(); USEFUL_CN * width]; | |
| let bins_count = 1 << BINS_DEPTH; | |
| let mut bin0 = vec![0u64; bins_count]; | |
| let mut bin1 = if USEFUL_CN > 1 { | |
| vec![0u64; bins_count] | |
| } else { | |
| vec![0u64; 0] | |
| }; | |
| let mut bin2 = if USEFUL_CN > 2 { | |
| vec![0u64; bins_count] | |
| } else { | |
| vec![0u64; 0] | |
| }; | |
| for row in image.chunks_exact(stride) { | |
| let fixed_row = &row[0..width * CN]; | |
| /* | |
| For high bit-depth, 8-bit it is essentially pass through, but for `f32` | |
| some quantization is needed. | |
| */ | |
| for (dst, src) in working_row | |
| .chunks_exact_mut(USEFUL_CN) | |
| .zip(fixed_row.chunks_exact(CN)) | |
| { | |
| dst[0] = reducer.reduce(src[0]); | |
| if USEFUL_CN > 1 { | |
| dst[1] = reducer.reduce(src[1]); | |
| } | |
| if USEFUL_CN > 2 { | |
| dst[2] = reducer.reduce(src[2]); | |
| } | |
| } | |
| // Calculate hist | |
| for chunk in working_row.chunks_exact_mut(USEFUL_CN) { | |
| let c0: usize = chunk[0].as_(); | |
| bin0[c0] += 1; | |
| if USEFUL_CN > 1 { | |
| let c1: usize = chunk[1].as_(); | |
| bin1[c1] += 1; | |
| } | |
| if USEFUL_CN > 2 { | |
| let c2: usize = chunk[2].as_(); | |
| bin2[c2] += 1; | |
| } | |
| } | |
| } | |
| working_row.resize(0, V::default()); | |
| let mut distinct_colors = 0u64; | |
| let mut d_working_row = vec![0u8; USEFUL_CN * width]; | |
| let mut distinct_map = vec![0u8; 1 << 21]; | |
| for row in image.chunks_exact(stride) { | |
| let fixed_row = &row[0..width * CN]; | |
| /* | |
| Distinct colors calculation is not straightforward, so quantization is always needed | |
| */ | |
| for (dst, src) in d_working_row | |
| .chunks_exact_mut(USEFUL_CN) | |
| .zip(fixed_row.chunks_exact(CN)) | |
| { | |
| dst[0] = distinct_colors_reducer.reduce(src[0]); | |
| if USEFUL_CN > 1 { | |
| dst[1] = distinct_colors_reducer.reduce(src[1]); | |
| } | |
| if USEFUL_CN > 2 { | |
| dst[2] = distinct_colors_reducer.reduce(src[2]); | |
| } | |
| } | |
| if USEFUL_CN == 1 { | |
| for &v in d_working_row.iter() { | |
| let full: u32 = v as u32 >> 3; | |
| let remainder = v as u32 - (full << 3); | |
| let bit = 1 << remainder; | |
| distinct_map[full as usize] |= bit; | |
| } | |
| } else if USEFUL_CN == 2 { | |
| for chunk in d_working_row.chunks_exact_mut(2) { | |
| let c0: u8 = chunk[0]; | |
| let c1: u8 = chunk[1]; | |
| let v_u32: u32 = u32::from_ne_bytes([c0, c1, 0, 0]); | |
| let full: u32 = v_u32 >> 3; | |
| let remainder = v_u32 - (full << 3); | |
| let bit = 1 << remainder; | |
| distinct_map[full as usize] |= bit; | |
| } | |
| } else if USEFUL_CN == 3 { | |
| for chunk in d_working_row.chunks_exact_mut(3) { | |
| let c0: u8 = chunk[0]; | |
| let c1: u8 = chunk[1]; | |
| let c2: u8 = chunk[2]; | |
| let v_u32: u32 = u32::from_ne_bytes([c0, c1, c2, 0]); | |
| let full: u32 = v_u32 >> 3; | |
| let remainder = v_u32 - (full << 3); | |
| let bit = 1 << remainder; | |
| distinct_map[full as usize] |= bit; | |
| } | |
| } | |
| } | |
| for &ones in distinct_map.iter() { | |
| distinct_colors += ones.count_ones() as u64; | |
| } | |
| let mut hist_bins = vec![]; | |
| hist_bins.push(bin0); | |
| if USEFUL_CN > 1 { | |
| hist_bins.push(bin1); | |
| } | |
| if USEFUL_CN > 2 { | |
| hist_bins.push(bin2); | |
| } | |
| ImageStatistics { | |
| hist_bins, | |
| distinct_colors, | |
| } | |
| } | |
| pub fn calculate_statistics(image: &DynamicImage) -> ImageStatistics<u64> { | |
| let width = image.width() as usize; | |
| let stride = image.width() as usize * image.color().channel_count() as usize; | |
| match image { | |
| DynamicImage::ImageLuma8(img) => calculate_statistics_impl::<u8, u8, 1, 1, 8>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned8::default(), | |
| ArgumentReducerUnsigned8::default(), | |
| ), | |
| DynamicImage::ImageLumaA8(img) => calculate_statistics_impl::<u8, u8, 2, 1, 8>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned8::default(), | |
| ArgumentReducerUnsigned8::default(), | |
| ), | |
| DynamicImage::ImageRgb8(img) => calculate_statistics_impl::<u8, u8, 3, 3, 8>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned8::default(), | |
| ArgumentReducerUnsigned8::default(), | |
| ), | |
| DynamicImage::ImageRgba8(img) => calculate_statistics_impl::<u8, u8, 4, 3, 8>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned8::default(), | |
| ArgumentReducerUnsigned8::default(), | |
| ), | |
| DynamicImage::ImageLuma16(img) => calculate_statistics_impl::<u16, u16, 1, 1, 16>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned16::<16>::default(), | |
| ArgumentReducerUnsigned16ToU8::<16>::default(), | |
| ), | |
| DynamicImage::ImageLumaA16(img) => calculate_statistics_impl::<u16, u16, 2, 1, 16>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned16::<16>::default(), | |
| ArgumentReducerUnsigned16ToU8::<16>::default(), | |
| ), | |
| DynamicImage::ImageRgb16(img) => calculate_statistics_impl::<u16, u16, 3, 3, 16>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned16::<16>::default(), | |
| ArgumentReducerUnsigned16ToU8::<16>::default(), | |
| ), | |
| DynamicImage::ImageRgba16(img) => calculate_statistics_impl::<u16, u16, 4, 3, 16>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerUnsigned16::<16>::default(), | |
| ArgumentReducerUnsigned16ToU8::<16>::default(), | |
| ), | |
| DynamicImage::ImageRgb32F(img) => calculate_statistics_impl::<f32, u16, 3, 3, 16>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerFloat32::default(), | |
| ArgumentReducerFloat32ToU8::default(), | |
| ), | |
| DynamicImage::ImageRgba32F(img) => calculate_statistics_impl::<f32, u16, 4, 3, 16>( | |
| &img, | |
| stride, | |
| width, | |
| ArgumentReducerFloat32::default(), | |
| ArgumentReducerFloat32ToU8::default(), | |
| ), | |
| _ => unimplemented!(), | |
| } | |
| } | |
| fn main() { | |
| let mut img = ImageReader::open("./assets/bench.png") | |
| .unwrap() | |
| .decode() | |
| .unwrap(); | |
| let img_f32 = DynamicImage::ImageRgba8(img.to_rgba8()); | |
| let hist = calculate_statistics(&img_f32); | |
| println!("Hist {:?}", hist); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment