Coverage Report

Created: 2025-11-05 08:08

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