Coverage Report

Created: 2026-05-30 07:32

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