Coverage Report

Created: 2026-09-02 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.2/src/image/crop.rs
Line
Count
Source
1
//! Crop away unwanted pixels. Includes automatic detection of bounding
2
//! rectangle. Currently does not support deep data and resolution levels.
3
4
use crate::{
5
    block::BlockIndex,
6
    image::{
7
        write::channels::{ChannelsWriter, GetPixel, WritableChannels},
8
        AnyChannel, AnyChannels, FlatSamples, FlatSamplesPixel, Layer, SpecificChannels,
9
    },
10
    math::{RoundingMode, Vec2},
11
    meta::{
12
        attribute::{ChannelList, IntegerBounds, LevelMode},
13
        header::{Header, LayerAttributes},
14
    },
15
};
16
17
/// Something that has a two-dimensional rectangular shape
18
pub trait GetBounds {
19
    /// The bounding rectangle of this pixel grid.
20
    fn bounds(&self) -> IntegerBounds;
21
}
22
23
/// Inspect the pixels in this image to determine where to crop some away
24
pub trait InspectSample: GetBounds {
25
    /// The type of pixel in this pixel grid.
26
    type Sample;
27
28
    /// Index is not in world coordinates, but within the data window.
29
    /// Position `(0,0)` always represents the top left pixel.
30
    fn inspect_sample(&self, local_index: Vec2<usize>) -> Self::Sample;
31
}
32
33
/// Crop some pixels ways when specifying a smaller rectangle
34
pub trait Crop: Sized {
35
    /// The type of  this image after cropping (probably the same as before)
36
    type Cropped;
37
38
    /// Crop the image to exclude unwanted pixels.
39
    /// Panics for invalid (larger than previously) bounds.
40
    /// The bounds are specified in absolute coordinates.
41
    /// Does not reduce allocation size of the current image, but instead only
42
    /// adjust a few boundary numbers. Use `reallocate_cropped()` on the
43
    /// return value to actually reduce the memory footprint.
44
    fn crop(self, bounds: IntegerBounds) -> Self::Cropped;
45
46
    /// Reduce your image to a smaller part, usually to save memory.
47
    /// Crop if bounds are specified, return the original if no bounds are
48
    /// specified. Does not reduce allocation size of the current image, but
49
    /// instead only adjust a few boundary numbers.
50
    /// Use `reallocate_cropped()` on the return value to actually reduce the
51
    /// memory footprint.
52
0
    fn try_crop(self, bounds: Option<IntegerBounds>) -> CropResult<Self::Cropped, Self> {
53
0
        match bounds {
54
0
            Some(bounds) => CropResult::Cropped(self.crop(bounds)),
55
0
            None => CropResult::Empty {
56
0
                original: self,
57
0
            },
58
        }
59
0
    }
60
}
61
62
/// Cropping an image fails if the image is fully transparent.
63
/// Use [`or_crop_to_1x1_if_empty`] or [`or_none_if_empty`] to obtain a normal
64
/// image again.
65
#[must_use]
66
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
67
pub enum CropResult<Cropped, Old> {
68
    /// The image contained some pixels and has been cropped or left untouched
69
    Cropped(Cropped),
70
71
    /// All pixels in the image would be discarded, removing the whole image
72
    Empty {
73
        /// The fully discarded image which caused the cropping to fail
74
        original: Old,
75
    },
76
}
77
78
/// Crop away unwanted pixels from the border if they match the specified rule.
79
pub trait CropWhere<Sample>: Sized {
80
    /// The type of the cropped image (probably the same as the original image).
81
    type Cropped;
82
83
    /// Crop away unwanted pixels from the border if they match the specified
84
    /// rule. Does not reduce allocation size of the current image, but
85
    /// instead only adjust a few boundary numbers.
86
    /// Use `reallocate_cropped()` on the return value to actually reduce the
87
    /// memory footprint.
88
    fn crop_where(self, discard_if: impl Fn(Sample) -> bool) -> CropResult<Self::Cropped, Self>;
89
90
    /// Crop away unwanted pixels from the border if they match the specified
91
    /// color. If you want discard based on a rule, use `crop_where` with a
92
    /// closure instead. Does not reduce allocation size of the current
93
    /// image, but instead only adjust a few boundary numbers.
94
    /// Use `reallocate_cropped()` on the return value to actually reduce the
95
    /// memory footprint.
96
    fn crop_where_eq(self, discard_color: impl Into<Sample>) -> CropResult<Self::Cropped, Self>
97
    where
98
        Sample: PartialEq;
99
100
    /// Convert this data to cropped data without discarding any pixels.
101
    fn crop_nowhere(self) -> Self::Cropped;
102
}
103
104
impl<Channels> Crop for Layer<Channels> {
105
    type Cropped = Layer<CroppedChannels<Channels>>;
106
107
0
    fn crop(self, bounds: IntegerBounds) -> Self::Cropped {
108
0
        CroppedChannels::crop_layer(bounds, self)
109
0
    }
110
}
111
112
impl<T> CropWhere<T::Sample> for T
113
where
114
    T: Crop + InspectSample,
