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