/src/image/src/utils/mod.rs
Line | Count | Source |
1 | | //! Utilities |
2 | | |
3 | | use std::collections::TryReserveError; |
4 | | |
5 | | /// Expand a buffer of packed 1, 2, or 4 bits integers into u8's. Assumes that |
6 | | /// every `row_size` entries there are padding bits up to the next byte boundary. |
7 | | #[allow(dead_code)] |
8 | | // When no image formats that use it are enabled |
9 | 0 | pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec<u8> { |
10 | | // Note: this conversion assumes that the scanlines begin on byte boundaries |
11 | 0 | let mask = (1u8 << bit_depth as usize) - 1; |
12 | 0 | let scaling_factor = 255 / ((1 << bit_depth as usize) - 1); |
13 | 0 | let bit_width = row_size * u32::from(bit_depth); |
14 | 0 | let skip = if bit_width.is_multiple_of(8) { |
15 | 0 | 0 |
16 | | } else { |
17 | 0 | (8 - bit_width % 8) / u32::from(bit_depth) |
18 | | }; |
19 | 0 | let row_len = row_size + skip; |
20 | 0 | let mut p = Vec::new(); |
21 | 0 | let mut i = 0; |
22 | 0 | for v in buf { |
23 | 0 | for shift_inv in 1..=8 / bit_depth { |
24 | 0 | let shift = 8 - bit_depth * shift_inv; |
25 | | // skip the pixels that can be neglected because scanlines should |
26 | | // start at byte boundaries |
27 | 0 | if i % (row_len as usize) < (row_size as usize) { |
28 | 0 | let pixel = (v & (mask << shift as usize)) >> shift as usize; |
29 | 0 | p.push(pixel * scaling_factor); |
30 | 0 | } |
31 | 0 | i += 1; |
32 | | } |
33 | | } |
34 | 0 | p |
35 | 0 | } |
36 | | |
37 | | #[inline(always)] |
38 | 0 | pub(crate) fn interleave_planes(out: &mut [u8], color: crate::ColorType, planes: &[&[u8]]) { |
39 | | #[track_caller] |
40 | 0 | pub(crate) fn trampoline<const PLANES: usize, const N: usize>( |
41 | 0 | out: &mut [u8], |
42 | 0 | planes: &[&[u8]], |
43 | 0 | ) { |
44 | 0 | interleave_planes_inner::<PLANES, N>( |
45 | 0 | out.as_chunks_mut::<N>().0, |
46 | 0 | <[_; PLANES]>::try_from(planes) |
47 | 0 | .unwrap() |
48 | 0 | .map(|arr| arr.as_chunks::<N>().0), Unexecuted instantiation: image::utils::interleave_planes::trampoline::<1, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<1, 2>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<1, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<2, 2>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<2, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<2, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<3, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<3, 2>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<3, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<4, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<4, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes::trampoline::<4, 2>::{closure#0} |
49 | | ) |
50 | 0 | } Unexecuted instantiation: image::utils::interleave_planes::trampoline::<1, 1> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<1, 2> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<1, 4> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<2, 2> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<2, 1> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<2, 4> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<3, 1> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<3, 2> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<3, 4> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<4, 4> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<4, 1> Unexecuted instantiation: image::utils::interleave_planes::trampoline::<4, 2> |
51 | | |
52 | 0 | assert_eq!(planes.len(), usize::from(color.channel_count())); |
53 | | |
54 | 0 | match color { |
55 | 0 | crate::ColorType::L8 => trampoline::<1, 1>(out, planes), |
56 | 0 | crate::ColorType::La8 => trampoline::<2, 1>(out, planes), |
57 | 0 | crate::ColorType::Rgb8 => trampoline::<3, 1>(out, planes), |
58 | 0 | crate::ColorType::Rgba8 => trampoline::<4, 1>(out, planes), |
59 | 0 | crate::ColorType::L16 => trampoline::<1, 2>(out, planes), |
60 | 0 | crate::ColorType::La16 => trampoline::<2, 2>(out, planes), |
61 | 0 | crate::ColorType::Rgb16 => trampoline::<3, 2>(out, planes), |
62 | 0 | crate::ColorType::Rgba16 => trampoline::<4, 2>(out, planes), |
63 | 0 | crate::ColorType::L32F => trampoline::<1, 4>(out, planes), |
64 | 0 | crate::ColorType::La32F => trampoline::<2, 4>(out, planes), |
65 | 0 | crate::ColorType::Rgb32F => trampoline::<3, 4>(out, planes), |
66 | 0 | crate::ColorType::Rgba32F => trampoline::<4, 4>(out, planes), |
67 | | } |
68 | 0 | } |
69 | | |
70 | | #[inline(always)] |
71 | 0 | fn interleave_planes_inner<const PLANES: usize, const N: usize>( |
72 | 0 | out: &mut [[u8; N]], |
73 | 0 | planes: [&[[u8; N]]; PLANES], |
74 | 0 | ) { |
75 | 0 | let mut iters = planes.map(|plane| plane.iter().copied()); Unexecuted instantiation: image::utils::interleave_planes_inner::<1, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<1, 2>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<1, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<2, 2>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<2, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<2, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<3, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<3, 2>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<3, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<4, 4>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<4, 1>::{closure#0}Unexecuted instantiation: image::utils::interleave_planes_inner::<4, 2>::{closure#0} |
76 | 0 | for out in out.as_chunks_mut::<PLANES>().0 { |
77 | 0 | let vals = iters.each_mut().map(Iterator::next); |
78 | | |
79 | | // I'd like to use array::zip once stable. |
80 | 0 | for i in 0..PLANES { |
81 | 0 | out[i] = vals[i].unwrap_or(out[i]); |
82 | 0 | } |
83 | | } |
84 | 0 | } Unexecuted instantiation: image::utils::interleave_planes_inner::<1, 1> Unexecuted instantiation: image::utils::interleave_planes_inner::<1, 2> Unexecuted instantiation: image::utils::interleave_planes_inner::<1, 4> Unexecuted instantiation: image::utils::interleave_planes_inner::<2, 2> Unexecuted instantiation: image::utils::interleave_planes_inner::<2, 1> Unexecuted instantiation: image::utils::interleave_planes_inner::<2, 4> Unexecuted instantiation: image::utils::interleave_planes_inner::<3, 1> Unexecuted instantiation: image::utils::interleave_planes_inner::<3, 2> Unexecuted instantiation: image::utils::interleave_planes_inner::<3, 4> Unexecuted instantiation: image::utils::interleave_planes_inner::<4, 4> Unexecuted instantiation: image::utils::interleave_planes_inner::<4, 1> Unexecuted instantiation: image::utils::interleave_planes_inner::<4, 2> |
85 | | |
86 | | #[allow(dead_code)] |
87 | | // When no image formats that use it are enabled |
88 | 0 | pub(crate) fn vec_copy_to_u8<T>(vec: &[T]) -> Vec<u8> |
89 | 0 | where |
90 | 0 | T: bytemuck::Pod, |
91 | | { |
92 | 0 | bytemuck::cast_slice(vec).to_owned() |
93 | 0 | } |
94 | | |
95 | | #[inline] |
96 | 0 | pub(crate) fn clamp<N>(a: N, min: N, max: N) -> N |
97 | 0 | where |
98 | 0 | N: PartialOrd, |
99 | | { |
100 | 0 | if a < min { |
101 | 0 | min |
102 | 0 | } else if a > max { |
103 | 0 | max |
104 | | } else { |
105 | 0 | a |
106 | | } |
107 | 0 | } Unexecuted instantiation: image::utils::clamp::<f64> Unexecuted instantiation: image::utils::clamp::<f32> Unexecuted instantiation: image::utils::clamp::<i32> Unexecuted instantiation: image::utils::clamp::<u32> |
108 | | |
109 | | #[inline] |
110 | 4.07k | pub(crate) fn vec_try_with_capacity<T>(capacity: usize) -> Result<Vec<T>, TryReserveError> { |
111 | 4.07k | let mut vec = Vec::new(); |
112 | 4.07k | vec.try_reserve_exact(capacity)?; |
113 | 4.07k | Ok(vec) |
114 | 4.07k | } Unexecuted instantiation: image::utils::vec_try_with_capacity::<f32> image::utils::vec_try_with_capacity::<u8> Line | Count | Source | 110 | 4.07k | pub(crate) fn vec_try_with_capacity<T>(capacity: usize) -> Result<Vec<T>, TryReserveError> { | 111 | 4.07k | let mut vec = Vec::new(); | 112 | 4.07k | vec.try_reserve_exact(capacity)?; | 113 | 4.07k | Ok(vec) | 114 | 4.07k | } |
Unexecuted instantiation: image::utils::vec_try_with_capacity::<u16> |
115 | | |
116 | | /// Returns whether `T` can only represent integer values. |
117 | | #[inline] |
118 | 0 | pub(crate) fn is_integer<T: num_traits::NumCast + num_traits::Zero>() -> bool { |
119 | | // This uses a cast of 0.5 to T, because integers will truncate to zero |
120 | | // while types that can represent fractional values will return something |
121 | | // other than zero. |
122 | 0 | <T as num_traits::NumCast>::from(0.5).unwrap().is_zero() |
123 | 0 | } Unexecuted instantiation: image::utils::is_integer::<f64> Unexecuted instantiation: image::utils::is_integer::<u32> |
124 | | |
125 | | /// This is equivalent to `r.seek(SeekFrom::Start(reader_offset + offset))`, |
126 | | /// but correctly handles the case where `reader_offset + offset` would overflow. |
127 | 18.1k | pub(crate) fn seek_start_with_offset<R: std::io::Read + std::io::Seek>( |
128 | 18.1k | r: &mut R, |
129 | 18.1k | reader_offset: u64, |
130 | 18.1k | offset: u64, |
131 | 18.1k | ) -> std::io::Result<u64> { |
132 | 18.1k | let Some(offset) = reader_offset.checked_add(offset) else { |
133 | 0 | return Err(std::io::Error::new( |
134 | 0 | std::io::ErrorKind::InvalidInput, |
135 | 0 | "offset overflow", |
136 | 0 | )); |
137 | | }; |
138 | 18.1k | r.seek(std::io::SeekFrom::Start(offset)) |
139 | 18.1k | } |
140 | | |
141 | | #[cfg(test)] |
142 | | mod test { |
143 | | #[test] |
144 | | fn gray_to_luma8_skip() { |
145 | | let check = |bit_depth, w, from, to| { |
146 | | assert_eq!(super::expand_bits(bit_depth, w, from), to); |
147 | | }; |
148 | | // Bit depth 1, skip is more than half a byte |
149 | | check( |
150 | | 1, |
151 | | 10, |
152 | | &[0b11110000, 0b11000000, 0b00001111, 0b11000000], |
153 | | vec![ |
154 | | 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, |
155 | | ], |
156 | | ); |
157 | | // Bit depth 2, skip is more than half a byte |
158 | | check( |
159 | | 2, |
160 | | 5, |
161 | | &[0b11110000, 0b11000000, 0b00001111, 0b11000000], |
162 | | vec![255, 255, 0, 0, 255, 0, 0, 255, 255, 255], |
163 | | ); |
164 | | // Bit depth 2, skip is 0 |
165 | | check( |
166 | | 2, |
167 | | 4, |
168 | | &[0b11110000, 0b00001111], |
169 | | vec![255, 255, 0, 0, 0, 0, 255, 255], |
170 | | ); |
171 | | // Bit depth 4, skip is half a byte |
172 | | check(4, 1, &[0b11110011, 0b00001100], vec![255, 0]); |
173 | | } |
174 | | } |