115
{
116
    type Cropped = <Self as Crop>::Cropped;
117
118
0
    fn crop_where(self, discard_if: impl Fn(T::Sample) -> bool) -> CropResult<Self::Cropped, Self> {
119
0
        let smaller_bounds = {
120
0
            let keep_if = |position| !discard_if(self.inspect_sample(position));
121
0
            try_find_smaller_bounds(self.bounds(), keep_if)
122
        };
123
124
0
        self.try_crop(smaller_bounds)
125
0
    }
126
127
0
    fn crop_where_eq(self, discard_color: impl Into<T::Sample>) -> CropResult<Self::Cropped, Self>
128
0
    where
129
0
        T::Sample: PartialEq,
130
    {
131
0
        let discard_color: T::Sample = discard_color.into();
132
0
        self.crop_where(|sample| sample == discard_color)
133
0
    }
134
135
0
    fn crop_nowhere(self) -> Self::Cropped {
136
0
        let current_bounds = self.bounds();
137
0
        self.crop(current_bounds)
138
0
    }
139
}
140
141
/// A smaller window into an existing pixel storage
142
#[derive(Debug, Clone, Eq, PartialEq)]
143
pub struct CroppedChannels<Channels> {
144
    /// The uncropped pixel storage
145
    pub full_channels: Channels,
146
147
    /// The uncropped pixel storage bounds
148
    pub full_bounds: IntegerBounds,
149
150
    /// The cropped pixel storage bounds
151
    pub cropped_bounds: IntegerBounds,
152
}
153
154
impl<Channels> CroppedChannels<Channels> {
155
    /// Wrap a layer in a cropped view with adjusted bounds, but without
156
    /// reallocating your pixels
157
0
    pub fn crop_layer(
158
0
        new_bounds: IntegerBounds,
159
0
        layer: Layer<Channels>,
160
0
    ) -> Layer<CroppedChannels<Channels>> {
161
0
        Layer {
162
0
            channel_data: CroppedChannels {
163
0
                cropped_bounds: new_bounds,
164
0
                full_bounds: layer.absolute_bounds(),
165
0
                full_channels: layer.channel_data,
166
0
            },
167
0
168
0
            size: new_bounds.size,
169
0
170
0
            attributes: LayerAttributes {
171
0
                layer_position: new_bounds.position,
172
0
                ..layer.attributes
173
0
            },
174
0
175
0
            encoding: layer.encoding,
176
0
        }
177
0
    }
178
}
179
180
// TODO make cropped view readable if you only need a specific section of the
181
// image?
182
183
// make cropped view writable:
184
185
impl<'slf, Channels: 'slf> WritableChannels<'slf> for CroppedChannels<Channels>
186
where
187
    Channels: WritableChannels<'slf>,
