Coverage Report

Created: 2026-08-14 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/images/buffer_par.rs
Line
Count
Source
1
use rayon::iter::{plumbing::*, IntoParallelRefIterator, IntoParallelRefMutIterator};
2
use rayon::iter::{IndexedParallelIterator, ParallelIterator};
3
use rayon::slice::{Iter, IterMut};
4
use std::fmt;
5
use std::ops::{Deref, DerefMut};
6
7
use crate::traits::Pixel;
8
use crate::ImageBuffer;
9
10
/// Parallel iterator over pixel refs and their coordinates.
11
#[derive(Clone)]
12
pub struct EnumeratePixelsPar<'a, P>
13
where
14
    P: Pixel + Sync + 'a,
15
    P::Subpixel: Sync + 'a,
16
{
17
    pixels: Iter<'a, P>,
18
    width: u32,
19
}
20
21
impl<'a, P> ParallelIterator for EnumeratePixelsPar<'a, P>
22
where
23
    P: Pixel + Sync + 'a,
24
    P::Subpixel: Sync + 'a,
25
{
26
    type Item = (u32, u32, &'a P);
27
28
0
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
29
0
    where
30
0
        C: UnindexedConsumer<Self::Item>,
31
    {
32
0
        self.pixels
33
0
            .enumerate()
34
0
            .map(|(i, p)| {
35
0
                (
36
0
                    (i % self.width as usize) as u32,
37
0
                    (i / self.width as usize) as u32,
38
0
                    p,
39
0
                )
40
0
            })
41
0
            .drive_unindexed(consumer)
42
0
    }
43
44
0
    fn opt_len(&self) -> Option<usize> {
45
0
        Some(self.len())
46
0
    }
47
}
48
49
impl<'a, P> IndexedParallelIterator for EnumeratePixelsPar<'a, P>
50
where
51
    P: Pixel + Sync + 'a,
52
    P::Subpixel: Sync + 'a,
53
{
54
0
    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
55
0
        self.pixels
56
0
            .enumerate()
57
0
            .map(|(i, p)| {
58
0
                (
59
0
                    (i % self.width as usize) as u32,
60
0
                    (i / self.width as usize) as u32,
61
0
                    p,
62
0
                )
63
0
            })
64
0
            .drive(consumer)
65
0
    }
66
67
0
    fn len(&self) -> usize {
68
0
        self.pixels.len()
69
0
    }
70
71
0
    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
72
0
        self.pixels
73
0
            .enumerate()
74
0
            .map(|(i, p)| {
75
0
                (
76
0
                    (i % self.width as usize) as u32,
77
0
                    (i / self.width as usize) as u32,
78
0
                    p,
79
0
                )
80
0
            })
81
0
            .with_producer(callback)
82
0
    }
83
}
84
85
impl<P> fmt::Debug for EnumeratePixelsPar<'_, P>
86
where
87
    P: Pixel + Sync + fmt::Debug,
88
    P::Subpixel: Sync,
89
{
90
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91
0
        f.debug_struct("EnumeratePixelsPar")
92
0
            .field("pixels", &self.pixels)
93
0
            .field("width", &self.width)
94
0
            .finish()
95
0
    }
96
}
97
98
/// Parallel iterator over mutable pixel refs and their coordinates.
99
pub struct EnumeratePixelsMutPar<'a, P>
100
where
101
    P: Pixel + Send + Sync + 'a,
102
    P::Subpixel: Send + Sync + 'a,
103
{
104
    pixels: IterMut<'a, P>,
105
    width: u32,
106
}
107
108
impl<'a, P> ParallelIterator for EnumeratePixelsMutPar<'a, P>
109
where
110
    P: Pixel + Send + Sync + 'a,
111
    P::Subpixel: Send + Sync + 'a,
