diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index 02ca8a277d..c45ffc7c99 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -1,6 +1,6 @@ //! Image Processing Functions use crate::math::Rect; -use crate::traits::{Lerp, Pixel, Primitive}; +use crate::traits::{Pixel, Primitive}; use crate::{GenericImage, GenericImageView, SubImage}; /// Affine transformations @@ -257,14 +257,11 @@ pub fn vertical_gradient(img: &mut I, start: &P, stop: &P) where I: GenericImage, P: Pixel, - S: Primitive + Lerp, + S: Primitive, { for y in 0..img.height() { - let pixel = start.map2(stop, |a, b| { - let y = ::from(y).unwrap(); - let height = ::from(img.height() - 1).unwrap(); - S::lerp(a, b, y / height) - }); + let ratio = y as f32 / (img.height() - 1) as f32; + let pixel = start.map2(stop, |a, b| S::lerp(a, b, ratio)); for x in 0..img.width() { img.put_pixel(x, y, pixel); @@ -290,14 +287,11 @@ pub fn horizontal_gradient(img: &mut I, start: &P, stop: &P) where I: GenericImage, P: Pixel, - S: Primitive + Lerp, + S: Primitive, { for x in 0..img.width() { - let pixel = start.map2(stop, |a, b| { - let x = ::from(x).unwrap(); - let width = ::from(img.width() - 1).unwrap(); - S::lerp(a, b, x / width) - }); + let ratio = x as f32 / (img.width() - 1) as f32; + let pixel = start.map2(stop, |a, b| S::lerp(a, b, ratio)); for y in 0..img.height() { img.put_pixel(x, y, pixel); diff --git a/src/primitive_sealed.rs b/src/primitive_sealed.rs index 98e1a57eac..b8ef622191 100644 --- a/src/primitive_sealed.rs +++ b/src/primitive_sealed.rs @@ -7,7 +7,7 @@ use crate::imageops::fast_blur::BlurAccumulator; /// This trait is `pub` but not exported, so it cannot be implemented outside /// this crate. #[allow(private_bounds)] -pub trait PrimitiveSealed: Sized + NearestFrom + WithBlurAcc + BgraSwizzle {} +pub trait PrimitiveSealed: Sized + NearestFrom + WithBlurAcc + BgraSwizzle + Lerp {} impl PrimitiveSealed for usize {} impl PrimitiveSealed for u8 {} @@ -179,3 +179,42 @@ macro_rules! impl_with_blur_acc_f32 { } impl_with_blur_acc_f32!(u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64); + +/// Linear interpolation without loss of precision due to `f32`. +pub(crate) trait Lerp: Sized { + fn lerp(a: Self, b: Self, ratio: f32) -> Self; +} + +macro_rules! impl_lerp_with_f32 { + ($($t:ty),+) => { $( + impl Lerp for $t { + fn lerp(a: Self, b: Self, ratio: f32) -> Self { + let a = a as f32; + let b = b as f32; + let res = a + (b - a) * ratio; + NearestFrom::nearest_from(res) + } + } + )+ }; +} +impl_lerp_with_f32!(u8, u16, i8, i16, f32); + +macro_rules! impl_lerp_with_f64_int { + ($($t:ty),+) => { $( + impl Lerp for $t { + fn lerp(a: Self, b: Self, ratio: f32) -> Self { + let a = a as f64; + let b = b as f64; + let res = a + (b - a) * ratio as f64; + res.round() as Self + } + } + )+ }; +} +impl_lerp_with_f64_int!(u32, u64, usize, i32, i64, isize); + +impl Lerp for f64 { + fn lerp(a: Self, b: Self, ratio: f32) -> Self { + a + (b - a) * ratio as f64 + } +} diff --git a/src/traits.rs b/src/traits.rs index 98b172f743..592e4fdbae 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -130,46 +130,6 @@ impl Enlargeable for f64 { type Larger = f64; } -/// Linear interpolation without involving floating numbers. -pub trait Lerp: Bounded + NumCast { - type Ratio: Primitive; - - fn lerp(a: Self, b: Self, ratio: Self::Ratio) -> Self { - let a = ::from(a).unwrap(); - let b = ::from(b).unwrap(); - - let res = a + (b - a) * ratio; - - if res > NumCast::from(Self::max_value()).unwrap() { - Self::max_value() - } else if res < NumCast::from(0).unwrap() { - NumCast::from(0).unwrap() - } else { - NumCast::from(res).unwrap() - } - } -} - -impl Lerp for u8 { - type Ratio = f32; -} - -impl Lerp for u16 { - type Ratio = f32; -} - -impl Lerp for u32 { - type Ratio = f64; -} - -impl Lerp for f32 { - type Ratio = f32; - - fn lerp(a: Self, b: Self, ratio: Self::Ratio) -> Self { - a + (b - a) * ratio - } -} - /// The pixel with an associated `ColorType`. /// Not all possible pixels represent one of the predefined `ColorType`s. pub trait PixelWithColorType: