From d425cf1b2950de9aa228f1eb86fb1eacea662ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Str=C3=B6ker?= Date: Wed, 8 Oct 2025 10:27:56 +0200 Subject: [PATCH 1/3] Initial commit From 6995029a36e607ac573056201f93999adfb8e28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Str=C3=B6ker?= Date: Wed, 8 Oct 2025 12:15:18 +0200 Subject: [PATCH 2/3] lut optimizations --- src/lut_filters.rs | 66 ++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/src/lut_filters.rs b/src/lut_filters.rs index a73068c..6e2d7a0 100644 --- a/src/lut_filters.rs +++ b/src/lut_filters.rs @@ -17,6 +17,8 @@ /// - Learn when LUTs are appropriate use image::{ImageBuffer, Rgb, RgbImage}; +use crate::lut_filters::naive::apply_brightness_contrast_gamma_impl; + pub fn apply_brightness_contrast(img: &RgbImage, brightness: i16, contrast: f32) -> RgbImage { naive::apply_brightness_contrast(img, brightness, contrast) } @@ -31,35 +33,40 @@ pub fn apply_brightness_contrast_gamma( contrast: f32, gamma: f32, ) -> RgbImage { - let temp_img = apply_brightness_contrast(img, brightness, contrast); - naive::apply_gamma(&temp_img, gamma) + apply_brightness_contrast_gamma_impl(img, brightness, contrast, gamma) } mod naive { + use super::*; - /// Apply brightness and contrast with floating-point math per pixel - pub fn apply_brightness_contrast(img: &RgbImage, brightness: i16, contrast: f32) -> RgbImage { + pub fn apply_brightness_contrast_gamma_impl( + img: &RgbImage, + brightness: i16, + contrast: f32, + gamma: f32, + ) -> RgbImage { + let mut lut = [0u8; 256]; + for i in 0..256 { + let mut a = ((i as f32 - 128.0) * (1.0 + contrast)) + 128.0 + brightness as f32; + a = (a / 255.0).powf(1.0 / gamma) * 255.0; + lut[i] = a.clamp(0.0, 255.0).round() as u8; + } + apply_lut(img, &lut) + } + + pub fn apply_lut(img: &RgbImage, lut: &[u8; 256]) -> RgbImage { let (width, height) = img.dimensions(); let mut output = ImageBuffer::new(width, height); for (x, y, pixel) in img.enumerate_pixels() { - let r = pixel[0] as f32; - let g = pixel[1] as f32; - let b = pixel[2] as f32; - - // Apply contrast and brightness (5 FP ops per channel!) - let r = ((r - 128.0) * (1.0 + contrast)) + 128.0 + brightness as f32; - let g = ((g - 128.0) * (1.0 + contrast)) + 128.0 + brightness as f32; - let b = ((b - 128.0) * (1.0 + contrast)) + 128.0 + brightness as f32; - output.put_pixel( x, y, Rgb([ - r.clamp(0.0, 255.0) as u8, - g.clamp(0.0, 255.0) as u8, - b.clamp(0.0, 255.0) as u8, + lut[pixel[0] as usize], + lut[pixel[1] as usize], + lut[pixel[2] as usize], ]), ); } @@ -67,22 +74,25 @@ mod naive { output } + /// Apply brightness and contrast with floating-point math per pixel + pub fn apply_brightness_contrast(img: &RgbImage, brightness: i16, contrast: f32) -> RgbImage { + let mut lut = [0u8; 256]; + for i in 0..256 { + lut[i] = (((i as f32 - 128.0) * (1.0 + contrast)) + 128.0 + brightness as f32) + .clamp(0.0, 255.0) + .round() as u8; + } + apply_lut(img, &lut) + } + /// Naive implementation: Apply gamma correction /// This is VERY slow because powf() is expensive! pub fn apply_gamma(img: &RgbImage, gamma: f32) -> RgbImage { - let (width, height) = img.dimensions(); - let mut output = ImageBuffer::new(width, height); - - for (x, y, pixel) in img.enumerate_pixels() { - // powf() is VERY expensive - this is why we need a LUT! - let r = (pixel[0] as f32 / 255.0).powf(1.0 / gamma) * 255.0; - let g = (pixel[1] as f32 / 255.0).powf(1.0 / gamma) * 255.0; - let b = (pixel[2] as f32 / 255.0).powf(1.0 / gamma) * 255.0; - - output.put_pixel(x, y, Rgb([r as u8, g as u8, b as u8])); + let mut lut = [0u8; 256]; + for i in 0..256 { + lut[i] = ((i as f32 / 255.0).powf(1.0 / gamma) * 255.0).round() as u8; } - - output + apply_lut(img, &lut) } } From c20942e43482248f9755c16a42be76699d0a49f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Str=C3=B6ker?= Date: Wed, 8 Oct 2025 13:59:29 +0200 Subject: [PATCH 3/3] enumerate_pixels_mut after clone --- src/lut_filters.rs | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/lut_filters.rs b/src/lut_filters.rs index 6e2d7a0..7f9b507 100644 --- a/src/lut_filters.rs +++ b/src/lut_filters.rs @@ -55,21 +55,35 @@ mod naive { apply_lut(img, &lut) } + // pub fn apply_lut(img: &RgbImage, lut: &[u8; 256]) -> RgbImage { + // let (width, height) = img.dimensions(); + // let mut output = ImageBuffer::new(width, height); + + // for (x, y, pixel) in img.enumerate_pixels() { + // output.put_pixel( + // x, + // y, + // Rgb([ + // lut[pixel[0] as usize], + // lut[pixel[1] as usize], + // lut[pixel[2] as usize], + // ]), + // ); + // } + + // output + // } + pub fn apply_lut(img: &RgbImage, lut: &[u8; 256]) -> RgbImage { - let (width, height) = img.dimensions(); - let mut output = ImageBuffer::new(width, height); - - for (x, y, pixel) in img.enumerate_pixels() { - output.put_pixel( - x, - y, - Rgb([ - lut[pixel[0] as usize], - lut[pixel[1] as usize], - lut[pixel[2] as usize], - ]), - ); - } + let mut output = img.clone(); + + output.enumerate_pixels_mut().for_each(|(_, _, pixel)| { + *pixel = Rgb([ + lut[pixel[0] as usize], + lut[pixel[1] as usize], + lut[pixel[2] as usize], + ]); + }); output }