Coverage Report

Created: 2026-03-20 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/imageops/sample.rs
Line
Count
Source
1
//! Functions and filters for the sampling of pixels.
2
//!
3
//! Some of these functions assume structure of the `Pixel` type beyond the trait interface.
4
//! Generally: methods operating on individual channels have a limit of 4 channels.
5
6
// See http://cs.brown.edu/courses/cs123/lectures/08_Image_Processing_IV.pdf
7
// for some of the theory behind image scaling and convolution
8
9
use num_traits::{NumCast, ToPrimitive, Zero};
10
use std::f32;
11
use std::ops::Mul;
12
13
use crate::imageops::filter_1d::{
14
    filter_2d_sep_la, filter_2d_sep_la_f32, filter_2d_sep_la_u16, filter_2d_sep_plane,
15
    filter_2d_sep_plane_f32, filter_2d_sep_plane_u16, filter_2d_sep_rgb, filter_2d_sep_rgb_f32,
16
    filter_2d_sep_rgb_u16, filter_2d_sep_rgba, filter_2d_sep_rgba_f32, filter_2d_sep_rgba_u16,
17
    FilterImageSize,
18
};
19
use crate::images::buffer::{Gray16Image, GrayAlpha16Image, Rgb16Image, Rgba16Image};
20
use crate::traits::{Enlargeable, Pixel, Primitive};
21
use crate::utils::{clamp, is_integer};
22
use crate::{
23
    DynamicImage, GenericImage, GenericImageView, GrayAlphaImage, GrayImage, ImageBuffer,
24
    Rgb32FImage, RgbImage, Rgba32FImage, RgbaImage,
25
};
26
27
const MAX_CHANNEL: usize = 4;
28
29
/// Available Sampling Filters.
30
///
31
/// ## Examples
32
///
33
/// To test the different sampling filters on a real example, you can find two
34
/// examples called
35
/// [`scaledown`](https://github.com/image-rs/image/tree/main/examples/scaledown)
36
/// and
37
/// [`scaleup`](https://github.com/image-rs/image/tree/main/examples/scaleup)
38
/// in the `examples` directory of the crate source code.
39
///
40
/// Here is a 3.58 MiB
41
/// [test image](https://github.com/image-rs/image/blob/main/examples/scaledown/test.jpg)
42
/// that has been scaled down to 300x225 px:
43
///
44
/// <!-- NOTE: To test new test images locally, replace the GitHub path with `../../../docs/` -->
45
/// <div style="display: flex; flex-wrap: wrap; align-items: flex-start;">
46
///   <div style="margin: 0 8px 8px 0;">
47
///     <img src="https://raw.githubusercontent.com/image-rs/image/main/examples/scaledown/scaledown-test-near.png" title="Nearest"><br>
48
///     Nearest Neighbor
49
///   </div>
50
///   <div style="margin: 0 8px 8px 0;">
51
///     <img src="https://raw.githubusercontent.com/image-rs/image/main/examples/scaledown/scaledown-test-tri.png" title="Triangle"><br>
52
///     Linear: Triangle
53
///   </div>
54
///   <div style="margin: 0 8px 8px 0;">
55
///     <img src="https://raw.githubusercontent.com/image-rs/image/main/examples/scaledown/scaledown-test-cmr.png" title="CatmullRom"><br>
56
///     Cubic: Catmull-Rom
57
///   </div>
58
///   <div style="margin: 0 8px 8px 0;">
59
///     <img src="https://raw.githubusercontent.com/image-rs/image/main/examples/scaledown/scaledown-test-gauss.png" title="Gaussian"><br>
60
///     Gaussian
61
///   </div>
62
///   <div style="margin: 0 8px 8px 0;">
63
///     <img src="https://raw.githubusercontent.com/image-rs/image/main/examples/scaledown/scaledown-test-lcz2.png" title="Lanczos3"><br>
64
///     Lanczos with window 3
65
///   </div>
66
/// </div>
67
///
68
/// ## Speed
69
///
70
/// Time required to create each of the examples above, tested on an Intel
71
/// i7-4770 CPU with Rust 1.37 in release mode:
72
///
73
/// <table style="width: auto;">
74
///   <tr>
75
///     <th>Nearest</th>
76
///     <td>31 ms</td>
77
///   </tr>
78
///   <tr>
79
///     <th>Triangle</th>
80
///     <td>414 ms</td>
81
///   </tr>
82
///   <tr>
83
///     <th>CatmullRom</th>
84
///     <td>817 ms</td>
85
///   </tr>
86
///   <tr>
87
///     <th>Gaussian</th>
88
///     <td>1180 ms</td>
89
///   </tr>
90
///   <tr>
91
///     <th>Lanczos3</th>
92
///     <td>1170 ms</td>
93
///   </tr>
94
/// </table>
95
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
96
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97
pub enum FilterType {
98
    /// Nearest Neighbor
99
    Nearest,
100
101
    /// Linear Filter
102
    Triangle,
103
104
    /// Cubic Filter
105
    CatmullRom,
106
107
    /// Gaussian Filter
108
    Gaussian,
109
110
    /// Lanczos with window 3
111
    Lanczos3,
112
}
113
114
/// A Representation of a separable filter.
115
pub(crate) struct Filter<'a> {
116
    /// The filter's filter function.
117
    pub(crate) kernel: Box<dyn Fn(f32) -> f32 + 'a>,
118
119
    /// The window on which this filter operates.
120
    pub(crate) support: f32,
121
}
122
123
struct FloatNearest(f32);
124
125
// to_i64, to_u64, and to_f64 implicitly affect all other lower conversions.
126
// Note that to_f64 by default calls to_i64 and thus needs to be overridden.
127
impl ToPrimitive for FloatNearest {
128
    // to_{i,u}64 is required, to_{i,u}{8,16} are useful.
129
    // If a usecase for full 32 bits is found its trivial to add
130
0
    fn to_i8(&self) -> Option<i8> {
131
0
        self.0.round().to_i8()
132
0
    }
133
0
    fn to_i16(&self) -> Option<i16> {
134
0
        self.0.round().to_i16()
135
0
    }
136
0
    fn to_i64(&self) -> Option<i64> {
137
0
        self.0.round().to_i64()
138
0
    }
139
0
    fn to_u8(&self) -> Option<u8> {
140
0
        self.0.round().to_u8()
141
0
    }
142
0
    fn to_u16(&self) -> Option<u16> {
143
0
        self.0.round().to_u16()
144
0
    }
145
0
    fn to_u64(&self) -> Option<u64> {
146
0
        self.0.round().to_u64()
147
0
    }
148
0
    fn to_f64(&self) -> Option<f64> {
149
0
        self.0.to_f64()
150
0
    }
151
}
152
153
// sinc function: the ideal sampling filter.
154
0
fn sinc(t: f32) -> f32 {
155
0
    let a = t * f32::consts::PI;
156
157
0
    if t == 0.0 {
158
0
        1.0
159
    } else {
160
0
        a.sin() / a
161
    }
162
0
}
163
164
// lanczos kernel function. A windowed sinc function.
165
0
fn lanczos(x: f32, t: f32) -> f32 {
166
0
    if x.abs() < t {
167
0
        sinc(x) * sinc(x / t)
168
    } else {
169
0
        0.0
170
    }
171
0
}
172
173
// Calculate a splice based on the b and c parameters.
174
// from authors Mitchell and Netravali.
175
0
fn bc_cubic_spline(x: f32, b: f32, c: f32) -> f32 {
176
0
    let a = x.abs();
177
178
0
    let k = if a < 1.0 {
179
0
        (12.0 - 9.0 * b - 6.0 * c) * a.powi(3)
180
0
            + (-18.0 + 12.0 * b + 6.0 * c) * a.powi(2)
181
0
            + (6.0 - 2.0 * b)
182
0
    } else if a < 2.0 {
183
0
        (-b - 6.0 * c) * a.powi(3)
184
0
            + (6.0 * b + 30.0 * c) * a.powi(2)
185
0
            + (-12.0 * b - 48.0 * c) * a
186
0
            + (8.0 * b + 24.0 * c)
187
    } else {
188
0
        0.0
189
    };
190
191
0
    k / 6.0
192
0
}
193
194
/// The Gaussian Function.
195
/// ```r``` is the standard deviation.
196
0
pub(crate) fn gaussian(x: f32, r: f32) -> f32 {
197
0
    ((2.0 * f32::consts::PI).sqrt() * r).recip() * (-x.powi(2) / (2.0 * r.powi(2))).exp()
198
0
}
199
200
/// Calculate the lanczos kernel with a window of 3
201
0
pub(crate) fn lanczos3_kernel(x: f32) -> f32 {
202
0
    lanczos(x, 3.0)
203
0
}
204
205
/// Calculate the gaussian function with a
206
/// standard deviation of 0.5
207
0
pub(crate) fn gaussian_kernel(x: f32) -> f32 {
208
0
    gaussian(x, 0.5)
209
0
}
210
211
/// Calculate the Catmull-Rom cubic spline.
212
/// Also known as a form of `BiCubic` sampling in two dimensions.
213
0
pub(crate) fn catmullrom_kernel(x: f32) -> f32 {
214
0
    bc_cubic_spline(x, 0.0, 0.5)
215
0
}
216
217
/// Calculate the triangle function.
218
/// Also known as `BiLinear` sampling in two dimensions.
219
0
pub(crate) fn triangle_kernel(x: f32) -> f32 {
220
0
    if x.abs() < 1.0 {
221
0
        1.0 - x.abs()
222
    } else {
223
0
        0.0
224
    }
225
0
}
226
227
/// Calculate the box kernel.
228
/// Only pixels inside the box should be considered, and those
229
/// contribute equally.  So this method simply returns 1.
230
0
pub(crate) fn box_kernel(_x: f32) -> f32 {
231
0
    1.0
232
0
}
233
234
// Sample the rows of the supplied image using the provided filter.
235
//
236
// The height of the image remains unchanged.
237
// ```new_width``` is the desired width of the new image
238
// ```filter``` is the filter to use for sampling.
239
// ```image``` is not necessarily Rgba and the order of channels is passed through.
240
//
241
// Note: if an empty image is passed in, panics unless the image is truly empty.
242
//
243
// Note: this function processes pixels for the standard pixel types with up to 4 channels.
244
0
fn horizontal_sample<P, S>(
245
0
    image: &Rgba32FImage,
246
0
    new_width: u32,
247
0
    filter: &mut Filter,
248
0
) -> ImageBuffer<P, Vec<S>>
249
0
where
250
0
    P: Pixel<Subpixel = S>,
251
0
    S: Primitive,