112
{
113
    type Item = (u32, u32, &'a mut P);
114
115
0
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
116
0
    where
117
0
        C: UnindexedConsumer<Self::Item>,
118
    {
119
0
        self.pixels
120
0
            .enumerate()
121
0
            .map(|(i, p)| {
122
0
                (
123
0
                    (i % self.width as usize) as u32,
124
0
                    (i / self.width as usize) as u32,
125
0
                    p,
126
0
                )
127
0
            })
128
0
            .drive_unindexed(consumer)
129
0
    }
130
131
0
    fn opt_len(&self) -> Option<usize> {
132
0
        Some(self.len())
133
0
    }
134
}
135
136
impl<'a, P> IndexedParallelIterator for EnumeratePixelsMutPar<'a, P>
137
where
138
    P: Pixel + Send + Sync + 'a,
139
    P::Subpixel: Send + Sync + 'a,
140
{
141
0
    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
142
0
        self.pixels
143
0
            .enumerate()
144
0
            .map(|(i, p)| {
145
0
                (
146
0
                    (i % self.width as usize) as u32,
147
0
                    (i / self.width as usize) as u32,
148
0
                    p,
149
0
                )
150
0
            })
151
0
            .drive(consumer)
152
0
    }
153
154
0
    fn len(&self) -> usize {
155
0
        self.pixels.len()
156
0
    }
157
158
0
    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
159
0
        self.pixels
160
0
            .enumerate()
161
0
            .map(|(i, p)| {
162
0
                (
163
0
                    (i % self.width as usize) as u32,
164
0
                    (i / self.width as usize) as u32,
165
0
                    p,
166
0
                )
167
0
            })
168
0
            .with_producer(callback)
169
0
    }
170
}
171
172
impl<P> fmt::Debug for EnumeratePixelsMutPar<'_, P>
173
where
174
    P: Pixel + Send + Sync + fmt::Debug,
175
    P::Subpixel: Send + Sync,
176
{
177
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178
0
        f.debug_struct("EnumeratePixelsMutPar")
179
0
            .field("pixels", &self.pixels)
180
0
            .field("width", &self.width)
181
0
            .finish()
182
0
    }
183
}
184
185
impl<P, Container> ImageBuffer<P, Container>
186
where
187
    P: Pixel + Sync,
188
    P::Subpixel: Sync,
189
    Container: Deref<Target = [P::Subpixel]>,
190
{
191
    /// Returns a parallel iterator over the pixels of this image and their coordinates, usable with `rayon`.
192
    /// See [`enumerate_pixels`] for more information.
193
    ///
194
    /// [`enumerate_pixels`]: Self::enumerate_pixels
195
0
    pub fn par_enumerate_pixels(&self) -> EnumeratePixelsPar<'_, P> {
196
0
        EnumeratePixelsPar {
197
0
            pixels: self.pixels().par_iter(),
198
0
            width: self.width(),
199
0
        }
200
0
    }
201
}
202
203
impl<P, Container> ImageBuffer<P, Container>
204
where
205
    P: Pixel + Send + Sync,
206
    P::Subpixel: Send + Sync,
207
    Container: Deref<Target = [P::Subpixel]> + DerefMut,
208
{
209
    /// Returns a parallel iterator over the mutable pixels of this image and their coordinates, usable with `rayon`.
210
    /// See [`enumerate_pixels_mut`] for more information.
211
    ///
212
    /// [`enumerate_pixels_mut`]: Self::enumerate_pixels_mut
213
0
    pub fn par_enumerate_pixels_mut(&mut self) -> EnumeratePixelsMutPar<'_, P> {
214
0
        let width = self.width();
215
0
        EnumeratePixelsMutPar {
216
0
            pixels: self.pixels_mut().par_iter_mut(),
217
0
            width,
218
0
        }
219
0
    }
220
}
221
222
impl<P> ImageBuffer<P, Vec<P::Subpixel>>
223
where
224
    P: Pixel + Send + Sync,
225
    P::Subpixel: Send + Sync,
