diff --git a/benches/imageops.rs b/benches/imageops.rs index 3f58892747..9ca8503001 100644 --- a/benches/imageops.rs +++ b/benches/imageops.rs @@ -18,7 +18,7 @@ pub fn bench_imageops(c: &mut Criterion) { }); c.bench_function("brighten", |b| { - b.iter(|| imageops::brighten(&src, 100)); + b.iter(|| imageops::brighten(&src, 0.3)); }); c.bench_function("contrast", |b| { diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index e85bd0b4c5..669a1a0bcd 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -149,11 +149,18 @@ where } /// Brighten the supplied image. -/// ```value``` is the amount to brighten each pixel by. -/// Negative values decrease the brightness and positive values increase it. /// -/// *[See also `brighten_in_place`.][brighten_in_place]* -pub fn brighten(image: &I, value: i32) -> ImageBuffer> +/// # Arguments +/// +/// - `value`: The amount between -1 and 1 to brighten each pixel by. +/// Negative values decrease the brightness and positive values increase it. +/// +/// A value of 1 will make all pixels white and a value of -1 will make all pixels black. 0 will do nothing. +/// +/// # See also +/// +/// - [`brighten_in_place`] for an in-place version of this function. +pub fn brighten(image: &I, value: f32) -> ImageBuffer> where I: GenericImageView, P: Pixel, @@ -161,54 +168,61 @@ where { let mut out = image.buffer_like(); - let max = S::DEFAULT_MAX_VALUE; - let max: i32 = NumCast::from(max).unwrap(); - + let apply = new_apply_brightness(value); for (x, y, pixel) in image.pixels() { - let e = pixel.map_with_alpha( - |b| { - let c: i32 = NumCast::from(b).unwrap(); - let d = clamp(c + value, 0, max); - - NumCast::from(d).unwrap() - }, - |alpha| alpha, - ); - out.put_pixel(x, y, e); + out.put_pixel(x, y, apply(pixel)); } out } /// Brighten the supplied image in place. -/// ```value``` is the amount to brighten each pixel by. -/// Negative values decrease the brightness and positive values increase it. /// -/// *[See also `brighten`.][brighten]* -pub fn brighten_in_place(image: &mut I, value: i32) +/// # Arguments +/// +/// - `value`: The amount between -1 and 1 to brighten each pixel by. +/// Negative values decrease the brightness and positive values increase it. +/// +/// A value of 1 will make all pixels white and a value of -1 will make all pixels black. 0 will do nothing. +/// +/// # See also +/// +/// - [`brighten`] for a non-destructive version of this function. +pub fn brighten_in_place(image: &mut I, value: f32) where I: GenericImage, { let (width, height) = image.dimensions(); - let max = ::Subpixel::DEFAULT_MAX_VALUE; - let max: i32 = NumCast::from(max).unwrap(); // TODO what does this do for f32? clamp at 1?? + let apply = new_apply_brightness(value); // TODO find a way to use pixels? for y in 0..height { for x in 0..width { - let e = image.get_pixel(x, y).map_with_alpha( - |b| { - let c: i32 = NumCast::from(b).unwrap(); - let d = clamp(c + value, 0, max); + image.put_pixel(x, y, apply(image.get_pixel(x, y))); + } + } +} - NumCast::from(d).unwrap() - }, - |alpha| alpha, - ); +fn new_apply_brightness(mut amount: f32) -> impl Fn(P) -> P { + let max: f32 = NumCast::from(P::Subpixel::DEFAULT_MAX_VALUE).unwrap(); + let min: f32 = NumCast::from(P::Subpixel::DEFAULT_MIN_VALUE).unwrap(); - image.put_pixel(x, y, e); - } + if amount.is_nan() { + amount = 0.0; + } + let amount = amount.clamp(-1.0, 1.0) * (max - min); + + move |pixel| { + pixel.map_with_alpha( + move |b| { + let c: f32 = NumCast::from(b).unwrap(); + let d = clamp(c + amount, min, max); + + NumCast::from(d).unwrap() + }, + |alpha| alpha, + ) } } @@ -617,9 +631,9 @@ mod test { ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = - ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap(); + ImageBuffer::from_raw(3, 2, vec![25u8, 26u8, 27u8, 35u8, 36u8, 37u8]).unwrap(); - assert_pixels_eq!(&brighten(&image, 10), &expected); + assert_pixels_eq!(&brighten(&image, 0.1), &expected); } #[test] @@ -628,9 +642,9 @@ mod test { ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap(); let expected: GrayImage = - ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 20u8, 21u8, 22u8]).unwrap(); + ImageBuffer::from_raw(3, 2, vec![25u8, 26u8, 27u8, 35u8, 36u8, 37u8]).unwrap(); - brighten_in_place(&mut image, 10); + brighten_in_place(&mut image, 0.1); assert_pixels_eq!(&image, &expected); } diff --git a/src/images/dynimage.rs b/src/images/dynimage.rs index d6a69d01c8..9b22dd1e9a 100644 --- a/src/images/dynimage.rs +++ b/src/images/dynimage.rs @@ -1059,13 +1059,22 @@ impl DynamicImage { } /// Brighten the pixels of this image. - /// `value` is the amount to brighten each pixel by. - /// Negative values decrease the brightness and positive values increase it. + /// + /// # Arguments + /// + /// - `value`: The amount between -1 and 1 to brighten each pixel by. + /// Negative values decrease the brightness and positive values increase it. + /// + /// A value of 1 will make all pixels white and a value of -1 will make all pixels black. 0 will do nothing. + /// + /// # Notes /// /// This method operates on pixel channel values directly without taking into account color /// space data. + /// + /// This operation keeps the alpha channel unchanged. #[must_use] - pub fn brighten(&self, value: i32) -> DynamicImage { + pub fn brighten(&self, value: f32) -> DynamicImage { dynamic_map!(*self, ref p => imageops::brighten(p, value)) } @@ -2222,7 +2231,7 @@ mod test { &|img| img.unsharpen(1.0, 3), &|img| img.filter3x3(&[0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0]), &|img| img.adjust_contrast(0.5), - &|img| img.brighten(10), + &|img| img.brighten(0.1), &|img| img.huerotate(180), ];