252
{
253
0
    let (width, height) = image.dimensions();
254
    // This is protection against a memory usage similar to #2340. See `vertical_sample`.
255
0
    assert!(
256
        // Checks the implication: (width == 0) -> (height == 0)
257
0
        width != 0 || height == 0,
258
0
        "Unexpected prior allocation size. This case should have been handled by the caller"
259
    );
260
261
0
    let mut out = ImageBuffer::new(new_width, height);
262
0
    out.copy_color_space_from(image);
263
0
    let mut ws = Vec::new();
264
265
0
    let max: f32 = NumCast::from(S::DEFAULT_MAX_VALUE).unwrap();
266
0
    let min: f32 = NumCast::from(S::DEFAULT_MIN_VALUE).unwrap();
267
0
    let ratio = width as f32 / new_width as f32;
268
0
    let sratio = if ratio < 1.0 { 1.0 } else { ratio };
269
0
    let src_support = filter.support * sratio;
270
271
0
    let mut pix_temp = <P as Pixel>::broadcast(S::DEFAULT_MAX_VALUE);
272
273
0
    for outx in 0..new_width {
274
        // Find the point in the input image corresponding to the centre
275
        // of the current pixel in the output image.
276
0
        let inputx = (outx as f32 + 0.5) * ratio;
277
278
        // Left and right are slice bounds for the input pixels relevant
279
        // to the output pixel we are calculating.  Pixel x is relevant
280
        // if and only if (x >= left) && (x < right).
281
282
        // Invariant: 0 <= left < right <= width
283
284
0
        let left = (inputx - src_support).floor() as i64;
285
0
        let left = clamp(left, 0, <i64 as From<_>>::from(width) - 1) as u32;
286
287
0
        let right = (inputx + src_support).ceil() as i64;
288
0
        let right = clamp(
289
0
            right,
290
0
            <i64 as From<_>>::from(left) + 1,
291
0
            <i64 as From<_>>::from(width),
292
0
        ) as u32;
293
294
        // Go back to left boundary of pixel, to properly compare with i
295
        // below, as the kernel treats the centre of a pixel as 0.
296
0
        let inputx = inputx - 0.5;
297
298
0
        ws.clear();
299
0
        let mut sum = 0.0;
300
0
        for i in left..right {
301
0
            let w = (filter.kernel)((i as f32 - inputx) / sratio);
302
0
            ws.push(w);
303
0
            sum += w;
304
0
        }
305
0
        for w in ws.iter_mut() {
306
0
            *w /= sum;
307
0
        }
308
309
0
        for y in 0..height {
310
0
            let mut t = [0.0; MAX_CHANNEL];
311
312
0
            for (i, w) in ws.iter().enumerate() {
313
0
                let p = image.get_pixel(left + i as u32, y);
314
315
0
                for (t, &c) in t.iter_mut().zip(p.channels()) {
316
0
                    *t += c * w;
317
0
                }
318
            }
319
320
0
            for (&tc, pc) in t.iter().zip(pix_temp.channels_mut()) {
321
0
                *pc = NumCast::from(FloatNearest(clamp(tc, min, max))).unwrap();
322
0
            }
323
324
0
            out.put_pixel(outx, y, pix_temp);
325
        }
326
    }
327
328
0
    out
329
0
}
330
331
/// Linearly sample from an image using coordinates in [0, 1].
332
0
pub fn sample_bilinear<P: Pixel>(
333
0
    img: &impl GenericImageView<Pixel = P>,
334
0
    u: f32,
335
0
    v: f32,
336
0
) -> Option<P> {
337
0
    if ![u, v].iter().all(|c| (0.0..=1.0).contains(c)) {
338
0
        return None;
339
0
    }
340
341
0
    let (w, h) = img.dimensions();
342
0
    if w == 0 || h == 0 {
343
0
        return None;
344
0
    }
345
346
0
    let ui = w as f32 * u - 0.5;
347
0
    let vi = h as f32 * v - 0.5;
348
0
    interpolate_bilinear(
349
0
        img,
350
0
        ui.max(0.).min((w - 1) as f32),
351
0
        vi.max(0.).min((h - 1) as f32),
352
    )
353
0
}
354
355
/// Sample from an image using coordinates in [0, 1], taking the nearest coordinate.
356
0
pub fn sample_nearest<P: Pixel>(
357
0
    img: &impl GenericImageView<Pixel = P>,
358
0
    u: f32,
359
0
    v: f32,
360
0
) -> Option<P> {
361
0
    if ![u, v].iter().all(|c| (0.0..=1.0).contains(c)) {
362
0
        return None;
363
0
    }
364
365
0
    let (w, h) = img.dimensions();
366
0
    let ui = w as f32 * u - 0.5;
367
0
    let ui = ui.max(0.).min((w.saturating_sub(1)) as f32);
368
369
0
    let vi = h as f32 * v - 0.5;
370
0
    let vi = vi.max(0.).min((h.saturating_sub(1)) as f32);
371
0
    interpolate_nearest(img, ui, vi)
372
0
}
373
374
/// Sample from an image using coordinates in [0, w-1] and [0, h-1], taking the
375
/// nearest pixel.
376
///
377
/// Coordinates outside the image bounds will return `None`, however the
378
/// behavior for points within half a pixel of the image bounds may change in
379
/// the future.
380
0
pub fn interpolate_nearest<P: Pixel>(
381
0
    img: &impl GenericImageView<Pixel = P>,
382
0
    x: f32,
383
0
    y: f32,
384
0
) -> Option<P> {
385
0
    let (w, h) = img.dimensions();
386
0
    if w == 0 || h == 0 {
387
0
        return None;
388
0
    }
389
0
    if !(0.0..=((w - 1) as f32)).contains(&x) {
390
0
        return None;
391
0
    }
392
0
    if !(0.0..=((h - 1) as f32)).contains(&y) {
393
0
        return None;
394
0
    }
395
396
0
    Some(img.get_pixel(x.round() as u32, y.round() as u32))
397
0
}
398
399
/// Linearly sample from an image using coordinates in [0, w-1] and [0, h-1].
400
0
pub fn interpolate_bilinear<P: Pixel>(
401
0
    img: &impl GenericImageView<Pixel = P>,
402
0
    x: f32,
403
0
    y: f32,
404
0
) -> Option<P> {
405
    // assumption needed for correctness of pixel creation
406
0
    assert!(P::CHANNEL_COUNT as usize <= MAX_CHANNEL);
407
408
0
    let (w, h) = img.dimensions();
409
0
    if w == 0 || h == 0 {
410
0
        return None;
411
0
    }
412
0
    if !(0.0..=((w - 1) as f32)).contains(&x) {
413
0
        return None;
414
0
    }
415
0
    if !(0.0..=((h - 1) as f32)).contains(&y) {
416
0
        return None;
417
0
    }
418
419
    // keep these as integers, for fewer FLOPs
420
0
    let uf = x.floor() as u32;
421
0
    let vf = y.floor() as u32;
422
0
    let uc = (uf + 1).min(w - 1);
423
0
    let vc = (vf + 1).min(h - 1);
424
425
    // clamp coords to the range of the image
426
0
    let mut sxx = [[0.; MAX_CHANNEL]; MAX_CHANNEL];
427
428
    // do not use Array::map, as it can be slow with high stack usage,
429
    // for [[f32; MAX_CHANNEL4]; MAX_CHANNEL4].
430
431
    // convert samples to f32
432
    // currently rgba is the largest one,
433
    // so just store as many items as necessary,
434
    // because there's not a simple way to be generic over all of them.
435
0
    let mut compute = |u: u32, v: u32, i| {
436
0
        let s = img.get_pixel(u, v);
437
0
        for (j, c) in s.channels().iter().enumerate() {
438
0
            sxx[j][i] = c.to_f32().unwrap();
439
0
        }
440
0
        s
441
0
    };
442
443
    // hacky reuse since cannot construct a generic Pixel
444
0
    let mut out: P = compute(uf, vf, 0);
445
0
    compute(uf, vc, 1);
446
0
    compute(uc, vf, 2);
447
0
    compute(uc, vc, 3);
448
449
    // weights, the later two are independent from the first 2 for better vectorization.
450
0
    let ufw = x - uf as f32;
451
0
    let vfw = y - vf as f32;
452
0
    let ucw = (uf + 1) as f32 - x;
453
0
    let vcw = (vf + 1) as f32 - y;
454
455
    // https://en.wikipedia.org/wiki/Bilinear_interpolation#Weighted_mean
456
    // the distance between pixels is 1 so there is no denominator
457
0
    let wff = ucw * vcw;
458
0
    let wfc = ucw * vfw;
459
0
    let wcf = ufw * vcw;
460
0
    let wcc = ufw * vfw;
461
    // was originally assert, but is actually not a cheap computation
462
0
    debug_assert!(f32::abs((wff + wfc + wcf + wcc) - 1.) < 1e-3);
463
464
    // hack to see if primitive is an integer or a float
465
0
    let is_float = P::Subpixel::DEFAULT_MAX_VALUE.to_f32().unwrap() == 1.0;
466
467
0
    for (i, c) in out.channels_mut().iter_mut().enumerate() {
468
0
        let v = wff * sxx[i][0] + wfc * sxx[i][1] + wcf * sxx[i][2] + wcc * sxx[i][3];
469
        // this rounding may introduce quantization errors,
470
        // Specifically what is meant is that many samples may deviate
471
        // from the mean value of the originals, but it's not possible to fix that.
472
0
        *c = <P::Subpixel as NumCast>::from(if is_float { v } else { v.round() }).unwrap_or({
473
0
            if v < 0.0 {
474
0
                P::Subpixel::DEFAULT_MIN_VALUE
475
            } else {
476
0
                P::Subpixel::DEFAULT_MAX_VALUE
477
            }
478
        });
479
    }
480
481
0
    Some(out)
482
0
}
483
484
// Sample the columns of the supplied image using the provided filter.
485
// The width of the image remains unchanged.
486
// ```new_height``` is the desired height of the new image
487
// ```filter``` is the filter to use for sampling.
488
// The return value is not necessarily Rgba, the underlying order of channels in ```image``` is
489
// preserved.
490
//
491
// Note: if an empty image is passed in, panics unless the image is truly empty.
492
0
fn vertical_sample<I, P, S>(image: &I, new_height: u32, filter: &mut Filter) -> Rgba32FImage
493
0
where
494
0
    I: GenericImageView<Pixel = P>,
495
0
    P: Pixel<Subpixel = S>,
496
0
    S: Primitive,
