Coverage Report

Created: 2026-01-09 07:43

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::*;
2
use rayon::iter::{IndexedParallelIterator, ParallelIterator};
3
use rayon::slice::{ChunksExact, ChunksExactMut, ParallelSlice, ParallelSliceMut};
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.
11
#[derive(Clone)]
12
pub struct PixelsPar<'a, P>
13
where
14
    P: Pixel + Sync + 'a,
15
    P::Subpixel: Sync + 'a,
16
{
17
    chunks: ChunksExact<'a, P::Subpixel>,
18
}
19
20
impl<'a, P> ParallelIterator for PixelsPar<'a, P>
21
where
22
    P: Pixel + Sync + 'a,
23
    P::Subpixel: Sync + 'a,
24
{
25
    type Item = &'a P;
26
27
0
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
28
0
    where
29
0
        C: UnindexedConsumer<Self::Item>,
30
    {
31
0
        self.chunks
32
0
            .map(|v| <P as Pixel>::from_slice(v))
33
0
            .drive_unindexed(consumer)
34
0
    }
35
36
0
    fn opt_len(&self) -> Option<usize> {
37
0
        Some(self.len())
38
0
    }
39
}
40
41
impl<'a, P> IndexedParallelIterator for PixelsPar<'a, P>
42
where
43
    P: Pixel + Sync + 'a,
44
    P::Subpixel: Sync + 'a,
45
{
46
0
    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
47
0
        self.chunks
48
0
            .map(|v| <P as Pixel>::from_slice(v))
49
0
            .drive(consumer)
50
0
    }
51
52
0
    fn len(&self) -> usize {
53
0
        self.chunks.len()
54
0
    }
55
56
0
    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
57
0
        self.chunks
58
0
            .map(|v| <P as Pixel>::from_slice(v))
59
0
            .with_producer(callback)
60
0
    }
61
}
62
63
impl<P> fmt::Debug for PixelsPar<'_, P>
64
where
65
    P: Pixel + Sync,
66
    P::Subpixel: Sync + fmt::Debug,
67
{
68
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69
0
        f.debug_struct("PixelsPar")
70
0
            .field("chunks", &self.chunks)
71
0
            .finish()
72
0
    }
73
}
74
75
/// Parallel iterator over mutable pixel refs.
76
pub struct PixelsMutPar<'a, P>
77
where
78
    P: Pixel + Send + Sync + 'a,
79
    P::Subpixel: Send + Sync + 'a,
80
{
81
    chunks: ChunksExactMut<'a, P::Subpixel>,
82
}
83
84
impl<'a, P> ParallelIterator for PixelsMutPar<'a, P>
85
where
86
    P: Pixel + Send + Sync + 'a,
87
    P::Subpixel: Send + Sync + 'a,
88
{
89
    type Item = &'a mut P;
90
91
0
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
92
0
    where
93
0
        C: UnindexedConsumer<Self::Item>,
94
    {
95
0
        self.chunks
96
0
            .map(|v| <P as Pixel>::from_slice_mut(v))
97
0
            .drive_unindexed(consumer)
98
0
    }
99
100
0
    fn opt_len(&self) -> Option<usize> {
101
0
        Some(self.len())
102
0
    }
103
}
104
105
impl<'a, P> IndexedParallelIterator for PixelsMutPar<'a, P>
106
where
107
    P: Pixel + Send + Sync + 'a,
108
    P::Subpixel: Send + Sync + 'a,
109
{
110
0
    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
111
0
        self.chunks
112
0
            .map(|v| <P as Pixel>::from_slice_mut(v))
113
0
            .drive(consumer)
114
0
    }
115
116
0
    fn len(&self) -> usize {
117
0
        self.chunks.len()
118
0
    }
119
120
0
    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
121
0
        self.chunks
122
0
            .map(|v| <P as Pixel>::from_slice_mut(v))
123
0
            .with_producer(callback)
124
0
    }
125
}
126
127
impl<P> fmt::Debug for PixelsMutPar<'_, P>
128
where
129
    P: Pixel + Send + Sync,
130
    P::Subpixel: Send + Sync + fmt::Debug,
131
{
132
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133
0
        f.debug_struct("PixelsMutPar")
134
0
            .field("chunks", &self.chunks)
135
0
            .finish()
136
0
    }
