Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 62 additions & 38 deletions src/lut_filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -31,58 +33,80 @@ 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 {
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,
]),
);
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() {
// 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 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
}

/// 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)
}
}

Expand Down