497
{
498
0
    let (width, height) = image.dimensions();
499
500
    // This is protection against a regression in memory usage such as #2340. Since the strategy to
501
    // deal with it depends on the caller it is a precondition of this function.
502
0
    assert!(
503
        // Checks the implication: (height == 0) -> (width == 0)
504
0
        height != 0 || width == 0,
505
0
        "Unexpected prior allocation size. This case should have been handled by the caller"
506
    );
507
508
0
    let mut out = ImageBuffer::new(width, new_height);
509
0
    out.copy_color_space_from(&image.buffer_with_dimensions(0, 0));
510
0
    let mut ws = Vec::new();
511
512
0
    let ratio = height as f32 / new_height as f32;
513
0
    let sratio = if ratio < 1.0 { 1.0 } else { ratio };
514
0
    let src_support = filter.support * sratio;
515
516
0
    for outy in 0..new_height {
517
        // For an explanation of this algorithm, see the comments
518
        // in horizontal_sample.
519
0
        let inputy = (outy as f32 + 0.5) * ratio;
520
521
0
        let left = (inputy - src_support).floor() as i64;
522
0
        let left = clamp(left, 0, <i64 as From<_>>::from(height) - 1) as u32;
523
524
0
        let right = (inputy + src_support).ceil() as i64;
525
0
        let right = clamp(
526
0
            right,
527
0
            <i64 as From<_>>::from(left) + 1,
528
0
            <i64 as From<_>>::from(height),
529
0
        ) as u32;
530
531
0
        let inputy = inputy - 0.5;
532
533
0
        ws.clear();
534
0
        let mut sum = 0.0;
535
0
        for i in left..right {
536
0
            let w = (filter.kernel)((i as f32 - inputy) / sratio);
537
0
            ws.push(w);
538
0
            sum += w;
539
0
        }
540
0
        for w in ws.iter_mut() {
541
0
            *w /= sum;
542
0
        }
543
544
0
        for x in 0..width {
545
0
            let mut pix = crate::Rgba([1.0; 4]);
546
547
0
            for (i, w) in ws.iter().enumerate() {
548
0
                let p = image.get_pixel(x, left + i as u32);
549
550
0
                for (tc, &c) in pix.channels_mut().iter_mut().zip(p.channels()) {
551
0
                    *tc += <f32 as NumCast>::from(c).unwrap() * w;
552
0
                }
553
            }
554
555
0
            out.put_pixel(x, outy, pix);
556
        }
557
    }
558
559
0
    out
560
0
}
561
562
/// Local struct for keeping track of pixel sums for fast thumbnail averaging
563
struct ThumbnailSum<S: Primitive + Enlargeable>([S::Larger; MAX_CHANNEL]);
564
565
impl<S: Primitive + Enlargeable> ThumbnailSum<S> {
566
0
    fn zeroed() -> Self {
567
0
        ThumbnailSum([S::Larger::zero(); MAX_CHANNEL])
568
0
    }
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<f32>>::zeroed
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u8>>::zeroed
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u16>>::zeroed
569
570
0
    fn sample_val(val: S) -> S::Larger {
571
0
        <S::Larger as NumCast>::from(val).unwrap()
572
0
    }
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<f32>>::sample_val
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u8>>::sample_val
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u16>>::sample_val
573
574
0
    fn add_pixel<P: Pixel<Subpixel = S>>(&mut self, pixel: P) {
575
0
        for (s, &c) in self.0.iter_mut().zip(pixel.channels()) {
576
0
            *s += Self::sample_val(c);
577
0
        }
578
0
    }
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<f32>>::add_pixel::<image::color::Rgb<f32>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<f32>>::add_pixel::<image::color::Rgba<f32>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u8>>::add_pixel::<image::color::Rgb<u8>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u8>>::add_pixel::<image::color::Luma<u8>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u8>>::add_pixel::<image::color::Rgba<u8>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u8>>::add_pixel::<image::color::LumaA<u8>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u16>>::add_pixel::<image::color::Rgb<u16>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u16>>::add_pixel::<image::color::Luma<u16>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u16>>::add_pixel::<image::color::Rgba<u16>>
Unexecuted instantiation: <image::imageops::sample::ThumbnailSum<u16>>::add_pixel::<image::color::LumaA<u16>>
579
}
580
581
/// Resize the supplied image to the specific dimensions.
582
///
583
/// For downscaling, this method uses a fast integer algorithm where each source pixel contributes
584
/// to exactly one target pixel.  May give aliasing artifacts if new size is close to old size.
585
///
586
/// In case the current width is smaller than the new width or similar for the height, another
587
/// strategy is used instead.  For each pixel in the output, a rectangular region of the input is
588
/// determined, just as previously.  But when no input pixel is part of this region, the nearest
589
/// pixels are interpolated instead.
590
///
591
/// For speed reasons, all interpolation is performed linearly over the colour values.  It will not
592
/// take the pixel colour spaces into account.
593
0
pub fn thumbnail<I, P, S>(image: &I, new_width: u32, new_height: u32) -> ImageBuffer<P, Vec<S>>
594
0
where
595
0
    I: GenericImageView<Pixel = P>,
596
0
    P: Pixel<Subpixel = S>,
597
0
    S: Primitive + Enlargeable,
598
{
599
    // Maximum support channels for `ThumbnailSum`.
600
0
    assert!(P::CHANNEL_COUNT as usize <= MAX_CHANNEL);
601
602
0
    let (width, height) = image.dimensions();
603
0
    let mut out = image.buffer_with_dimensions(new_width, new_height);
604
605
0
    if height == 0 || width == 0 {
606
0
        return out;
607
0
    }
608
609
0
    let x_ratio = width as f32 / new_width as f32;
610
0
    let y_ratio = height as f32 / new_height as f32;
611
612
0
    for outy in 0..new_height {
613
0
        let bottomf = outy as f32 * y_ratio;
614
0
        let topf = bottomf + y_ratio;
615
616
0
        let bottom = clamp(bottomf.ceil() as u32, 0, height - 1);
617
0
        let top = clamp(topf.ceil() as u32, bottom, height);
618
619
0
        for outx in 0..new_width {
620
0
            let leftf = outx as f32 * x_ratio;
621
0
            let rightf = leftf + x_ratio;
622
623
0
            let left = clamp(leftf.ceil() as u32, 0, width - 1);
624
0
            let right = clamp(rightf.ceil() as u32, left, width);
625
626
0
            let avg = if bottom != top && left != right {
627
0
                thumbnail_sample_block(image, left, right, bottom, top)
628
0
            } else if bottom != top {
629
                // && left == right
630
                // In the first column we have left == 0 and right > ceil(y_scale) > 0 so this
631
                // assertion can never trigger.
632
0
                debug_assert!(
633
0
                    left > 0 && right > 0,
634
0
                    "First output column must have corresponding pixels"
635
                );
636
637
0
                let fraction_horizontal = (leftf.fract() + rightf.fract()) / 2.;
638
0
                thumbnail_sample_fraction_horizontal(
639
0
                    image,
640
0
                    right - 1,
641
0
                    fraction_horizontal,
642
0
                    bottom,
643
0
                    top,
644
                )
645
0
            } else if left != right {
646
                // && bottom == top
647
                // In the first line we have bottom == 0 and top > ceil(x_scale) > 0 so this
648
                // assertion can never trigger.
649
0
                debug_assert!(
650
0
                    bottom > 0 && top > 0,
651
0
                    "First output row must have corresponding pixels"
652
                );
653
654
0
                let fraction_vertical = (topf.fract() + bottomf.fract()) / 2.;
655
0
                thumbnail_sample_fraction_vertical(image, left, right, top - 1, fraction_vertical)
656
            } else {
657
                // bottom == top && left == right
658
0
                let fraction_horizontal = (topf.fract() + bottomf.fract()) / 2.;
659
0
                let fraction_vertical = (leftf.fract() + rightf.fract()) / 2.;
660
661
0
                thumbnail_sample_fraction_both(
662
0
                    image,
663
0
                    right - 1,
664
0
                    fraction_horizontal,
665
0
                    top - 1,
666
0
                    fraction_vertical,
667
                )
668
            };
669
670
0
            out.put_pixel(outx, outy, avg);
671
        }
672
    }
673
674
0
    out
675
0
}
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
676
677
/// Get a pixel for a thumbnail where the input window encloses at least a full pixel.
678
0
fn thumbnail_sample_block<I, P, S>(image: &I, left: u32, right: u32, bottom: u32, top: u32) -> P
679
0
where
680
0
    I: GenericImageView<Pixel = P>,
681
0
    P: Pixel<Subpixel = S>,
682
0
    S: Primitive + Enlargeable,
683
{
684
0
    let mut sum = ThumbnailSum::zeroed();
685
686
0
    for y in bottom..top {
687
0
        for x in left..right {
688
0
            let k = image.get_pixel(x, y);
689
0
            sum.add_pixel(k);
690
0
        }
691
    }
692
693
0
    let n = <S::Larger as NumCast>::from((right - left) * (top - bottom)).unwrap();
694
695
    // For integer types division truncates, so we need to add n/2 to round to
696
    // the nearest integer. Floating point types do not need this.
697
0
    let round = if is_integer::<S::Larger>() {
698
0
        <S::Larger as NumCast>::from(n / NumCast::from(2).unwrap()).unwrap()
699
    } else {
700
0
        S::Larger::zero()
701
    };
702
703
0
    let rounded_avg = sum.0.map(|v| S::clamp_from((v + round) / n));
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#0}
704
0
    let used_channels: usize = (<P as Pixel>::CHANNEL_COUNT).min(4u8).into();
705
0
    *<P as Pixel>::from_slice(&rounded_avg[..used_channels])
706
0
}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_block::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
707
708
/// Get a thumbnail pixel where the input window encloses at least a vertical pixel.
709
0
fn thumbnail_sample_fraction_horizontal<I, P, S>(
710
0
    image: &I,
711
0
    left: u32,
712
0
    fraction_horizontal: f32,
713
0
    bottom: u32,
714
0
    top: u32,
715
0
) -> P
716
0
where
717
0
    I: GenericImageView<Pixel = P>,
718
0
    P: Pixel<Subpixel = S>,
719
0
    S: Primitive + Enlargeable,
