Coverage Report

Created: 2026-08-31 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/primitive_sealed.rs
Line
Count
Source
1
//! Module for crate-private traits implemented for all primitive types.
2
3
use crate::imageops::fast_blur::BlurAccumulator;
4
5
/// Crate-private trait to seal the [`Primitive`](crate::Primitive) trait.
6
///
7
/// This trait is `pub` but not exported, so it cannot be implemented outside
8
/// this crate.
9
#[allow(private_bounds)]
10
pub trait PrimitiveSealed:
11
    Sized + NearestFrom<f32> + WithBlurAcc + BgraSwizzle + RgbToLuma
12
{
13
}
14
15
impl PrimitiveSealed for usize {}
16
impl PrimitiveSealed for u8 {}
17
impl PrimitiveSealed for u16 {}
18
impl PrimitiveSealed for u32 {}
19
impl PrimitiveSealed for u64 {}
20
impl PrimitiveSealed for isize {}
21
impl PrimitiveSealed for i8 {}
22
impl PrimitiveSealed for i16 {}
23
impl PrimitiveSealed for i32 {}
24
impl PrimitiveSealed for i64 {}
25
impl PrimitiveSealed for f32 {}
26
impl PrimitiveSealed for f64 {}
27
28
/// Defines specialized methods for rgb<->bgr and rgba<->bgra swizzles
29
///
30
/// By default, uses as_chunks_mut and swaps the first and third elements in the pixel slice.
31
/// For u8 rgba however, benchmarks have shown that interpreting the 4 bytes as a u32 and swap+rotate
32
/// ends up autovectorizing better.
33
// Note: no attempts have been made to find if a similar optimization could apply to primitives beyond u8 or to bgr instead of bgra.
34
pub(crate) trait BgraSwizzle: Sized {
35
198
    fn swizzle_rgb_bgr(pixels: &mut [Self]) {
36
8.50M
        for pixel in pixels.as_chunks_mut::<3>().0 {
37
8.50M
            pixel.swap(0, 2);
38
8.50M
        }
39
198
    }
40
0
    fn swizzle_rgba_bgra(pixels: &mut [Self]) {
41
0
        for pix in pixels.as_chunks_mut::<4>().0 {
42
0
            pix.swap(0, 2);
43
0
        }
44
0
    }
45
}
46
47
impl BgraSwizzle for usize {}
48
impl BgraSwizzle for u8 {
49
87
    fn swizzle_rgba_bgra(pixels: &mut [Self]) {
50
3.75M
        for pix in pixels.as_chunks_mut::<4>().0 {
51
3.75M
            let bgra = u32::from_be_bytes(*pix);
52
3.75M
            let argb = bgra.swap_bytes(); // reverses order of channels (bytes)
53
3.75M
            let rgba = argb.rotate_left(8); // rotate first byte to last place
54
3.75M
            *pix = rgba.to_be_bytes();
55
3.75M
        }
56
87
    }
57
}
58
impl BgraSwizzle for u16 {}
59
impl BgraSwizzle for u32 {}
60
impl BgraSwizzle for u64 {}
61
impl BgraSwizzle for isize {}
62
impl BgraSwizzle for i8 {}
63
impl BgraSwizzle for i16 {}
64
impl BgraSwizzle for i32 {}
65
impl BgraSwizzle for i64 {}
66
impl BgraSwizzle for f32 {}
67
impl BgraSwizzle for f64 {}
68
69
/// Returns the nearest value of `Self` to a given value.
70
///
71
/// Properties:
72
/// - For a float -> int conversion:
73
///     - The float is rounded to the nearest integer.
74
///       (An implementation may use a fast approximation instead of precise rounding.)
75
///     - NaN is mapped to 0.
76
///     - Values outside the range of the integer type are clamped to the min or max value.
77
/// - For a float -> float conversion:
78
///     - The float is clamped to the range `[0.0, 1.0]`.
79
///     - NaN is mapped to 0.0.
80
pub(crate) trait NearestFrom<T> {
81
    /// Returns the nearest value of `Self` to `value`.
82
    ///
83
    /// Properties:
84
    /// - For a float -> int conversion:
85
    ///     - The float is rounded to the nearest integer.
86
    ///       (An implementation may use a fast approximation instead of precise rounding.)
87
    ///     - NaN is mapped to 0.
88
    ///     - Values outside the range of the integer type are clamped to the min or max value.
89
    /// - For a float -> float conversion:
90
    ///     - Values outside are clamped to +-inf.
91
    ///     - NaN is kept as NaN.
92
    ///     - Precision may be lost.
93
    fn nearest_from(value: T) -> Self;
94
95
    /// Returns the nearest value of `Self` to `value` *within* the default
96
    /// range of `Self`. This range is 0..=1 for floats and the full range of
97
    /// the integer type for integers.
98
    ///
99
    /// If `Self` is an integer type, this is the same as `nearest_from`.
100
    ///
101
    /// Properties:
102
    /// - For a float -> int conversion: Same as `nearest_from`.
103
    /// - For a float -> float conversion:
104
    ///     - The float is clamped to the range `[0.0, 1.0]`.
105
    ///     - NaN is mapped to 0.0.
106
    ///     - Precision may be lost.
107
    fn clamp_nearest_from(value: T) -> Self;
108
}
109
110
impl NearestFrom<f32> for u8 {
111
0
    fn nearest_from(value: f32) -> Self {
112
        // Approximate rounding using the well-known + 0.5 trick.
113
        // This does not handle certain cases correctly. E.g. `0.5_f32.nextdown()`
114
        // is incorrectly rounded to 1 instead of 0. However, this isn't typically
115
        // an issue in practice.
116
0
        (value + 0.5) as u8
117
0
    }
118
0
    fn clamp_nearest_from(value: f32) -> Self {
119
0
        Self::nearest_from(value)
120
0
    }
121
}
122
impl NearestFrom<f32> for u16 {
123
0
    fn nearest_from(value: f32) -> Self {
124
0
        (value + 0.5) as u16
125
0
    }
126
0
    fn clamp_nearest_from(value: f32) -> Self {
127
0
        Self::nearest_from(value)
128
0
    }
129
}
130
impl NearestFrom<f32> for f32 {
131
0
    fn nearest_from(value: f32) -> Self {
132
0
        value
133
0
    }
134
    #[allow(clippy::manual_clamp)] // to map NaN to 0.0
135
0
    fn clamp_nearest_from(value: f32) -> Self {
136
0
        value.max(0.0).min(1.0)
137
0
    }
138
}
139
impl NearestFrom<f32> for f64 {
140
0
    fn nearest_from(value: f32) -> Self {
141
0
        value as f64
142
0
    }
143
    #[allow(clippy::manual_clamp)] // to map NaN to 0.0
144
0
    fn clamp_nearest_from(value: f32) -> Self {
145
0
        value.max(0.0).min(1.0) as f64
146
0
    }
147
}
148
149
macro_rules! impl_nearest_from_f32_for_ints {
150
    ($($t:ty),+) => { $(
151
        impl NearestFrom<f32> for $t {
152
0
            fn nearest_from(value: f32) -> Self {
153
0
                value.round() as $t
154
0
            }
Unexecuted instantiation: <u32 as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <u64 as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <usize as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <i8 as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <i16 as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <i32 as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <i64 as image::primitive_sealed::NearestFrom<f32>>::nearest_from
Unexecuted instantiation: <isize as image::primitive_sealed::NearestFrom<f32>>::nearest_from
155
0
            fn clamp_nearest_from(value: f32) -> Self {
156
0
                Self::nearest_from(value)
157
0
            }
Unexecuted instantiation: <u32 as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <u64 as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <usize as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <i8 as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <i16 as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <i32 as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <i64 as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
Unexecuted instantiation: <isize as image::primitive_sealed::NearestFrom<f32>>::clamp_nearest_from
158
        }
159
    )+ };
160
}
161
impl_nearest_from_f32_for_ints!(u32, u64, usize, i8, i16, i32, i64, isize);
162
163
/// Crate-private companion to [`Primitive`] that picks the box-blur
164
/// accumulator type for each primitive.
165
///
166
/// `u8` uses an integer (`u32`) accumulator for speed; everything else goes
167
/// through `f32`.
168
pub(crate) trait WithBlurAcc: Sized {
169
    type BlurAcc: BlurAccumulator<Self>;
170
}
171
172
impl WithBlurAcc for u8 {
173
    type BlurAcc = u32;
174
}
175
176
macro_rules! impl_with_blur_acc_f32 {
177
    ($($t:ty),+) => { $(
178
        impl WithBlurAcc for $t {
179
            type BlurAcc = f32;
180
        }
181
    )+ };
182
}
183
184
impl_with_blur_acc_f32!(u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
185
186
/// Converts sRGB to luma using standard coefficients for all primitives.
187
pub(crate) trait RgbToLuma: Copy + Sized + crate::traits::Enlargeable {
188
    #[inline]
189
0
    fn rgb_to_luma(r: Self, g: Self, b: Self) -> Self {
190
        use num_traits::NumCast;
191
192
        /// Coefficients to transform from sRGB to a CIE Y (luminance) value.
193
        const SRGB_LUMA: [u32; 3] = [2126, 7152, 722];
194
        const SRGB_LUMA_DIV: u32 = 10000;
195
196
0
        let l = <Self::Larger as NumCast>::from(SRGB_LUMA[0]).unwrap() * r.to_larger()
197
0
            + <Self::Larger as NumCast>::from(SRGB_LUMA[1]).unwrap() * g.to_larger()
198
0
            + <Self::Larger as NumCast>::from(SRGB_LUMA[2]).unwrap() * b.to_larger();
199
0
        Self::clamp_from(l / <Self::Larger as NumCast>::from(SRGB_LUMA_DIV).unwrap())
200
0
    }
201
}
202
impl RgbToLuma for usize {}
203
impl RgbToLuma for u8 {
204
0
    fn rgb_to_luma(r: u8, g: u8, b: u8) -> u8 {
205
        // The following constants give the same results as:
206
        //   ((r as u32 * 2126 + g as u32 * 7152 + b as u32 * 722 + 5000) / 10000) as u8
207
        // Note that results are correctly rounded to the nearest integer.
208
        const W_R: u32 = ((1_u64 << 24) * 2126).div_ceil(10000) as u32;
209
        const W_G: u32 = ((1_u64 << 24) * 7152).div_ceil(10000) as u32;
210
        const W_B: u32 = ((1_u64 << 24) * 722).div_ceil(10000) as u32;
211
0
        ((r as u32 * W_R + g as u32 * W_G + b as u32 * W_B + 0x800000) >> 24) as u8
212
0
    }
213
}
214
impl RgbToLuma for u16 {}
215
impl RgbToLuma for u32 {}
216
impl RgbToLuma for u64 {}
217
impl RgbToLuma for isize {}
218
impl RgbToLuma for i8 {}
219
impl RgbToLuma for i16 {}
220
impl RgbToLuma for i32 {}
221
impl RgbToLuma for i64 {}
222
impl RgbToLuma for f32 {
223
    #[inline]
224
0
    fn rgb_to_luma(r: f32, g: f32, b: f32) -> f32 {
225
        const SCALE_R: f32 = 2126. / 10000.;
226
        const SCALE_G: f32 = 7152. / 10000.;
227
        const SCALE_B: f32 = 722. / 10000.;
228
0
        SCALE_R * r + SCALE_G * g + SCALE_B * b
229
0
    }
230
}
231
impl RgbToLuma for f64 {}