226
{
227
    /// Constructs a new `ImageBuffer` by repeated application of the supplied function,
228
    /// utilizing multi-threading via `rayon`.
229
    ///
230
    /// The arguments to the function are the pixel's x and y coordinates.
231
    ///
232
    /// # Panics
233
    ///
234
    /// Panics when the resulting image is larger than the maximum size of a vector.
235
0
    pub fn from_par_fn<F>(width: u32, height: u32, f: F) -> ImageBuffer<P, Vec<P::Subpixel>>
236
0
    where
237
0
        F: Fn(u32, u32) -> P + Send + Sync,
238
    {
239
0
        let mut buf = ImageBuffer::new(width, height);
240
0
        buf.par_enumerate_pixels_mut().for_each(|(x, y, p)| {
241
0
            *p = f(x, y);
242
0
        });
243
244
0
        buf
245
0
    }
246
}
247
248
#[cfg(test)]
249
mod test {
250
    use crate::{Rgb, RgbImage};
251
    use rayon::iter::{IndexedParallelIterator, ParallelIterator};
252
253
    fn test_width_height(width: u32, height: u32, len: usize) {
254
        let mut image = RgbImage::new(width, height);
255
256
        assert_eq!(image.par_enumerate_pixels_mut().len(), len);
257
        assert_eq!(image.par_enumerate_pixels().len(), len);
258
        assert_eq!(image.pixels_mut().len(), len);
259
        assert_eq!(image.pixels().len(), len);
260
    }
261
262
    #[test]
263
    fn zero_width_zero_height() {
264
        test_width_height(0, 0, 0);
265
    }
266
267
    #[test]
268
    fn zero_width_nonzero_height() {
269
        test_width_height(0, 2, 0);
270
    }
271
272
    #[test]
273
    fn nonzero_width_zero_height() {
274
        test_width_height(2, 0, 0);
275
    }
276
277
    #[test]
278
    fn iter_parity() {
279
        let mut image1 = RgbImage::from_fn(17, 29, |x, y| {
280
            Rgb(std::array::from_fn(|i| {
281
                ((x + y * 98 + i as u32 * 27) % 255) as u8
282
            }))
283
        });
284
        let mut image2 = image1.clone();
285
286
        assert_eq!(
287
            image1.enumerate_pixels_mut().collect::<Vec<_>>(),
288
            image2.par_enumerate_pixels_mut().collect::<Vec<_>>()
289
        );
290
        assert_eq!(
291
            image1.enumerate_pixels().collect::<Vec<_>>(),
292
            image2.par_enumerate_pixels().collect::<Vec<_>>()
293
        );
294
    }
295
}
296
297
#[cfg(test)]
298
#[cfg(feature = "benchmarks")]
299
mod benchmarks {
300
    use crate::{Rgb, RgbImage};
301
302
    const S: u32 = 1024;
303
304
    #[bench]
305
    fn creation(b: &mut test::Bencher) {
306
        let mut bytes = 0;
307
        b.iter(|| {
308
            let img = RgbImage::from_fn(S, S, |_, _| test::black_box(pixel_func()));
309
310
            bytes += img.as_raw().len() as u64;
311
        });
312
313
        b.bytes = bytes;
314
    }
315
316
    #[bench]
317
    fn creation_par(b: &mut test::Bencher) {
318
        let mut bytes = 0;
319
        b.iter(|| {
320
            let img = RgbImage::from_par_fn(S, S, |_, _| test::black_box(pixel_func()));
321
322
            bytes += img.as_raw().len() as u64;
323
        });
324
325
        b.bytes = bytes;
326
    }
327
328
    fn pixel_func() -> Rgb<u8> {
329
        use std::collections::hash_map::RandomState;
330
        use std::hash::{BuildHasher, Hasher};
331
        Rgb(std::array::from_fn(|_| {
332
            RandomState::new().build_hasher().finish() as u8
333
        }))
334
    }
335
}