137
}
138
139
/// Parallel iterator over pixel refs and their coordinates.
140
#[derive(Clone)]
141
pub struct EnumeratePixelsPar<'a, P>
142
where
143
    P: Pixel + Sync + 'a,
144
    P::Subpixel: Sync + 'a,
145
{
146
    pixels: PixelsPar<'a, P>,
147
    width: u32,
148
}
149
150
impl<'a, P> ParallelIterator for EnumeratePixelsPar<'a, P>
151
where
152
    P: Pixel + Sync + 'a,
153
    P::Subpixel: Sync + 'a,
154
{
155
    type Item = (u32, u32, &'a P);
156
157
0
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
158
0
    where
159
0
        C: UnindexedConsumer<Self::Item>,
160
    {
161
0
        self.pixels
162
0
            .enumerate()
163
0
            .map(|(i, p)| {
164
0
                (
165
0
                    (i % self.width as usize) as u32,
166
0
                    (i / self.width as usize) as u32,
167
0
                    p,
168
0
                )
169
0
            })
170
0
            .drive_unindexed(consumer)
171
0
    }
172
173
0
    fn opt_len(&self) -> Option<usize> {
174
0
        Some(self.len())
175
0
    }
176
}
177
178
impl<'a, P> IndexedParallelIterator for EnumeratePixelsPar<'a, P>
179
where
180
    P: Pixel + Sync + 'a,
181
    P::Subpixel: Sync + 'a,
182
{
183
0
    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
184
0
        self.pixels
185
0
            .enumerate()
186
0
            .map(|(i, p)| {
187
0
                (
188
0
                    (i % self.width as usize) as u32,
189
0
                    (i / self.width as usize) as u32,
190
0
                    p,
191
0
                )
192
0
            })
193
0
            .drive(consumer)
194
0
    }
195
196
0
    fn len(&self) -> usize {
197
0
        self.pixels.len()
198
0
    }
199
200
0
    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
201
0
        self.pixels
202
0
            .enumerate()
203
0
            .map(|(i, p)| {
204
0
                (
205
0
                    (i % self.width as usize) as u32,
206
0
                    (i / self.width as usize) as u32,
207
0
                    p,
208
0
                )
209
0
            })
210
0
            .with_producer(callback)
211
0
    }
212
}
213
214
impl<P> fmt::Debug for EnumeratePixelsPar<'_, P>
215
where
216
    P: Pixel + Sync,
217
    P::Subpixel: Sync + fmt::Debug,
218
{
219
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
220
0
        f.debug_struct("EnumeratePixelsPar")
221
0
            .field("pixels", &self.pixels)
222
0
            .field("width", &self.width)
223
0
            .finish()
224
0
    }
225
}
226
227
/// Parallel iterator over mutable pixel refs and their coordinates.
228
pub struct EnumeratePixelsMutPar<'a, P>
229
where
230
    P: Pixel + Send + Sync + 'a,
231
    P::Subpixel: Send + Sync + 'a,
232
{
233
    pixels: PixelsMutPar<'a, P>,
234
    width: u32,
235
}
236
237
impl<'a, P> ParallelIterator for EnumeratePixelsMutPar<'a, P>
238
where
239
    P: Pixel + Send + Sync + 'a,
240
    P::Subpixel: Send + Sync + 'a,