720
{
721
0
    let fract = fraction_horizontal;
722
723
0
    let mut sum_left = ThumbnailSum::zeroed();
724
0
    let mut sum_right = ThumbnailSum::zeroed();
725
0
    for x in bottom..top {
726
0
        let k_left = image.get_pixel(left, x);
727
0
        sum_left.add_pixel(k_left);
728
0
729
0
        let k_right = image.get_pixel(left + 1, x);
730
0
        sum_right.add_pixel(k_right);
731
0
    }
732
733
    // Now we approximate: left/n*(1-fract) + right/n*fract
734
0
    let fact_right = fract / ((top - bottom) as f32);
735
0
    let fact_left = (1. - fract) / ((top - bottom) as f32);
736
737
0
    let mix_left_and_right = |leftv: S::Larger, rightv: S::Larger| {
738
0
        <S as NumCast>::from(
739
0
            fact_left * leftv.to_f32().unwrap() + fact_right * rightv.to_f32().unwrap(),
740
        )
741
0
        .expect("Average sample value should fit into sample type")
742
0
    };
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#0}
743
744
0
    let left = sum_left.0;
745
0
    let right = sum_right.0;
746
747
0
    let mixed_sum = [
748
0
        mix_left_and_right(left[0], right[0]),
749
0
        mix_left_and_right(left[1], right[1]),
750
0
        mix_left_and_right(left[2], right[2]),
751
0
        mix_left_and_right(left[3], right[3]),
752
0
    ];
753
754
0
    let used_channels: usize = (<P as Pixel>::CHANNEL_COUNT).min(4u8).into();
755
0
    *<P as Pixel>::from_slice(&mixed_sum[..used_channels])
756
0
}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_horizontal::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
757
758
/// Get a thumbnail pixel where the input window encloses at least a horizontal pixel.
759
0
fn thumbnail_sample_fraction_vertical<I, P, S>(
760
0
    image: &I,
761
0
    left: u32,
762
0
    right: u32,
763
0
    bottom: u32,
764
0
    fraction_vertical: f32,
765
0
) -> P
766
0
where
767
0
    I: GenericImageView<Pixel = P>,
768
0
    P: Pixel<Subpixel = S>,
769
0
    S: Primitive + Enlargeable,
770
{
771
0
    let fract = fraction_vertical;
772
773
0
    let mut sum_bot = ThumbnailSum::zeroed();
774
0
    let mut sum_top = ThumbnailSum::zeroed();
775
0
    for x in left..right {
776
0
        let k_bot = image.get_pixel(x, bottom);
777
0
        sum_bot.add_pixel(k_bot);
778
0
779
0
        let k_top = image.get_pixel(x, bottom + 1);
780
0
        sum_top.add_pixel(k_top);
781
0
    }
782
783
    // Now we approximate: bot/n*fract + top/n*(1-fract)
784
0
    let fact_top = fract / ((right - left) as f32);
785
0
    let fact_bot = (1. - fract) / ((right - left) as f32);
786
787
0
    let mix_bot_and_top = |botv: S::Larger, topv: S::Larger| {
788
0
        <S as NumCast>::from(fact_bot * botv.to_f32().unwrap() + fact_top * topv.to_f32().unwrap())
789
0
            .expect("Average sample value should fit into sample type")
790
0
    };
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#0}
791
792
0
    let bot = sum_bot.0;
793
0
    let top = sum_top.0;
794
795
0
    let mixed_sum = [
796
0
        mix_bot_and_top(bot[0], top[0]),
797
0
        mix_bot_and_top(bot[1], top[1]),
798
0
        mix_bot_and_top(bot[2], top[2]),
799
0
        mix_bot_and_top(bot[3], top[3]),
800
0
    ];
801
802
0
    let used_channels: usize = (<P as Pixel>::CHANNEL_COUNT).min(4u8).into();
803
0
    *<P as Pixel>::from_slice(&mixed_sum[..used_channels])
804
0
}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_vertical::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
805
806
/// Get a single pixel for a thumbnail where the input window does not enclose any full pixel.
807
0
fn thumbnail_sample_fraction_both<I, P, S>(
808
0
    image: &I,
809
0
    left: u32,
810
0
    fraction_vertical: f32,
811
0
    bottom: u32,
812
0
    fraction_horizontal: f32,
813
0
) -> P
814
0
where
815
0
    I: GenericImageView<Pixel = P>,
816
0
    P: Pixel<Subpixel = S>,
817
0
    S: Primitive + Enlargeable,
818
{
819
0
    let k_bl = image.get_pixel(left, bottom);
820
0
    let k_tl = image.get_pixel(left, bottom + 1);
821
0
    let k_br = image.get_pixel(left + 1, bottom);
822
0
    let k_tr = image.get_pixel(left + 1, bottom + 1);
823
824
0
    let frac_v = fraction_vertical;
825
0
    let frac_h = fraction_horizontal;
826
827
0
    let fact_tr = frac_v * frac_h;
828
0
    let fact_tl = frac_v * (1. - frac_h);
829
0
    let fact_br = (1. - frac_v) * frac_h;
830
0
    let fact_bl = (1. - frac_v) * (1. - frac_h);
831
832
0
    let mix = |br: S, tr: S, bl: S, tl: S| {
833
0
        <S as NumCast>::from(
834
0
            fact_br * br.to_f32().unwrap()
835
0
                + fact_tr * tr.to_f32().unwrap()
836
0
                + fact_bl * bl.to_f32().unwrap()
837
0
                + fact_tl * tl.to_f32().unwrap(),
838
        )
839
0
        .expect("Average sample value should fit into sample type")
840
0
    };
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#0}
841
842
    // Make a copy of any pixel, we'll fully overwrite it.
843
0
    let mut sum = k_br;
844
845
0
    for (i, ch) in sum.channels_mut().iter_mut().enumerate() {
846
0
        *ch = mix(
847
0
            k_br.channels()[i],
848
0
            k_tr.channels()[i],
849
0
            k_bl.channels()[i],
850
0
            k_tl.channels()[i],
851
0
        );
852
0
    }
853
854
0
    sum
855
0
}
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::thumbnail_sample_fraction_both::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
856
857
/// Perform a 3x3 box filter on the supplied image.
858
///
859
/// # Arguments:
860
///
861
/// * `image` - source image.
862
/// * `kernel` - is an array of the filter weights of length 9.
863
///
864
/// This method typically assumes that the input is scene-linear light.
865
/// If it is not, color distortion may occur.
866
0
pub fn filter3x3<I, P, S>(image: &I, kernel: &[f32; 9]) -> ImageBuffer<P, Vec<S>>
867
0
where
868
0
    I: GenericImageView<Pixel = P>,
869
0
    P: Pixel<Subpixel = S>,
870
0
    S: Primitive,