188
{
189
    type Writer = CroppedWriter<Channels::Writer>;
190
191
0
    fn infer_channel_list(&self) -> ChannelList {
192
0
        self.full_channels.infer_channel_list() // no need for adjustments, as
193
                                                // the layer content already
194
                                                // reflects the changes
195
0
    }
196
197
0
    fn infer_level_modes(&self) -> (LevelMode, RoundingMode) {
198
0
        self.full_channels.infer_level_modes()
199
0
    }
200
201
0
    fn create_writer(&'slf self, header: &Header) -> Self::Writer {
202
0
        let offset = (self.cropped_bounds.position - self.full_bounds.position)
203
0
            .to_usize("invalid cropping bounds for cropped view")
204
0
            .unwrap();
205
206
0
        CroppedWriter {
207
0
            channels: self.full_channels.create_writer(header),
208
0
            offset,
209
0
        }
210
0
    }
211
}
212
213
/// A writer for the cropped view layer
214
#[derive(Debug, Clone, PartialEq)]
215
pub struct CroppedWriter<ChannelsWriter> {
216
    channels: ChannelsWriter,
217
    offset: Vec2<usize>,
218
}
219
220
impl<'c, Channels> ChannelsWriter for CroppedWriter<Channels>
221
where
222
    Channels: ChannelsWriter,
223
{
224
0
    fn extract_uncompressed_block(&self, header: &Header, block: BlockIndex) -> Vec<u8> {
225
0
        let block = BlockIndex {
226
0
            pixel_position: block.pixel_position + self.offset,
227
0
            ..block
228
0
        };
229
230
0
        self.channels.extract_uncompressed_block(header, block)
231
0
    }
232
}
233
234
impl<Samples, Channels> InspectSample for Layer<SpecificChannels<Samples, Channels>>
235
where
236
    Samples: GetPixel,
