Coverage Report

Created: 2026-09-02 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}