871
{
872
    // The kernel's input positions relative to the current pixel.
873
0
    let taps: &[(isize, isize)] = &[
874
0
        (-1, -1),
875
0
        (0, -1),
876
0
        (1, -1),
877
0
        (-1, 0),
878
0
        (0, 0),
879
0
        (1, 0),
880
0
        (-1, 1),
881
0
        (0, 1),
882
0
        (1, 1),
883
0
    ];
884
885
0
    let (width, height) = image.dimensions();
886
0
    let mut out = image.buffer_like();
887
888
0
    let max = S::DEFAULT_MAX_VALUE;
889
0
    let max: f32 = NumCast::from(max).unwrap();
890
891
0
    let inverse_sum = match kernel.iter().sum() {
892
0
        0.0 => 1.0,
893
0
        sum => 1.0 / sum,
894
    };
895
896
0
    for y in 1..height - 1 {
897
0
        for x in 1..width - 1 {
898
0
            let mut t = [0.0; MAX_CHANNEL];
899
900
            // TODO: There is no need to recalculate the kernel for each pixel.
901
            // Only a subtract and addition is needed for pixels after the first
902
            // in each row.
903
0
            for (&k, &(a, b)) in kernel.iter().zip(taps.iter()) {
904
0
                let x0 = x as isize + a;
905
0
                let y0 = y as isize + b;
906
907
0
                let p = image.get_pixel(x0 as u32, y0 as u32);
908
909
0
                for (tc, &c) in t.iter_mut().zip(p.channels()) {
910
0
                    *tc += <f32 as NumCast>::from(c).unwrap() * k;
911
0
                }
912
            }
913
914
0
            let channel_avg = t
915
0
                .map(|ti| ti * inverse_sum)
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#0}
916
0
                .map(|ti| NumCast::from(clamp(ti, 0.0, max)).unwrap());
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#1}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#1}
917
918
0
            let pix = *Pixel::from_slice(&channel_avg[..P::CHANNEL_COUNT as usize]);
919
920
0
            out.put_pixel(x, y, pix);
921
        }
922
    }
923
924
0
    out
925
0
}
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::filter3x3::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
926
927
/// Resize the supplied image to the specified dimensions.
928
///
929
/// # Arguments:
930
///
931
/// * `nwidth` - new image width.
932
/// * `nheight` - new image height.
933
/// * `filter` -  is the sampling filter to use, see [FilterType] for mor information.
934
///
935
/// This method assumes alpha pre-multiplication for images that contain non-constant alpha.
936
///
937
/// This method typically assumes that the input is scene-linear light.
938
/// If it is not, color distortion may occur.
939
0
pub fn resize<I: GenericImageView>(
940
0
    image: &I,
941
0
    nwidth: u32,
942
0
    nheight: u32,
943
0
    filter: FilterType,
944
0
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> {
945
    // Check if there is nothing to sample from.
946
0
    let is_empty = {
947
0
        let (width, height) = image.dimensions();
948
0
        width == 0 || height == 0
949
    };
950
951
0
    if is_empty {
952
0
        return image.buffer_with_dimensions(nwidth, nheight);
953
0
    }
954
955
    // check if the new dimensions are the same as the old. if they are, make a copy instead of resampling
956
0
    if (nwidth, nheight) == image.dimensions() {
957
0
        let mut tmp = image.buffer_like();
958
0
        tmp.copy_from(image, 0, 0).unwrap();
959
0
        return tmp;
960
0
    }
961
962
0
    let mut method = match filter {
963
0
        FilterType::Nearest => Filter {
964
0
            kernel: Box::new(box_kernel),
965
0
            support: 0.0,
966
0
        },
967
0
        FilterType::Triangle => Filter {
968
0
            kernel: Box::new(triangle_kernel),
969
0
            support: 1.0,
970
0
        },
971
0
        FilterType::CatmullRom => Filter {
972
0
            kernel: Box::new(catmullrom_kernel),
973
0
            support: 2.0,
974
0
        },
975
0
        FilterType::Gaussian => Filter {
976
0
            kernel: Box::new(gaussian_kernel),
977
0
            support: 3.0,
978
0
        },
979
0
        FilterType::Lanczos3 => Filter {
980
0
            kernel: Box::new(lanczos3_kernel),
981
0
            support: 3.0,
982
0
        },
983
    };
984
985
    // Note: tmp is not necessarily actually Rgba
986
0
    let tmp: Rgba32FImage = vertical_sample(image, nheight, &mut method);
987
0
    horizontal_sample(&tmp, nwidth, &mut method)
988
0
}
989
990
/// Performs a Gaussian blur on the supplied image.
991
///
992
/// # Arguments
993
///
994
/// * `radius` - Blurring window radius. These parameter controls directly the window size.
995
///   We choose visually optimal sigma for the given radius. To control sigma
996
///   directly use [DynamicImage::blur_advanced] instead.
997
///
998
/// Use [`crate::imageops::fast_blur()`] for a faster but less
999
/// accurate version.
1000
/// This method assumes alpha pre-multiplication for images that contain non-constant alpha.
1001
/// This method typically assumes that the input is scene-linear light.
1002
/// If it is not, color distortion may occur.
1003
0
pub fn blur<I: GenericImageView>(
1004
0
    image: &I,
1005
0
    radius: f32,
1006
0
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> {
1007
0
    gaussian_blur_indirect(image, GaussianBlurParameters::new_from_radius(radius))
1008
0
}
1009
1010
/// Performs a Gaussian blur on the supplied image.
1011
///
1012
/// # Arguments
1013
///
1014
///  - `parameters` - see [GaussianBlurParameters] for more info.
1015
///
1016
/// This method assumes alpha pre-multiplication for images that contain non-constant alpha.
1017
/// This method typically assumes that the input is scene-linear light.
1018
/// If it is not, color distortion may occur.
1019
0
pub fn blur_advanced<I: GenericImageView>(
1020
0
    image: &I,
1021
0
    parameters: GaussianBlurParameters,
1022
0
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> {
1023
0
    gaussian_blur_indirect(image, parameters)
1024
0
}
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::blur_advanced::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>>
1025
1026
0
fn get_gaussian_kernel_1d(width: usize, sigma: f32) -> Vec<f32> {
1027
0
    let mut sum_norm: f32 = 0f32;
1028
0
    let mut kernel = vec![0f32; width];
1029
0
    let scale = 1f32 / (f32::sqrt(2f32 * f32::consts::PI) * sigma);
1030
0
    let mean = (width / 2) as f32;
1031
1032
0
    for (x, weight) in kernel.iter_mut().enumerate() {
1033
0
        let new_weight = f32::exp(-0.5f32 * f32::powf((x as f32 - mean) / sigma, 2.0f32)) * scale;
1034
0
        *weight = new_weight;
1035
0
        sum_norm += new_weight;
1036
0
    }
1037
1038
0
    if sum_norm != 0f32 {
1039
0
        let sum_scale = 1f32 / sum_norm;
1040
0
        for weight in &mut kernel {
1041
0
            *weight = weight.mul(sum_scale);
1042
0
        }
1043
0
    }
1044
1045
0
    kernel
1046
0
}
1047
1048
/// Holds analytical gaussian blur representation
1049
#[derive(Copy, Clone, PartialOrd, PartialEq)]
1050
pub struct GaussianBlurParameters {
1051
    /// X-axis kernel, must be odd
1052
    x_axis_kernel_size: u32,
1053
    /// X-axis sigma, must > 0, not subnormal, and not NaN
1054
    ///
1055
    /// Sigma controls how flat the sample is. A visually optimal sigma is around
1056
    /// `window_size / 6`.
1057
    /// When sigma is close to the window size, the sample becomes almost flat,
1058
    /// behaving like a windowed average or a box blur.
1059
    /// When sigma is close to 1, only center pixels are involved in windowing,
1060
    /// so a smaller window is preferred in this case.
1061
    x_axis_sigma: f32,
1062
    /// Y-axis kernel, must be odd
1063
    y_axis_kernel_size: u32,
1064
    /// Y-axis sigma, must > 0, not subnormal, and not NaN
1065
    ///
1066
    /// Sigma controls how flat the sample is. A visually optimal sigma is around
1067
    /// `window_size / 6`.
1068
    /// When sigma is close to the window size, the sample becomes almost flat,
1069
    /// behaving like a windowed average or a box blur.
1070
    /// When sigma is close to 1, only center pixels are involved in windowing,
1071
    /// so a smaller window is preferred in this case.
1072
    y_axis_sigma: f32,
1073
}
1074
1075
impl GaussianBlurParameters {
1076
    /// Built-in smoothing kernel with size 3.
1077
    pub const SMOOTHING_3: GaussianBlurParameters = GaussianBlurParameters {
1078
        x_axis_kernel_size: 3,
1079
        x_axis_sigma: 0.8,
1080
        y_axis_kernel_size: 3,
1081
        y_axis_sigma: 0.8,
1082
    };
1083
1084
    /// Built-in smoothing kernel with size 5.
1085
    pub const SMOOTHING_5: GaussianBlurParameters = GaussianBlurParameters {
1086
        x_axis_kernel_size: 5,
1087
        x_axis_sigma: 1.1,
1088
        y_axis_kernel_size: 5,
1089
        y_axis_sigma: 1.1,
1090
    };
1091
1092
    /// Built-in smoothing kernel with size 7.
1093
    pub const SMOOTHING_7: GaussianBlurParameters = GaussianBlurParameters {
1094
        x_axis_kernel_size: 7,
1095
        x_axis_sigma: 1.4,
1096
        y_axis_kernel_size: 7,
1097
        y_axis_sigma: 1.4,
1098
    };
1099
1100
    /// Creates a new parameters set from radius only.
1101
0
    pub fn new_from_radius(radius: f32) -> GaussianBlurParameters {
1102
        // Previous implementation was allowing passing 0 so we'll allow here also.
1103
0
        assert!(radius >= 0.0);
1104
0
        if radius != 0. {
1105
0
            assert!(
1106
0
                radius.is_normal(),
1107
0
                "Radius do not allow infinities, NaNs or subnormals"
1108
            );
1109
0
        }
1110
0
        GaussianBlurParameters::new_from_kernel_size(radius * 2. + 1.)
1111
0
    }
1112
1113
    /// Creates a new parameters set from kernel size only.
1114
    ///
1115
    /// Kernel size will be rounded to nearest odd, and used with fraction
1116
    /// to compute accurate required sigma.
1117
0
    pub fn new_from_kernel_size(kernel_size: f32) -> GaussianBlurParameters {
1118
0
        assert!(
1119
0
            kernel_size > 0.,
1120
0
            "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives"
1121
        );
1122
0
        assert!(
1123
0
            kernel_size.is_normal(),
1124
0
            "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives"
1125
        );
1126
0
        let i_kernel_size = GaussianBlurParameters::round_to_nearest_odd(kernel_size);
1127
0
        assert_ne!(i_kernel_size % 2, 0, "Kernel size must be odd");
1128
0
        let v_sigma = GaussianBlurParameters::sigma_from_kernel_size(kernel_size);
1129
0
        GaussianBlurParameters {
1130
0
            x_axis_kernel_size: i_kernel_size,
1131
0
            x_axis_sigma: v_sigma,
1132
0
            y_axis_kernel_size: i_kernel_size,
1133
0
            y_axis_sigma: v_sigma,
1134
0
        }
1135
0
    }
1136
1137
    /// Creates a new anisotropic parameter set from kernel sizes
1138
    ///
1139
    /// Kernel size will be rounded to nearest odd, and used with fraction
1140
    /// to compute accurate required sigma.
1141
0
    pub fn new_anisotropic_kernel_size(
1142
0
        x_axis_kernel_size: f32,
1143
0
        y_axis_kernel_size: f32,
1144
0
    ) -> GaussianBlurParameters {
1145
0
        assert!(
1146
0
            x_axis_kernel_size > 0.,
1147
0
            "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives"
1148
        );
1149
0
        assert!(
1150
0
            y_axis_kernel_size.is_normal(),
1151
0
            "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives"
1152
        );
1153
0
        assert!(
1154
0
            y_axis_kernel_size > 0.,
1155
0
            "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives"
1156
        );
1157
0
        assert!(
1158
0
            y_axis_kernel_size.is_normal(),
1159
0
            "Kernel size do not allow infinities, zeros, NaNs or subnormals or negatives"
1160
        );
1161
0
        let x_kernel_size = GaussianBlurParameters::round_to_nearest_odd(x_axis_kernel_size);
1162
0
        assert_ne!(x_kernel_size % 2, 0, "Kernel size must be odd");
1163
0
        let y_kernel_size = GaussianBlurParameters::round_to_nearest_odd(y_axis_kernel_size);
1164
0
        assert_ne!(y_kernel_size % 2, 0, "Kernel size must be odd");
1165
0
        let x_sigma = GaussianBlurParameters::sigma_from_kernel_size(x_axis_kernel_size);
1166
0
        let y_sigma = GaussianBlurParameters::sigma_from_kernel_size(y_axis_kernel_size);
1167
0
        GaussianBlurParameters {
1168
0
            x_axis_kernel_size: x_kernel_size,
1169
0
            x_axis_sigma: x_sigma,
1170
0
            y_axis_kernel_size: y_kernel_size,
1171
0
            y_axis_sigma: y_sigma,
1172
0
        }
1173
0
    }
1174
1175
    /// Creates a new parameters set from sigma only
1176
    ///
1177
    /// # Panics
1178
    /// Panics if sigma is negative, NaN or infinity.
1179
0
    pub fn new_from_sigma(sigma: f32) -> GaussianBlurParameters {
1180
0
        assert!(
1181
0
            sigma >= 0.0 && sigma.is_finite(),
1182
0
            "Sigma must be non-negative and finite"
1183
        );
1184
1185
        /// As sigma gets smaller, the blur kernel quickly approaches all zeros
1186
        /// with the center being 1, which is an identity for 1D convolution.
1187
        /// I.e. no blur. So we define a threshold for sigma below which we
1188
        /// consider the blur to be an identity.
1189
        ///
1190
        /// Small sigma generally map to a 3x1 kernel. This kernel will have the
1191
        /// values `[w, 1.0, w]` before normalization (ignoring shared factors),
1192
        /// where `w = exp(-0.5 / pow(sigma, 2))`. If the sum of weights is
1193
        /// rounded to 1, i.e. `1.0 + 2.0 * w == 1.0`, then w is small enough
1194
        /// that the kernel is effectively an identity. So we pick this
1195
        /// threshold as the largest sigma such that
1196
        /// `1.0 + 2.0 * exp(-0.5 / pow(sigma, 2)) == 1.0` when rounded to f32
1197
        /// precision.
1198
        const IDENTITY_THRESHOLD: f32 = 0.16986436;
1199
0
        if sigma < IDENTITY_THRESHOLD {
1200
            // Any (normalized) kernel of size 1 is the identity, so sigma
1201
            // doesn't matter. However, we pick sigma=1 to avoid  potential
1202
            // issues with NaN, infinities, and subnormals.
1203
0
            return GaussianBlurParameters {
1204
0
                x_axis_kernel_size: 1,
1205
0
                x_axis_sigma: 1.0,
1206
0
                y_axis_kernel_size: 1,
1207
0
                y_axis_sigma: 1.0,
1208
0
            };
1209
0
        }
1210
1211
0
        let kernel_size = GaussianBlurParameters::kernel_size_from_sigma(sigma);
1212
0
        GaussianBlurParameters {
1213
0
            x_axis_kernel_size: kernel_size,
1214
0
            x_axis_sigma: sigma,
1215
0
            y_axis_kernel_size: kernel_size,
1216
0
            y_axis_sigma: sigma,
1217
0
        }
1218
0
    }
1219
1220
    #[inline]
1221
0
    fn round_to_nearest_odd(x: f32) -> u32 {
1222
0
        let n = x.round() as u32;
1223
0
        if !n.is_multiple_of(2) {
1224
0
            n
1225
        } else {
1226
0
            let lower = n - 1;
1227
0
            let upper = n + 1;
1228
1229
0
            let dist_lower = (x - lower as f32).abs();
1230
0
            let dist_upper = (x - upper as f32).abs();
1231
1232
0
            if dist_lower <= dist_upper {
1233
0
                lower
1234
            } else {
1235
0
                upper
1236
            }
1237
        }
1238
0
    }
1239
1240
0
    fn sigma_from_kernel_size(kernel_size: f32) -> f32 {
1241
0
        let safe_kernel_size = if kernel_size <= 1. { 0.8 } else { kernel_size };
1242
        // This formula finds a "optimal perceptual sigma" for a given kernel
1243
        // size. It is also used by other libraries, such as OpenCV:
1244
        // https://docs.opencv.org/4.x/d4/d86/group__imgproc__filter.html#gac05a120c1ae92a6060dd0db190a61afa
1245
0
        0.3 * ((safe_kernel_size - 1.) * 0.5 - 1.) + 0.8
1246
0
    }
1247
1248
0
    fn kernel_size_from_sigma(sigma: f32) -> u32 {
1249
        // This uses the inverse of the formula in `sigma_from_kernel_size` to
1250
        // find the kernel size for a given sigma. This isn't the only valid
1251
        // choice. For example, OpenCV uses 6σ+1 as the kernel size. Both work.
1252
        // Note that a kernel size of 6.666σ convers 99.91% of the Gaussian
1253
        // distribution, resulting in a high-quality blur without ringing
1254
        // artifacts.
1255
        //
1256
        // Some more details:
1257
        // - The returned kernel size is at least 3. This is important for small
1258
        //   sigmas, since a kernel size of 1 would no no blur at all.
1259
        // - The `| 1` ensure that the returned kernel size is odd, which is
1260
        //   required. In effect, even numbers are rounded up to the next odd
1261
        //   number, which is the same as the behavior of `round_to_nearest_odd`.
1262
0
        (6.66666 * sigma - 2.3333333).max(3.) as u32 | 1
1263
0
    }
1264
}
1265
1266
0
pub(crate) fn gaussian_blur_dyn_image(
1267
0
    image: &DynamicImage,
1268
0
    parameters: GaussianBlurParameters,
1269
0
) -> DynamicImage {
1270
0
    let x_axis_kernel = get_gaussian_kernel_1d(
1271
0
        parameters.x_axis_kernel_size as usize,
1272
0
        parameters.x_axis_sigma,
1273
    );
1274
1275
0
    let y_axis_kernel = get_gaussian_kernel_1d(
1276
0
        parameters.y_axis_kernel_size as usize,
1277
0
        parameters.y_axis_sigma,
1278
    );
1279
1280
0
    let filter_image_size = FilterImageSize {
1281
0
        width: image.width() as usize,
1282
0
        height: image.height() as usize,
1283
0
    };
1284
1285
0
    let mut target = match image {
1286
0
        DynamicImage::ImageLuma8(img) => {
1287
0
            let mut dest_image = vec![0u8; img.len()];
1288
0
            filter_2d_sep_plane(
1289
0
                img.as_raw(),
1290
0
                &mut dest_image,
1291
0
                filter_image_size,
1292
0
                &x_axis_kernel,
1293
0
                &y_axis_kernel,
1294
            )
1295
0
            .unwrap();
1296
0
            DynamicImage::ImageLuma8(
1297
0
                GrayImage::from_raw(img.width(), img.height(), dest_image).unwrap(),
1298
0
            )
1299
        }
1300
0
        DynamicImage::ImageLumaA8(img) => {
1301
0
            let mut dest_image = vec![0u8; img.len()];
1302
0
            filter_2d_sep_la(
1303
0
                img.as_raw(),
1304
0
                &mut dest_image,
1305
0
                filter_image_size,
1306
0
                &x_axis_kernel,
1307
0
                &y_axis_kernel,
1308
            )
1309
0
            .unwrap();
1310
0
            DynamicImage::ImageLumaA8(
1311
0
                GrayAlphaImage::from_raw(img.width(), img.height(), dest_image).unwrap(),
1312
0
            )
1313
        }
1314
0
        DynamicImage::ImageRgb8(img) => {
1315
0
            let mut dest_image = vec![0u8; img.len()];
1316
0
            filter_2d_sep_rgb(
1317
0
                img.as_raw(),
1318
0
                &mut dest_image,
1319
0
                filter_image_size,
1320
0
                &x_axis_kernel,
1321
0
                &y_axis_kernel,
1322
            )
1323
0
            .unwrap();
1324
0
            DynamicImage::ImageRgb8(
1325
0
                RgbImage::from_raw(img.width(), img.height(), dest_image).unwrap(),
1326
0
            )
1327
        }
1328
0
        DynamicImage::ImageRgba8(img) => {
1329
0
            let mut dest_image = vec![0u8; img.len()];
1330
0
            filter_2d_sep_rgba(
1331
0
                img.as_raw(),
1332
0
                &mut dest_image,
1333
0
                filter_image_size,
1334
0
                &x_axis_kernel,
1335
0
                &y_axis_kernel,
1336
            )
1337
0
            .unwrap();
1338
0
            DynamicImage::ImageRgba8(
1339
0
                RgbaImage::from_raw(img.width(), img.height(), dest_image).unwrap(),
1340
0
            )
1341
        }
1342
0
        DynamicImage::ImageLuma16(img) => {
1343
0
            let mut dest_image = vec![0u16; img.len()];
1344
0
            filter_2d_sep_plane_u16(
1345
0
                img.as_raw(),
1346
0
                &mut dest_image,
1347
0
                filter_image_size,
1348
0
                &x_axis_kernel,
1349
0
                &y_axis_kernel,
1350
            )
1351
0
            .unwrap();
1352
0
            DynamicImage::ImageLuma16(
1353
0
                Gray16Image::from_raw(img.width(), img.height(), dest_image).unwrap(),
1354
0
            )
1355
        }
1356
0
        DynamicImage::ImageLumaA16(img) => {
1357
0
            let mut dest_image = vec![0u16; img.len()];
1358
0
            filter_2d_sep_la_u16(
1359
0
                img.as_raw(),
1360
0
                &mut dest_image,
1361
0
                filter_image_size,
1362
0
                &x_axis_kernel,
1363
0
                &y_axis_kernel,
1364
            )
1365
0
            .unwrap();
1366
0
            DynamicImage::ImageLumaA16(
1367
0
                GrayAlpha16Image::from_raw(img.width(), img.height(), dest_image).unwrap(),
1368
0
            )
1369
        }
1370
0
        DynamicImage::ImageRgb16(img) => {
1371
0
            let mut dest_image = vec![0u16; img.len()];
1372
0
            filter_2d_sep_rgb_u16(
1373
0
                img.as_raw(),
1374
0
                &mut dest_image,
1375
0
                filter_image_size,
1376
0
                &x_axis_kernel,
1377
0
                &y_axis_kernel,
1378
            )
1379
0
            .unwrap();
1380
0
            DynamicImage::ImageRgb16(
1381
0
                Rgb16Image::from_raw(img.width(), img.height(), dest_image).unwrap(),
1382
0
            )
1383
        }
1384
0
        DynamicImage::ImageRgba16(img) => {
1385
0
            let mut dest_image = vec![0u16; img.len()];
1386
0
            filter_2d_sep_rgba_u16(
1387
0
                img.as_raw(),
1388
0
                &mut dest_image,
1389
0
                filter_image_size,
1390
0
                &x_axis_kernel,
1391
0
                &y_axis_kernel,
1392
            )
1393
0
            .unwrap();
1394
0
            DynamicImage::ImageRgba16(
1395
0
                Rgba16Image::from_raw(img.width(), img.height(), dest_image).unwrap(),
1396
0
            )
1397
        }
1398
0
        DynamicImage::ImageRgb32F(img) => {
1399
0
            let mut dest_image = vec![0f32; img.len()];
1400
0
            filter_2d_sep_rgb_f32(
1401
0
                img.as_raw(),
1402
0
                &mut dest_image,
1403
0
                filter_image_size,
1404
0
                &x_axis_kernel,
1405
0
                &y_axis_kernel,
1406
            )
1407
0
            .unwrap();
1408
0
            DynamicImage::ImageRgb32F(
1409
0
                Rgb32FImage::from_raw(img.width(), img.height(), dest_image).unwrap(),
1410
0
            )
1411
        }
1412
0
        DynamicImage::ImageRgba32F(img) => {
1413
0
            let mut dest_image = vec![0f32; img.len()];
1414
0
            filter_2d_sep_rgba_f32(
1415
0
                img.as_raw(),
1416
0
                &mut dest_image,
1417
0
                filter_image_size,
1418
0
                &x_axis_kernel,
1419
0
                &y_axis_kernel,
1420
            )
1421
0
            .unwrap();
1422
0
            DynamicImage::ImageRgba32F(
1423
0
                Rgba32FImage::from_raw(img.width(), img.height(), dest_image).unwrap(),
1424
0
            )
1425
        }
1426
    };
1427
1428
    // Must succeed.
1429
0
    let _ = target.set_color_space(image.color_space());
1430
0
    target
1431
0
}
1432
1433
0
fn gaussian_blur_indirect<I: GenericImageView>(
1434
0
    image: &I,
1435
0
    parameters: GaussianBlurParameters,
1436
0
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> {
1437
0
    match I::Pixel::CHANNEL_COUNT {
1438
0
        1 => gaussian_blur_indirect_impl::<I, 1>(image, parameters),
1439
0
        2 => gaussian_blur_indirect_impl::<I, 2>(image, parameters),
1440
0
        3 => gaussian_blur_indirect_impl::<I, 3>(image, parameters),
1441
0
        4 => gaussian_blur_indirect_impl::<I, 4>(image, parameters),
1442
0
        _ => unimplemented!(),
1443
    }
1444
0
}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>>
1445
1446
0
fn gaussian_blur_indirect_impl<I: GenericImageView, const CN: usize>(
1447
0
    image: &I,
1448
0
    parameters: GaussianBlurParameters,
1449
0
) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> {
1450
0
    let mut transient = vec![0f32; image.width() as usize * image.height() as usize * CN];
1451
0
    let transient_chunks = transient.as_chunks_mut::<CN>().0.iter_mut();
1452
0
    for (pixel, dst) in image.pixels().zip(transient_chunks) {
1453
0
        let px = pixel.2.channels();
1454
0
        match CN {
1455
0
            1 => {
1456
0
                dst[0] = NumCast::from(px[0]).unwrap();
1457
0
            }
1458
0
            2 => {
1459
0
                dst[0] = NumCast::from(px[0]).unwrap();
1460
0
                dst[1] = NumCast::from(px[1]).unwrap();
1461
0
            }
1462
0
            3 => {
1463
0
                dst[0] = NumCast::from(px[0]).unwrap();
1464
0
                dst[1] = NumCast::from(px[1]).unwrap();
1465
0
                dst[2] = NumCast::from(px[2]).unwrap();
1466
0
            }
1467
0
            4 => {
1468
0
                dst[0] = NumCast::from(px[0]).unwrap();
1469
0
                dst[1] = NumCast::from(px[1]).unwrap();
1470
0
                dst[2] = NumCast::from(px[2]).unwrap();
1471
0
                dst[3] = NumCast::from(px[3]).unwrap();
1472
0
            }
1473
0
            _ => unreachable!(),
1474
        }
1475
    }
1476
1477
0
    let mut transient_dst = vec![0.; image.width() as usize * image.height() as usize * CN];
1478
1479
0
    let x_axis_kernel = get_gaussian_kernel_1d(
1480
0
        parameters.x_axis_kernel_size as usize,
1481
0
        parameters.x_axis_sigma,
1482
    );
1483
0
    let y_axis_kernel = get_gaussian_kernel_1d(
1484
0
        parameters.y_axis_kernel_size as usize,
1485
0
        parameters.y_axis_sigma,
1486
    );
1487
1488
0
    let filter_image_size = FilterImageSize {
1489
0
        width: image.width() as usize,
1490
0
        height: image.height() as usize,
1491
0
    };
1492
1493
0
    match CN {
1494
0
        1 => {
1495
0
            filter_2d_sep_plane_f32(
1496
0
                &transient,
1497
0
                &mut transient_dst,
1498
0
                filter_image_size,
1499
0
                &x_axis_kernel,
1500
0
                &y_axis_kernel,
1501
0
            )
1502
0
            .unwrap();
1503
0
        }
1504
0
        2 => {
1505
0
            filter_2d_sep_la_f32(
1506
0
                &transient,
1507
0
                &mut transient_dst,
1508
0
                filter_image_size,
1509
0
                &x_axis_kernel,
1510
0
                &y_axis_kernel,
1511
0
            )
1512
0
            .unwrap();
1513
0
        }
1514
0
        3 => {
1515
0
            filter_2d_sep_rgb_f32(
1516
0
                &transient,
1517
0
                &mut transient_dst,
1518
0
                filter_image_size,
1519
0
                &x_axis_kernel,
1520
0
                &y_axis_kernel,
1521
0
            )
1522
0
            .unwrap();
1523
0
        }
1524
0
        4 => {
1525
0
            filter_2d_sep_rgba_f32(
1526
0
                &transient,
1527
0
                &mut transient_dst,
1528
0
                filter_image_size,
1529
0
                &x_axis_kernel,
1530
0
                &y_axis_kernel,
1531
0
            )
1532
0
            .unwrap();
1533
0
        }
1534
0
        _ => unreachable!(),
1535
    }
1536
1537
0
    let mut out = image.buffer_like();
1538
0
    let transient_dst_chunks = transient_dst.as_chunks_mut::<CN>().0.iter_mut();
1539
0
    for (dst, src) in out.pixels_mut().zip(transient_dst_chunks) {
1540
0
        let pix = src.map(|v| NumCast::from(FloatNearest(v)).unwrap());
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, 3>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, 3>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, 3>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, 1>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, 1>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, 4>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, 4>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, 4>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, 2>::{closure#0}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, 2>::{closure#0}
1541
0
        *dst = *Pixel::from_slice(&pix);
1542
    }
1543
1544
0
    out
1545
0
}
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, 3>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, 3>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, 3>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, 1>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, 1>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, 4>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, 4>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, 4>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, 2>
Unexecuted instantiation: image::imageops::sample::gaussian_blur_indirect_impl::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, 2>
1546
1547
/// Performs an unsharpen mask on the supplied image.
1548
///
1549
/// # Arguments:
1550
///
1551
/// * `sigma` - is the amount to blur the image by.
1552
/// * `threshold` - is the threshold for minimal brightness change that will be sharpened.
1553
///
1554
/// This method typically assumes that the input is scene-linear light.
1555
/// If it is not, color distortion may occur.
1556
///
1557
/// See [Digital unsharp masking](https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking) for more information.
1558
0
pub fn unsharpen<I, P, S>(image: &I, sigma: f32, threshold: i32) -> ImageBuffer<P, Vec<S>>
1559
0
where
1560
0
    I: GenericImageView<Pixel = P>,
1561
0
    P: Pixel<Subpixel = S>,
1562
0
    S: Primitive,
1563
{
1564
0
    let mut tmp = blur_advanced(image, GaussianBlurParameters::new_from_sigma(sigma));
1565
1566
0
    let max = S::DEFAULT_MAX_VALUE;
1567
0
    let max: i32 = NumCast::from(max).unwrap();
1568
0
    let (width, height) = image.dimensions();
1569
1570
0
    for y in 0..height {
1571
0
        for x in 0..width {
1572
0
            let a = image.get_pixel(x, y);
1573
0
            let b = tmp.get_pixel_mut(x, y);
1574
1575
0
            let p = a.map2(b, |c, d| {
1576
0
                let ic: i32 = NumCast::from(c).unwrap();
1577
0
                let id: i32 = NumCast::from(d).unwrap();
1578
1579
0
                let diff = ic - id;
1580
1581
0
                if diff.abs() > threshold {
1582
0
                    let e = clamp(ic + diff, 0, max); // FIXME what does this do for f32? clamp 0-1 integers??
1583
1584
0
                    NumCast::from(e).unwrap()
1585
                } else {
1586
0
                    c
1587
                }
1588
0
            });
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>::{closure#0}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>::{closure#0}
1589
1590
0
            *b = p;
1591
        }
1592
    }
1593
1594
0
    tmp
1595
0
}
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgb<f32>, alloc::vec::Vec<f32>>, image::color::Rgb<f32>, f32>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgb<u8>, alloc::vec::Vec<u8>>, image::color::Rgb<u8>, u8>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgb<u16>, alloc::vec::Vec<u16>>, image::color::Rgb<u16>, u16>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Luma<u8>, alloc::vec::Vec<u8>>, image::color::Luma<u8>, u8>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Luma<u16>, alloc::vec::Vec<u16>>, image::color::Luma<u16>, u16>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgba<f32>, alloc::vec::Vec<f32>>, image::color::Rgba<f32>, f32>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgba<u8>, alloc::vec::Vec<u8>>, image::color::Rgba<u8>, u8>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::Rgba<u16>, alloc::vec::Vec<u16>>, image::color::Rgba<u16>, u16>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::LumaA<u8>, alloc::vec::Vec<u8>>, image::color::LumaA<u8>, u8>
Unexecuted instantiation: image::imageops::sample::unsharpen::<image::images::buffer::ImageBuffer<image::color::LumaA<u16>, alloc::vec::Vec<u16>>, image::color::LumaA<u16>, u16>
1596
1597
#[cfg(test)]
1598
mod tests {
1599
    use super::{resize, sample_bilinear, sample_nearest, FilterType};
1600
    use crate::{GenericImageView, ImageBuffer, RgbImage};
1601
    #[cfg(feature = "benchmarks")]
1602
    use test;
1603
1604
    #[bench]
1605
    #[cfg(all(feature = "benchmarks", feature = "png"))]
1606
    fn bench_resize(b: &mut test::Bencher) {
1607
        use std::path::Path;
1608
        let img = crate::open(Path::new("./examples/fractal.png")).unwrap();
1609
        b.iter(|| {
1610
            test::black_box(resize(&img, 200, 200, FilterType::Nearest));
1611
        });
1612
        b.bytes = 800 * 800 * 3 + 200 * 200 * 3;
1613
    }
1614
1615
    #[test]
1616
    #[cfg(feature = "png")]
1617
    fn test_resize_same_size() {
1618
        use std::path::Path;
1619
        let img = crate::open(Path::new("./examples/fractal.png")).unwrap();
1620
        let mut resized = img.clone();
1621
        resized.resize(img.width(), img.height(), FilterType::Triangle);
1622
        assert!(img.pixels().eq(resized.pixels()));
1623
    }
1624
1625
    #[test]
1626
    #[cfg(feature = "png")]
1627
    fn test_sample_bilinear() {
1628
        use std::path::Path;
1629
        let img = crate::open(Path::new("./examples/fractal.png")).unwrap();
1630
        assert!(sample_bilinear(&img, 0., 0.).is_some());
1631
        assert!(sample_bilinear(&img, 1., 0.).is_some());
1632
        assert!(sample_bilinear(&img, 0., 1.).is_some());
1633
        assert!(sample_bilinear(&img, 1., 1.).is_some());
1634
        assert!(sample_bilinear(&img, 0.5, 0.5).is_some());
1635
1636
        assert!(sample_bilinear(&img, 1.2, 0.5).is_none());
1637
        assert!(sample_bilinear(&img, 0.5, 1.2).is_none());
1638
        assert!(sample_bilinear(&img, 1.2, 1.2).is_none());
1639
1640
        assert!(sample_bilinear(&img, -0.1, 0.2).is_none());
1641
        assert!(sample_bilinear(&img, 0.2, -0.1).is_none());
1642
        assert!(sample_bilinear(&img, -0.1, -0.1).is_none());
1643
    }
1644
    #[test]
1645
    #[cfg(feature = "png")]
1646
    fn test_sample_nearest() {
1647
        use std::path::Path;
1648
        let img = crate::open(Path::new("./examples/fractal.png")).unwrap();
1649
        assert!(sample_nearest(&img, 0., 0.).is_some());
1650
        assert!(sample_nearest(&img, 1., 0.).is_some());
1651
        assert!(sample_nearest(&img, 0., 1.).is_some());
1652
        assert!(sample_nearest(&img, 1., 1.).is_some());
1653
        assert!(sample_nearest(&img, 0.5, 0.5).is_some());
1654
1655
        assert!(sample_nearest(&img, 1.2, 0.5).is_none());
1656
        assert!(sample_nearest(&img, 0.5, 1.2).is_none());
1657
        assert!(sample_nearest(&img, 1.2, 1.2).is_none());
1658
1659
        assert!(sample_nearest(&img, -0.1, 0.2).is_none());
1660
        assert!(sample_nearest(&img, 0.2, -0.1).is_none());
1661
        assert!(sample_nearest(&img, -0.1, -0.1).is_none());
1662
    }
1663
    #[test]
1664
    fn test_sample_bilinear_correctness() {
1665
        use crate::Rgba;
1666
        let img = ImageBuffer::from_fn(2, 2, |x, y| match (x, y) {
1667
            (0, 0) => Rgba([255, 0, 0, 0]),
1668
            (0, 1) => Rgba([0, 255, 0, 0]),
1669
            (1, 0) => Rgba([0, 0, 255, 0]),
1670
            (1, 1) => Rgba([0, 0, 0, 255]),
1671
            _ => panic!(),
1672
        });
1673
        assert_eq!(sample_bilinear(&img, 0.5, 0.5), Some(Rgba([64; 4])));
1674
        assert_eq!(sample_bilinear(&img, 0.0, 0.0), Some(Rgba([255, 0, 0, 0])));
1675
        assert_eq!(sample_bilinear(&img, 0.0, 1.0), Some(Rgba([0, 255, 0, 0])));
1676
        assert_eq!(sample_bilinear(&img, 1.0, 0.0), Some(Rgba([0, 0, 255, 0])));
1677
        assert_eq!(sample_bilinear(&img, 1.0, 1.0), Some(Rgba([0, 0, 0, 255])));
1678
1679
        assert_eq!(
1680
            sample_bilinear(&img, 0.5, 0.0),
1681
            Some(Rgba([128, 0, 128, 0]))
1682
        );
1683
        assert_eq!(
1684
            sample_bilinear(&img, 0.0, 0.5),
1685
            Some(Rgba([128, 128, 0, 0]))
1686
        );
1687
        assert_eq!(
1688
            sample_bilinear(&img, 0.5, 1.0),
1689
            Some(Rgba([0, 128, 0, 128]))
1690
        );
1691
        assert_eq!(
1692
            sample_bilinear(&img, 1.0, 0.5),
1693
            Some(Rgba([0, 0, 128, 128]))
1694
        );
1695
    }
1696
    #[bench]
1697
    #[cfg(feature = "benchmarks")]
1698
    fn bench_sample_bilinear(b: &mut test::Bencher) {
1699
        use crate::Rgba;
1700
        let img = ImageBuffer::from_fn(2, 2, |x, y| match (x, y) {
1701
            (0, 0) => Rgba([255, 0, 0, 0]),
1702
            (0, 1) => Rgba([0, 255, 0, 0]),
1703
            (1, 0) => Rgba([0, 0, 255, 0]),
1704
            (1, 1) => Rgba([0, 0, 0, 255]),
1705
            _ => panic!(),
1706
        });
1707
        b.iter(|| {
1708
            sample_bilinear(&img, test::black_box(0.5), test::black_box(0.5));
1709
        });
1710
    }
1711
    #[test]
1712
    fn test_sample_nearest_correctness() {
1713
        use crate::Rgba;
1714
        let img = ImageBuffer::from_fn(2, 2, |x, y| match (x, y) {
1715
            (0, 0) => Rgba([255, 0, 0, 0]),
1716
            (0, 1) => Rgba([0, 255, 0, 0]),
1717
            (1, 0) => Rgba([0, 0, 255, 0]),
1718
            (1, 1) => Rgba([0, 0, 0, 255]),
1719
            _ => panic!(),
1720
        });
1721
1722
        assert_eq!(sample_nearest(&img, 0.0, 0.0), Some(Rgba([255, 0, 0, 0])));
1723
        assert_eq!(sample_nearest(&img, 0.0, 1.0), Some(Rgba([0, 255, 0, 0])));
1724
        assert_eq!(sample_nearest(&img, 1.0, 0.0), Some(Rgba([0, 0, 255, 0])));
1725
        assert_eq!(sample_nearest(&img, 1.0, 1.0), Some(Rgba([0, 0, 0, 255])));
1726
1727
        assert_eq!(sample_nearest(&img, 0.5, 0.5), Some(Rgba([0, 0, 0, 255])));
1728
        assert_eq!(sample_nearest(&img, 0.5, 0.0), Some(Rgba([0, 0, 255, 0])));
1729
        assert_eq!(sample_nearest(&img, 0.0, 0.5), Some(Rgba([0, 255, 0, 0])));
1730
        assert_eq!(sample_nearest(&img, 0.5, 1.0), Some(Rgba([0, 0, 0, 255])));
1731
        assert_eq!(sample_nearest(&img, 1.0, 0.5), Some(Rgba([0, 0, 0, 255])));
1732
    }
1733
1734
    #[bench]
1735
    #[cfg(all(feature = "benchmarks", feature = "tiff"))]
1736
    fn bench_resize_same_size(b: &mut test::Bencher) {
1737
        let path = concat!(
1738
            env!("CARGO_MANIFEST_DIR"),
1739
            "/tests/images/tiff/testsuite/mandrill.tiff"
1740
        );
1741
        let image = crate::open(path).unwrap();
1742
        b.iter(|| {
1743
            test::black_box(image.clone()).resize(
1744
                image.width(),
1745
                image.height(),
1746
                FilterType::CatmullRom,
1747
            );
1748
        });
1749
        b.bytes = u64::from(image.width() * image.height() * 3);
1750
    }
1751
1752
    #[test]
1753
    fn test_issue_186() {
1754
        let img: RgbImage = ImageBuffer::new(100, 100);
1755
        let _ = resize(&img, 50, 50, FilterType::Lanczos3);
1756
    }
1757
1758
    #[bench]
1759
    #[cfg(all(feature = "benchmarks", feature = "tiff"))]
1760
    fn bench_thumbnail(b: &mut test::Bencher) {
1761
        let path = concat!(
1762
            env!("CARGO_MANIFEST_DIR"),
1763
            "/tests/images/tiff/testsuite/mandrill.tiff"
1764
        );
1765
        let image = crate::open(path).unwrap();
1766
        b.iter(|| {
1767
            test::black_box(image.thumbnail(256, 256));
1768
        });
1769
        b.bytes = 512 * 512 * 4 + 256 * 256 * 4;
1770
    }
1771
1772
    #[bench]
1773
    #[cfg(all(feature = "benchmarks", feature = "tiff"))]
1774
    fn bench_thumbnail_upsize(b: &mut test::Bencher) {
1775
        let path = concat!(
1776
            env!("CARGO_MANIFEST_DIR"),
1777
            "/tests/images/tiff/testsuite/mandrill.tiff"
1778
        );
1779
        let image = crate::open(path).unwrap().thumbnail(256, 256);
1780
        b.iter(|| {
1781
            test::black_box(image.thumbnail(512, 512));
1782
        });
1783
        b.bytes = 512 * 512 * 4 + 256 * 256 * 4;
1784
    }
1785
1786
    #[bench]
1787
    #[cfg(all(feature = "benchmarks", feature = "tiff"))]
1788
    fn bench_thumbnail_upsize_irregular(b: &mut test::Bencher) {
1789
        let path = concat!(
1790
            env!("CARGO_MANIFEST_DIR"),
1791
            "/tests/images/tiff/testsuite/mandrill.tiff"
1792
        );
1793
        let image = crate::open(path).unwrap().thumbnail(193, 193);
1794
        b.iter(|| {
1795
            test::black_box(image.thumbnail(256, 256));
1796
        });
1797
        b.bytes = 193 * 193 * 4 + 256 * 256 * 4;
1798
    }
1799
1800
    #[test]
1801
    #[cfg(feature = "png")]
1802
    fn resize_transparent_image() {
1803
        use super::FilterType::{CatmullRom, Gaussian, Lanczos3, Nearest, Triangle};
1804
        use crate::imageops::crop;
1805
        use crate::math::Rect;
1806
        use crate::RgbaImage;
1807
1808
        fn assert_resize(image: &RgbaImage, filter: FilterType) {
1809
            let resized = resize(image, 16, 16, filter);
1810
            let select = Rect::from_xy_ranges(5..11, 5..11);
1811
            let cropped = crop(&resized, select).to_image();
1812
            for pixel in cropped.pixels() {
1813
                let alpha = pixel.0[3];
1814
                assert!(
1815
                    alpha != 254 && alpha != 253,
1816
                    "alpha value: {alpha}, {filter:?}"
1817
                );
1818
            }
1819
        }
1820
1821
        let path = concat!(
1822
            env!("CARGO_MANIFEST_DIR"),
1823
            "/tests/images/png/transparency/tp1n3p08.png"
1824
        );
1825
        let img = crate::open(path).unwrap();
1826
        let rgba8 = img.as_rgba8().unwrap();
1827
        let filters = &[Nearest, Triangle, CatmullRom, Gaussian, Lanczos3];
1828
        for filter in filters {
1829
            assert_resize(rgba8, *filter);
1830
        }
1831
    }
1832
1833
    #[test]
1834
    fn bug_1600() {
1835
        let image = crate::RgbaImage::from_raw(629, 627, vec![255; 629 * 627 * 4]).unwrap();
1836
        let result = resize(&image, 22, 22, FilterType::Lanczos3);
1837
        assert!(result.into_raw().into_iter().any(|c| c != 0));
1838
    }
1839
1840
    #[test]
1841
    fn issue_2340() {
1842
        let empty = crate::GrayImage::from_raw(1 << 31, 0, vec![]).unwrap();
1843
        // Really we're checking that no overflow / outsized allocation happens here.
1844
        let result = resize(&empty, 1, 1, FilterType::Lanczos3);
1845
        assert!(result.into_raw().into_iter().all(|c| c == 0));
1846
        // With the previous strategy before the regression this would allocate 1TB of memory for a
1847
        // temporary during the sampling evaluation.
1848
        let result = resize(&empty, 256, 256, FilterType::Lanczos3);
1849
        assert!(result.into_raw().into_iter().all(|c| c == 0));
1850
    }
1851
1852
    #[test]
1853
    fn issue_2340_refl() {
1854
        // Tests the swapped coordinate version of `issue_2340`.
1855
        let empty = crate::GrayImage::from_raw(0, 1 << 31, vec![]).unwrap();
1856
        let result = resize(&empty, 1, 1, FilterType::Lanczos3);
1857
        assert!(result.into_raw().into_iter().all(|c| c == 0));
1858
        let result = resize(&empty, 256, 256, FilterType::Lanczos3);
1859
        assert!(result.into_raw().into_iter().all(|c| c == 0));
1860
    }
1861
}