237
{
238
    type Sample = Samples::Pixel;
239
240
0
    fn inspect_sample(&self, local_index: Vec2<usize>) -> Samples::Pixel {
241
0
        self.channel_data.pixels.get_pixel(local_index)
242
0
    }
243
}
244
245
impl InspectSample for Layer<AnyChannels<FlatSamples>> {
246
    type Sample = FlatSamplesPixel;
247
248
0
    fn inspect_sample(&self, local_index: Vec2<usize>) -> FlatSamplesPixel {
249
0
        self.sample_vec_at(local_index)
250
0
    }
251
}
252
253
// ALGORITHM IDEA: for arbitrary channels, find the most desired channel,
254
// and process that first, keeping the processed bounds as starting point for
255
// the other layers
256
257
/// Realize a cropped view of the original data,
258
/// by actually removing the unwanted original pixels,
259
/// reducing the memory consumption.
260
/// Currently not supported for `SpecificChannels`.
261
pub trait ApplyCroppedView {
262
    /// The simpler type after cropping is realized
263
    type Reallocated;
264
265
    /// Make the cropping real by reallocating the underlying storage,
266
    /// with the goal of reducing total memory usage.
267
    /// Currently not supported for `SpecificChannels`.
268
    fn reallocate_cropped(self) -> Self::Reallocated;
269
}
270
271
impl ApplyCroppedView for Layer<CroppedChannels<AnyChannels<FlatSamples>>> {
272
    type Reallocated = Layer<AnyChannels<FlatSamples>>;
273
274
0
    fn reallocate_cropped(self) -> Self::Reallocated {
275
0
        let cropped_absolute_bounds = self.channel_data.cropped_bounds;
276
0
        let cropped_relative_bounds =
277
0
            cropped_absolute_bounds.with_origin(-self.channel_data.full_bounds.position);
278
279
0
        assert!(
280
0
            self.absolute_bounds().contains(cropped_absolute_bounds),
281
            "bounds not valid for layer dimensions"
282
        );
283
0
        assert!(cropped_relative_bounds.size.area() > 0, "the cropped image would be empty");
284
285
        Layer {
286
0
            channel_data: if cropped_relative_bounds.size == self.channel_data.full_bounds.size {
287
0
                assert_eq!(
288
                    cropped_absolute_bounds.position, self.channel_data.full_bounds.position,
289
0
                    "crop bounds size equals, but position does not"
290
                );
291
292
                // the cropping would not remove any pixels
293
0
                self.channel_data.full_channels
294
            } else {
295
0
                let start_x = cropped_relative_bounds.position.x() as usize; // safe, because just checked above
296
0
                let start_y = cropped_relative_bounds.position.y() as usize; // safe, because just checked above
297
0
                let x_range = start_x..start_x + cropped_relative_bounds.size.width();
298
0
                let old_width = self.channel_data.full_bounds.size.width();
299
0
                let new_height = cropped_relative_bounds.size.height();
300
301
0
                let channels = self
302
0
                    .channel_data
303
0
                    .full_channels
304
0
                    .list
305
0
                    .into_iter()
306
0
                    .map(|channel: AnyChannel<FlatSamples>| {
307
0
                        fn crop_samples<T: Copy>(
308
0
                            samples: Vec<T>,
309
0
                            old_width: usize,
310
0
                            new_height: usize,
311
0
                            x_range: std::ops::Range<usize>,
312
0
                            y_start: usize,
313
0
                        ) -> Vec<T> {
314
0
                            let filtered_lines =
315
0
                                samples.chunks_exact(old_width).skip(y_start).take(new_height);
316
0
                            let trimmed_lines = filtered_lines.map(|line| &line[x_range.clone()]);
Unexecuted instantiation: <exr::image::Layer<exr::image::crop::CroppedChannels<exr::image::AnyChannels<exr::image::FlatSamples>>> as exr::image::crop::ApplyCroppedView>::reallocate_cropped::{closure#0}::crop_samples::<half::binary16::f16>::{closure#0}
Unexecuted instantiation: <exr::image::Layer<exr::image::crop::CroppedChannels<exr::image::AnyChannels<exr::image::FlatSamples>>> as exr::image::crop::ApplyCroppedView>::reallocate_cropped::{closure#0}::crop_samples::<f32>::{closure#0}
Unexecuted instantiation: <exr::image::Layer<exr::image::crop::CroppedChannels<exr::image::AnyChannels<exr::image::FlatSamples>>> as exr::image::crop::ApplyCroppedView>::reallocate_cropped::{closure#0}::crop_samples::<u32>::{closure#0}
317
0
                            trimmed_lines.flatten().map(|x| *x).collect() // TODO does this use memcpy?
318
0
                        }
Unexecuted instantiation: <exr::image::Layer<exr::image::crop::CroppedChannels<exr::image::AnyChannels<exr::image::FlatSamples>>> as exr::image::crop::ApplyCroppedView>::reallocate_cropped::{closure#0}::crop_samples::<half::binary16::f16>
Unexecuted instantiation: <exr::image::Layer<exr::image::crop::CroppedChannels<exr::image::AnyChannels<exr::image::FlatSamples>>> as exr::image::crop::ApplyCroppedView>::reallocate_cropped::{closure#0}::crop_samples::<f32>
Unexecuted instantiation: <exr::image::Layer<exr::image::crop::CroppedChannels<exr::image::AnyChannels<exr::image::FlatSamples>>> as exr::image::crop::ApplyCroppedView>::reallocate_cropped::{closure#0}::crop_samples::<u32>
319
320
0
                        let samples = match channel.sample_data {
321
0
                            FlatSamples::F16(samples) => FlatSamples::F16(crop_samples(
322
0
                                samples,
323
0
                                old_width,
324
0
                                new_height,
325
0
                                x_range.clone(),
326
0
                                start_y,
327
0
                            )),
328
329
0
                            FlatSamples::F32(samples) => FlatSamples::F32(crop_samples(
330
0
                                samples,
331
0
                                old_width,
332
0
                                new_height,
333
0
                                x_range.clone(),
334
0
                                start_y,
335
0
                            )),
336
337
0
                            FlatSamples::U32(samples) => FlatSamples::U32(crop_samples(
338
0
                                samples,
339
0
                                old_width,
340
0
                                new_height,
341
0
                                x_range.clone(),
342
0
                                start_y,
343
0
                            )),
344
                        };
345
346
0
                        AnyChannel {
347
0
                            sample_data: samples,
348
0
                            ..channel
349
0
                        }
350
0
                    })
351
0
                    .collect();
352
353
0
                AnyChannels {
354
0
                    list: channels,
355
0
                }
356
            },
357
358
0
            attributes: self.attributes,
359
0
            encoding: self.encoding,
360
0
            size: self.size,
361
        }
362
0
    }
363
}
364
365
/// Return the smallest bounding rectangle including all pixels that satisfy the
366
/// predicate. Worst case: Fully transparent image, visits each pixel once.
367
/// Best case: Fully opaque image, visits two pixels.
368
/// Returns `None` if the image is fully transparent.
369
/// Returns `[(0,0), size]` if the image is fully opaque.
370
/// Designed to be cache-friendly linear search. Optimized for row-major image
371
/// vectors.
372
0
pub fn try_find_smaller_bounds(
373
0
    current_bounds: IntegerBounds,
374
0
    pixel_at: impl Fn(Vec2<usize>) -> bool,
375
0
) -> Option<IntegerBounds> {
376
0
    assert_ne!(
377
0
        current_bounds.size.area(),
378
        0,
379
0
        "cannot find smaller bounds of an image with zero width or height"
380
    );
381
0
    let Vec2(width, height) = current_bounds.size;
382
383
    // scans top to bottom (left to right)
384
0
    let first_top_left_pixel = (0..height)
385
0
        .flat_map(|y| (0..width).map(move |x| Vec2(x, y)))
386
0
        .find(|&position| pixel_at(position))?; // return none if no pixel should be kept
387
388
    // scans bottom to top (right to left)
389
0
    let first_bottom_right_pixel = (first_top_left_pixel.y() + 1..height) // excluding the top line
390
0
        .flat_map(|y| (0..width).map(move |x| Vec2(x, y))) // x search cannot start at first_top.x, because this must catch all bottom pixels
391
0
        .rev()
392
0
        .find(|&position| pixel_at(position))
393
0
        .unwrap_or(first_top_left_pixel); // did not find any at bottom, but we know top has some pixel
394
395
    // now we know exactly how much we can throw away top and bottom,
396
    // but we don't know exactly about left or right
397
0
    let top = first_top_left_pixel.y();
398
0
    let bottom = first_bottom_right_pixel.y();
399
400
    // we only now some arbitrary left and right bounds which we need to refine.
401
    // because the actual image contents might be wider than the corner points.
402
    // we know that we do not need to look in the center between min x and max x,
403
    // as these must be included in any case.
404
0
    let mut min_left_x = first_top_left_pixel.x().min(first_bottom_right_pixel.x());
405
0
    let mut max_right_x = first_bottom_right_pixel.x().max(first_top_left_pixel.x());
406
407
    // requires for loop, because bounds change while searching
408
0
    for y in top..=bottom {
409
        // escape the loop if there is nothing left to crop
410
0
        if min_left_x == 0 && max_right_x == width - 1 {
411
0
            break;
412
0
        }
413
414
        // search from right image edge towards image center, until known max x, for
415
        // existing pixels, possibly including some pixels that would have been
416
        // cropped otherwise
417
0
        if max_right_x != width - 1 {
418
0
            max_right_x = (max_right_x + 1..width)
419
0
                .rev() // excluding current max
420
0
                .find(|&x| pixel_at(Vec2(x, y)))
421
0
                .unwrap_or(max_right_x);
422
0
        }
423
424
        // search from left image edge towards image center, until known min x, for
425
        // existing pixels, possibly including some pixels that would have been
426
        // cropped otherwise
427
0
        if min_left_x != 0 {
428
0
            min_left_x = (0..min_left_x) // excluding current min
429
0
                .find(|&x| pixel_at(Vec2(x, y)))
430
0
                .unwrap_or(min_left_x);
431
0
        }
432
    }
433
434
    // TODO add 1px margin to avoid interpolation issues?
435
0
    let local_start = Vec2(min_left_x, top);
436
0
    let local_end = Vec2(max_right_x + 1, bottom + 1);
437
0
    Some(IntegerBounds::new(
438
0
        current_bounds.position + local_start.to_i32(),
439
0
        local_end - local_start,
440
0
    ))
441
0
}
442
443
impl<S> GetBounds for Layer<S> {
444
0
    fn bounds(&self) -> IntegerBounds {
445
0
        self.absolute_bounds()
446
0
    }
447
}
448
449
impl<Cropped, Original> CropResult<Cropped, Original> {
450
    /// If the image was fully empty, return `None`, otherwise return
451
    /// `Some(cropped_image)`.
452
0
    pub fn or_none_if_empty(self) -> Option<Cropped> {
453
0
        match self {
454
0
            CropResult::Cropped(cropped) => Some(cropped),
455
            CropResult::Empty {
456
                ..
457
0
            } => None,
458
        }
459
0
    }
460
461
    /// If the image was fully empty, crop to one single pixel of all the
462
    /// transparent pixels instead, leaving the layer intact while reducing
463
    /// memory usage.
464
    ///
465
    /// # Panics
466
    /// Panics if the layer has zero width and height (which indicates an
467
    /// invalid layer state). This should never happen with properly
468
    /// constructed images.
469
0
    pub fn or_crop_to_1x1_if_empty(self) -> Cropped
470
0
    where
471
0
        Original: Crop<Cropped = Cropped> + GetBounds,
472
    {
473
0
        match self {
474
0
            CropResult::Cropped(cropped) => cropped,
475
            CropResult::Empty {
476
0
                original,
477
            } => {
478
0
                let bounds = original.bounds();
479
0
                if bounds.size == Vec2(0, 0) {
480
0
                    unreachable!("layer has zero width and height - this indicates an invalid layer state that should have been caught during construction")
481
0
                }
482
0
                original.crop(IntegerBounds::new(bounds.position, Vec2(1, 1)))
483
            }
484
        }
485
0
    }
486
}
487
488
#[cfg(test)]
489
mod test {
490
    use super::*;
491
492
    #[test]
493
    fn find_bounds() {
494
        fn find_bounds(offset: Vec2<i32>, lines: &Vec<Vec<i32>>) -> IntegerBounds {
495
            if let Some(first_line) = lines.first() {
496
                assert!(
497
                    lines.iter().all(|line| line.len() == first_line.len()),
498
                    "invalid test input"
499
                );
500
                IntegerBounds::new(offset, (first_line.len(), lines.len()))
501
            } else {
502
                IntegerBounds::new(offset, (0, 0))
503
            }
504
        }
505
506
        fn assert_found_smaller_bounds(
507
            offset: Vec2<i32>,
508
            uncropped_lines: Vec<Vec<i32>>,
509
            expected_cropped_lines: Vec<Vec<i32>>,
510
        ) {
511
            let old_bounds = find_bounds(offset, &uncropped_lines);
512
513
            let found_bounds = try_find_smaller_bounds(old_bounds, |position| {
514
                uncropped_lines[position.y()][position.x()] != 0
515
            })
516
            .unwrap();
517
518
            let found_bounds = found_bounds.with_origin(-offset); // make indices local
519
520
            let cropped_lines: Vec<Vec<i32>> = uncropped_lines
521
                [found_bounds.position.y() as usize..found_bounds.end().y() as usize]
522
                .iter()
523
                .map(|uncropped_line| {
524
                    uncropped_line
525
                        [found_bounds.position.x() as usize..found_bounds.end().x() as usize]
526
                        .to_vec()
527
                })
528
                .collect();
529
530
            assert_eq!(cropped_lines, expected_cropped_lines);
531
        }
532
533
        assert_found_smaller_bounds(
534
            Vec2(-3, -3),
535
            vec![vec![2, 3, 4], vec![2, 3, 4]],
536
            vec![vec![2, 3, 4], vec![2, 3, 4]],
537
        );
538
539
        assert_found_smaller_bounds(Vec2(-3, -3), vec![vec![2]], vec![vec![2]]);
540
541
        assert_found_smaller_bounds(
542
            Vec2(-3, -3),
543
            vec![vec![0], vec![2], vec![0], vec![0]],
544
            vec![vec![2]],
545
        );
546
547
        assert_found_smaller_bounds(Vec2(-3, -3), vec![vec![0, 0, 0, 3, 0]], vec![vec![3]]);
548
549
        assert_found_smaller_bounds(
550
            Vec2(3, 3),
551
            vec![vec![0, 1, 1, 2, 1, 0], vec![0, 1, 3, 1, 1, 0], vec![0, 1, 1, 1, 1, 0]],
552
            vec![vec![1, 1, 2, 1], vec![1, 3, 1, 1], vec![1, 1, 1, 1]],
553
        );
554
555
        assert_found_smaller_bounds(
556
            Vec2(3, 3),
557
            vec![
558
                vec![0, 0, 0, 0],
559
                vec![1, 1, 2, 1],
560
                vec![1, 3, 1, 1],
561
                vec![1, 1, 1, 1],
562
                vec![0, 0, 0, 0],
563
            ],
564
            vec![vec![1, 1, 2, 1], vec![1, 3, 1, 1], vec![1, 1, 1, 1]],
565
        );
566
567
        assert_found_smaller_bounds(
568
            Vec2(3, 3),
569
            vec![vec![0, 1, 1, 2, 1, 0], vec![0, 0, 3, 1, 0, 0], vec![0, 1, 1, 1, 1, 0]],
570
            vec![vec![1, 1, 2, 1], vec![0, 3, 1, 0], vec![1, 1, 1, 1]],
571
        );
572
573
        assert_found_smaller_bounds(
574
            Vec2(3, 3),
575
            vec![vec![0, 0, 1, 2, 0, 0], vec![0, 1, 3, 1, 1, 0], vec![0, 0, 1, 1, 0, 0]],
576
            vec![vec![0, 1, 2, 0], vec![1, 3, 1, 1], vec![0, 1, 1, 0]],
577
        );
578
579
        assert_found_smaller_bounds(
580
            Vec2(1, 3),
581
            vec![vec![1, 0, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0]],
582
            vec![vec![1]],
583
        );
584
585
        assert_found_smaller_bounds(
586
            Vec2(1, 3),
587
            vec![vec![0, 0, 0, 0], vec![0, 1, 0, 0], vec![0, 0, 0, 0]],
588
            vec![vec![1]],
589
        );
590
591
        assert_found_smaller_bounds(
592
            Vec2(-1, -3),
593
            vec![vec![0, 0, 0, 0], vec![0, 0, 0, 1], vec![0, 0, 0, 0]],
594
            vec![vec![1]],
595
        );
596
597
        assert_found_smaller_bounds(
598
            Vec2(-1, -3),
599
            vec![
600
                vec![0, 0, 0, 0, 0, 0, 0],
601
                vec![0, 0, 0, 0, 0, 0, 0],
602
                vec![0, 0, 1, 1, 1, 0, 0],
603
                vec![0, 0, 1, 1, 1, 0, 0],
604
                vec![0, 0, 1, 1, 1, 0, 0],
605
                vec![0, 0, 0, 0, 0, 0, 0],
606
                vec![0, 0, 0, 0, 0, 0, 0],
607
            ],
608
            vec![vec![1, 1, 1], vec![1, 1, 1], vec![1, 1, 1]],
609
        );
610
611
        assert_found_smaller_bounds(
612
            Vec2(1000, -300),
613
            vec![
614
                vec![0, 0, 0, 0, 0, 0, 0],
615
                vec![0, 0, 0, 0, 0, 0, 0],
616
                vec![0, 0, 1, 1, 1, 0, 0],
617
                vec![0, 1, 1, 1, 1, 1, 0],
618
                vec![0, 0, 1, 1, 1, 0, 0],
619
                vec![0, 0, 0, 0, 0, 0, 0],
620
                vec![0, 0, 0, 0, 0, 0, 0],
621
            ],
622
            vec![vec![0, 1, 1, 1, 0], vec![1, 1, 1, 1, 1], vec![0, 1, 1, 1, 0]],
623
        );
624
625
        assert_found_smaller_bounds(
626
            Vec2(-10, -300),
627
            vec![
628
                vec![0, 0, 0, 0, 0, 0, 0],
629
                vec![0, 0, 0, 0, 0, 0, 0],
630
                vec![0, 0, 1, 0, 1, 0, 0],
631
                vec![0, 0, 0, 0, 0, 0, 0],
632
                vec![0, 0, 1, 0, 1, 0, 0],
633
                vec![0, 0, 0, 0, 0, 0, 0],
634
                vec![0, 0, 0, 0, 0, 0, 0],
635
            ],
636
            vec![vec![1, 0, 1], vec![0, 0, 0], vec![1, 0, 1]],
637
        );
638
639
        assert_found_smaller_bounds(
640
            Vec2(-10, -300),
641
            vec![
642
                vec![0, 0, 0, 0, 0, 0, 0],
643
                vec![0, 0, 0, 0, 0, 0, 0],
644
                vec![0, 0, 1, 0, 1, 0, 0],
645
                vec![0, 0, 0, 0, 0, 0, 0],
646
                vec![0, 0, 0, 0, 0, 0, 0],
647
                vec![0, 0, 0, 0, 0, 0, 0],
648
                vec![0, 0, 0, 0, 0, 0, 0],
649
            ],
650
            vec![vec![1, 0, 1]],
651
        );
652
653
        assert_found_smaller_bounds(
654
            Vec2(-10, -300),
655
            vec![
656
                vec![0, 0, 0, 0, 0, 0, 0],
657
                vec![0, 0, 0, 1, 0, 0, 0],
658
                vec![0, 0, 0, 2, 0, 0, 0],
659
                vec![0, 0, 3, 3, 3, 0, 0],
660
                vec![0, 0, 0, 4, 0, 0, 0],
661
                vec![0, 0, 0, 0, 0, 0, 0],
662
            ],
663
            vec![vec![0, 1, 0], vec![0, 2, 0], vec![3, 3, 3], vec![0, 4, 0]],
664
        );
665
666
        assert_found_smaller_bounds(
667
            Vec2(-10, -300),
668
            vec![
669
                vec![0, 0, 0, 0, 0, 0, 0],
670
                vec![0, 0, 0, 0, 0, 0, 0],
671
                vec![0, 0, 0, 0, 1, 0, 0],
672
                vec![0, 0, 0, 0, 0, 0, 0],
673
                vec![0, 0, 0, 0, 0, 0, 0],
674
                vec![0, 0, 1, 0, 0, 0, 0],
675
                vec![0, 0, 0, 0, 0, 0, 0],
676
            ],
677
            vec![vec![0, 0, 1], vec![0, 0, 0], vec![0, 0, 0], vec![1, 0, 0]],
678
        );
679
680
        assert_found_smaller_bounds(
681
            Vec2(-10, -300),
682
            vec![
683
                vec![0, 0, 0, 0, 0, 0, 0],
684
                vec![0, 0, 0, 0, 0, 0, 0],
685
                vec![0, 0, 1, 0, 0, 0, 0],
686
                vec![0, 0, 0, 0, 0, 0, 0],
687
                vec![0, 0, 0, 0, 0, 1, 0],
688
                vec![0, 0, 0, 0, 0, 0, 0],
689
                vec![0, 0, 0, 0, 0, 0, 0],
690
            ],
691
            vec![vec![1, 0, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 1]],
692
        );
693
694
        assert_found_smaller_bounds(
695
            Vec2(-10, -300),
696
            vec![
697
                vec![0, 0, 0, 0, 0, 0, 0],
698
                vec![0, 0, 0, 0, 0, 0, 0],
699
                vec![0, 0, 1, 0, 0, 0, 0],
700
                vec![0, 0, 0, 0, 0, 0, 0],
701
                vec![0, 0, 0, 0, 0, 0, 0],
702
                vec![0, 0, 1, 0, 0, 0, 0],
703
                vec![0, 0, 0, 0, 0, 0, 0],
704
            ],
705
            vec![vec![1], vec![0], vec![0], vec![1]],
706
        );
707
708
        assert_found_smaller_bounds(
709
            Vec2(-1, -3),
710
            vec![vec![0, 0, 1, 0], vec![0, 0, 0, 1], vec![0, 0, 0, 0]],
711
            vec![vec![1, 0], vec![0, 1]],
712
        );
713
714
        assert_found_smaller_bounds(
715
            Vec2(-1, -3),
716
            vec![vec![1, 0, 0, 0], vec![0, 1, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0]],
717
            vec![vec![1, 0], vec![0, 1]],
718
        );
719
    }
720
721
    #[test]
722
    fn find_no_bounds() {
723
        let pixels = vec![vec![0, 0, 0, 0], vec![0, 0, 0, 0], vec![0, 0, 0, 0]];
724
725
        let bounds = try_find_smaller_bounds(IntegerBounds::new((0, 0), (4, 3)), |position| {
726
            pixels[position.y()][position.x()] != 0
727
        });
728
729
        assert_eq!(bounds, None)
730
    }
731
}