241
{
242
    type Item = (u32, u32, &'a mut P);
243
244
0
    fn drive_unindexed<C>(self, consumer: C) -> C::Result
245
0
    where
246
0
        C: UnindexedConsumer<Self::Item>,
247
    {
248
0
        self.pixels
249
0
            .enumerate()
250
0
            .map(|(i, p)| {
251
0
                (
252
0
                    (i % self.width as usize) as u32,
253
0
                    (i / self.width as usize) as u32,
254
0
                    p,
255
0
                )
256
0
            })
257
0
            .drive_unindexed(consumer)
258
0
    }
259
260
0
    fn opt_len(&self) -> Option<usize> {
261
0
        Some(self.len())
262
0
    }
263
}
264
265
impl<'a, P> IndexedParallelIterator for EnumeratePixelsMutPar<'a, P>
266
where
267
    P: Pixel + Send + Sync + 'a,
268
    P::Subpixel: Send + Sync + 'a,
269
{
270
0
    fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
271
0
        self.pixels
272
0
            .enumerate()
273
0
            .map(|(i, p)| {
274
0
                (
275
0
                    (i % self.width as usize) as u32,
276
0
                    (i / self.width as usize) as u32,
277
0
                    p,
278
0
                )
279
0
            })
280
0
            .drive(consumer)
281
0
    }
282
283
0
    fn len(&self) -> usize {
284
0
        self.pixels.len()
285
0
    }
286
287
0
    fn with_producer<CB: ProducerCallback<Self::Item>>(self, callback: CB) -> CB::Output {
288
0
        self.pixels
289
0
            .enumerate()
290
0
            .map(|(i, p)| {
291
0
                (
292
0
                    (i % self.width as usize) as u32,
293
0
                    (i / self.width as usize) as u32,
294
0
                    p,
295
0
                )
296
0
            })
297
0
            .with_producer(callback)
298
0
    }
299
}
300
301
impl<P> fmt::Debug for EnumeratePixelsMutPar<'_, P>
302
where
303
    P: Pixel + Send + Sync,
304
    P::Subpixel: Send + Sync + fmt::Debug,
305
{
306
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307
0
        f.debug_struct("EnumeratePixelsMutPar")
308
0
            .field("pixels", &self.pixels)
309
0
            .field("width", &self.width)
310
0
            .finish()
311
0
    }
312
}
313
314
impl<P, Container> ImageBuffer<P, Container>
315
where
316
    P: Pixel + Sync,
317
    P::Subpixel: Sync,
318
    Container: Deref<Target = [P::Subpixel]>,
319
{
320
    /// Returns a parallel iterator over the pixels of this image, usable with `rayon`.
321
    /// See [`pixels`] for more information.
322
    ///
323
    /// [`pixels`]: #method.pixels
324
0
    pub fn par_pixels(&self) -> PixelsPar<'_, P> {
325
0
        PixelsPar {
326
0
            chunks: self
327
0
                .inner_pixels()
328
0
                .par_chunks_exact(<P as Pixel>::CHANNEL_COUNT as usize),
329
0
        }
330
0
    }
331
332
    /// Returns a parallel iterator over the pixels of this image and their coordinates, usable with `rayon`.
333
    /// See [`enumerate_pixels`] for more information.
334
    ///
335
    /// [`enumerate_pixels`]: #method.enumerate_pixels
336
0
    pub fn par_enumerate_pixels(&self) -> EnumeratePixelsPar<'_, P> {
337
0
        EnumeratePixelsPar {
338
0
            pixels: self.par_pixels(),
339
0
            width: self.width(),
340
0
        }
341
0
    }
342
}
343
344
impl<P, Container> ImageBuffer<P, Container>
345
where
346
    P: Pixel + Send + Sync,
347
    P::Subpixel: Send + Sync,
348
    Container: Deref<Target = [P::Subpixel]> + DerefMut,
349
{
350
    /// Returns a parallel iterator over the mutable pixels of this image, usable with `rayon`.
351
    /// See [`pixels_mut`] for more information.
352
    ///
353
    /// [`pixels_mut`]: #method.pixels_mut
354
0
    pub fn par_pixels_mut(&mut self) -> PixelsMutPar<'_, P> {
355
0
        PixelsMutPar {
356
0
            chunks: self
357
0
                .inner_pixels_mut()
358
0
                .par_chunks_exact_mut(<P as Pixel>::CHANNEL_COUNT as usize),
359
0
        }
360
0
    }
361
362
    /// Returns a parallel iterator over the mutable pixels of this image and their coordinates, usable with `rayon`.
363
    /// See [`enumerate_pixels_mut`] for more information.
364
    ///
365
    /// [`enumerate_pixels_mut`]: #method.enumerate_pixels_mut
366
0
    pub fn par_enumerate_pixels_mut(&mut self) -> EnumeratePixelsMutPar<'_, P> {
367
0
        let width = self.width();
368
0
        EnumeratePixelsMutPar {
369
0
            pixels: self.par_pixels_mut(),
370
0
            width,
371
0
        }
372
0
    }
373
}
374
375
impl<P> ImageBuffer<P, Vec<P::Subpixel>>
376
where
377
    P: Pixel + Send + Sync,
378
    P::Subpixel: Send + Sync,
379
{
380
    /// Constructs a new `ImageBuffer` by repeated application of the supplied function,
381
    /// utilizing multi-threading via `rayon`.
382
    ///
383
    /// The arguments to the function are the pixel's x and y coordinates.
384
    ///
385
    /// # Panics
386
    ///
387
    /// Panics when the resulting image is larger than the maximum size of a vector.
388
0
    pub fn from_par_fn<F>(width: u32, height: u32, f: F) -> ImageBuffer<P, Vec<P::Subpixel>>
389
0
    where
390
0
        F: Fn(u32, u32) -> P + Send + Sync,
391
    {
392
0
        let mut buf = ImageBuffer::new(width, height);
393
0
        buf.par_enumerate_pixels_mut().for_each(|(x, y, p)| {
394
0
            *p = f(x, y);
395
0
        });
396
397
0
        buf
398
0
    }
399
}
400
401
#[cfg(test)]
402
mod test {
403
    use crate::{Rgb, RgbImage};
404
    use rayon::iter::{IndexedParallelIterator, ParallelIterator};
405
406
    fn test_width_height(width: u32, height: u32, len: usize) {
407
        let mut image = RgbImage::new(width, height);
408
409
        assert_eq!(image.par_enumerate_pixels_mut().len(), len);
410
        assert_eq!(image.par_enumerate_pixels().len(), len);
411
        assert_eq!(image.par_pixels_mut().len(), len);
412
        assert_eq!(image.par_pixels().len(), len);
413
    }
414
415
    #[test]
416
    fn zero_width_zero_height() {
417
        test_width_height(0, 0, 0);
418
    }
419
420
    #[test]
421
    fn zero_width_nonzero_height() {
422
        test_width_height(0, 2, 0);
423
    }
424
425
    #[test]
426
    fn nonzero_width_zero_height() {
427
        test_width_height(2, 0, 0);
428
    }
429
430
    #[test]
431
    fn iter_parity() {
432
        let mut image1 = RgbImage::from_fn(17, 29, |x, y| {
433
            Rgb(std::array::from_fn(|i| {
434
                ((x + y * 98 + i as u32 * 27) % 255) as u8
435
            }))
436
        });
437
        let mut image2 = image1.clone();
438
439
        assert_eq!(
440
            image1.enumerate_pixels_mut().collect::<Vec<_>>(),
441
            image2.par_enumerate_pixels_mut().collect::<Vec<_>>()
442
        );
443
        assert_eq!(
444
            image1.enumerate_pixels().collect::<Vec<_>>(),
445
            image2.par_enumerate_pixels().collect::<Vec<_>>()
446
        );
447
        assert_eq!(
448
            image1.pixels_mut().collect::<Vec<_>>(),
449
            image2.par_pixels_mut().collect::<Vec<_>>()
450
        );
451
        assert_eq!(
452
            image1.pixels().collect::<Vec<_>>(),
453
            image2.par_pixels().collect::<Vec<_>>()
454
        );
455
    }
456
}
457
458
#[cfg(test)]
459
#[cfg(feature = "benchmarks")]
460
mod benchmarks {
461
    use crate::{Rgb, RgbImage};
462
463
    const S: u32 = 1024;
464
465
    #[bench]
466
    fn creation(b: &mut test::Bencher) {
467
        let mut bytes = 0;
468
        b.iter(|| {
469
            let img = RgbImage::from_fn(S, S, |_, _| test::black_box(pixel_func()));
470
471
            bytes += img.as_raw().len() as u64;
472
        });
473
474
        b.bytes = bytes;
475
    }
476
477
    #[bench]
478
    fn creation_par(b: &mut test::Bencher) {
479
        let mut bytes = 0;
480
        b.iter(|| {
481
            let img = RgbImage::from_par_fn(S, S, |_, _| test::black_box(pixel_func()));
482
483
            bytes += img.as_raw().len() as u64;
484
        });
485
486
        b.bytes = bytes;
487
    }
488
489
    fn pixel_func() -> Rgb<u8> {
490
        use std::collections::hash_map::RandomState;
491
        use std::hash::{BuildHasher, Hasher};
492
        Rgb(std::array::from_fn(|_| {
493
            RandomState::new().build_hasher().finish() as u8
494
        }))
495
    }
496
}