exr/image/
crop.rs

1//! Crop away unwanted pixels. Includes automatic detection of bounding rectangle.
2//! Currently does not support deep data and resolution levels.
3
4use crate::meta::attribute::{IntegerBounds, LevelMode, ChannelList};
5use crate::math::{Vec2, RoundingMode};
6use crate::image::{Layer, FlatSamples, SpecificChannels, AnyChannels, FlatSamplesPixel, AnyChannel};
7use crate::image::write::channels::{GetPixel, WritableChannels, ChannelsWriter};
8use crate::meta::header::{LayerAttributes, Header};
9use crate::block::BlockIndex;
10
11/// Something that has a two-dimensional rectangular shape
12pub 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
19pub 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
30pub 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    fn try_crop(self, bounds: Option<IntegerBounds>) -> CropResult<Self::Cropped, Self> {
47        match bounds {
48            Some(bounds) => CropResult::Cropped(self.crop(bounds)),
49            None => CropResult::Empty { original: self },
50        }
51    }
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)]
58pub 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.
72pub 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
92impl<Channels> Crop for Layer<Channels> {
93    type Cropped = Layer<CroppedChannels<Channels>>;
94
95    fn crop(self, bounds: IntegerBounds) -> Self::Cropped {
96        CroppedChannels::crop_layer(bounds, self)
97    }
98}
99
100impl<T> CropWhere<T::Sample> for T where T: Crop + InspectSample {
101    type Cropped = <Self as Crop>::Cropped;
102
103    fn crop_where(self, discard_if: impl Fn(T::Sample) -> bool) -> CropResult<Self::Cropped, Self> {
104        let smaller_bounds = {
105            let keep_if = |position| !discard_if(self.inspect_sample(position));
106            try_find_smaller_bounds(self.bounds(), keep_if)
107        };
108
109        self.try_crop(smaller_bounds)
110    }
111
112    fn crop_where_eq(self, discard_color: impl Into<T::Sample>) -> CropResult<Self::Cropped, Self> where T::Sample: PartialEq {
113        let discard_color: T::Sample = discard_color.into();
114        self.crop_where(|sample| sample == discard_color)
115    }
116
117    fn crop_nowhere(self) -> Self::Cropped {
118        let current_bounds = self.bounds();
119        self.crop(current_bounds)
120    }
121}
122
123/// A smaller window into an existing pixel storage
124#[derive(Debug, Clone, Eq, PartialEq)]
125pub 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
137impl<Channels> CroppedChannels<Channels> {
138
139    /// Wrap a layer in a cropped view with adjusted bounds, but without reallocating your pixels
140    pub fn crop_layer(new_bounds: IntegerBounds, layer: Layer<Channels>) -> Layer<CroppedChannels<Channels>> {
141        Layer {
142            channel_data: CroppedChannels {
143                cropped_bounds: new_bounds,
144                full_bounds: layer.absolute_bounds(),
145                full_channels: layer.channel_data,
146            },
147
148            size: new_bounds.size,
149
150            attributes: LayerAttributes {
151                layer_position: new_bounds.position,
152                .. layer.attributes
153            },
154
155            encoding: layer.encoding
156        }
157    }
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
164impl<'slf, Channels:'slf> WritableChannels<'slf> for CroppedChannels<Channels> where Channels: WritableChannels<'slf> {
165    fn infer_channel_list(&self) -> ChannelList {
166        self.full_channels.infer_channel_list() // no need for adjustments, as the layer content already reflects the changes
167    }
168
169    fn infer_level_modes(&self) -> (LevelMode, RoundingMode) {
170        self.full_channels.infer_level_modes()
171    }
172
173    type Writer = CroppedWriter<Channels::Writer>;
174
175    fn create_writer(&'slf self, header: &Header) -> Self::Writer {
176        let offset = (self.cropped_bounds.position - self.full_bounds.position)
177            .to_usize("invalid cropping bounds for cropped view").unwrap();
178
179        CroppedWriter { channels: self.full_channels.create_writer(header), offset }
180    }
181}
182
183/// A writer for the cropped view layer
184#[derive(Debug, Clone, PartialEq)]
185pub struct CroppedWriter<ChannelsWriter> {
186    channels: ChannelsWriter,
187    offset: Vec2<usize>
188}
189
190impl<'c, Channels> ChannelsWriter for CroppedWriter<Channels> where Channels: ChannelsWriter {
191    fn extract_uncompressed_block(&self, header: &Header, block: BlockIndex) -> Vec<u8> {
192        let block = BlockIndex {
193            pixel_position: block.pixel_position + self.offset,
194            .. block
195        };
196
197        self.channels.extract_uncompressed_block(header, block)
198    }
199}
200
201impl<Samples, Channels> InspectSample for Layer<SpecificChannels<Samples, Channels>> where Samples: GetPixel {
202    type Sample = Samples::Pixel;
203    fn inspect_sample(&self, local_index: Vec2<usize>) -> Samples::Pixel {
204        self.channel_data.pixels.get_pixel(local_index)
205    }
206}
207
208impl InspectSample for Layer<AnyChannels<FlatSamples>> {
209    type Sample = FlatSamplesPixel;
210
211    fn inspect_sample(&self, local_index: Vec2<usize>) -> FlatSamplesPixel {
212        self.sample_vec_at(local_index)
213    }
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`.
223pub 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
234impl ApplyCroppedView for Layer<CroppedChannels<AnyChannels<FlatSamples>>> {
235    type Reallocated = Layer<AnyChannels<FlatSamples>>;
236
237    fn reallocate_cropped(self) -> Self::Reallocated {
238        let cropped_absolute_bounds = self.channel_data.cropped_bounds;
239        let cropped_relative_bounds = cropped_absolute_bounds.with_origin(-self.channel_data.full_bounds.position);
240
241        assert!(self.absolute_bounds().contains(cropped_absolute_bounds), "bounds not valid for layer dimensions");
242        assert!(cropped_relative_bounds.size.area() > 0, "the cropped image would be empty");
243
244        Layer {
245            channel_data: if cropped_relative_bounds.size == self.channel_data.full_bounds.size {
246                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                self.channel_data.full_channels
250            }
251            else {
252                let start_x = cropped_relative_bounds.position.x() as usize; // safe, because just checked above
253                let start_y = cropped_relative_bounds.position.y() as usize; // safe, because just checked above
254                let x_range = start_x .. start_x + cropped_relative_bounds.size.width();
255                let old_width = self.channel_data.full_bounds.size.width();
256                let new_height = cropped_relative_bounds.size.height();
257
258                let channels = self.channel_data.full_channels.list.into_iter().map(|channel: AnyChannel<FlatSamples>| {
259                    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                        let filtered_lines = samples.chunks_exact(old_width).skip(y_start).take(new_height);
261                        let trimmed_lines = filtered_lines.map(|line| &line[x_range.clone()]);
262                        trimmed_lines.flatten().map(|x|*x).collect() // TODO does this use memcpy?
263                    }
264
265                    let samples = match channel.sample_data {
266                        FlatSamples::F16(samples) => FlatSamples::F16(crop_samples(
267                            samples, old_width, new_height, x_range.clone(), start_y
268                        )),
269
270                        FlatSamples::F32(samples) => FlatSamples::F32(crop_samples(
271                            samples, old_width, new_height, x_range.clone(), start_y
272                        )),
273
274                        FlatSamples::U32(samples) => FlatSamples::U32(crop_samples(
275                            samples, old_width, new_height, x_range.clone(), start_y
276                        )),
277                    };
278
279                    AnyChannel { sample_data: samples, ..channel }
280                }).collect();
281
282                AnyChannels { list: channels }
283            },
284
285            attributes: self.attributes,
286            encoding: self.encoding,
287            size: self.size,
288        }
289    }
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.
300pub fn try_find_smaller_bounds(current_bounds: IntegerBounds, pixel_at: impl Fn(Vec2<usize>) -> bool) -> Option<IntegerBounds> {
301    assert_ne!(current_bounds.size.area(), 0, "cannot find smaller bounds of an image with zero width or height");
302    let Vec2(width, height) = current_bounds.size;
303
304    // scans top to bottom (left to right)
305    let first_top_left_pixel = (0 .. height)
306        .flat_map(|y| (0 .. width).map(move |x| Vec2(x,y)))
307        .find(|&position| pixel_at(position))?; // return none if no pixel should be kept
308
309    // scans bottom to top (right to left)
310    let first_bottom_right_pixel = (first_top_left_pixel.y() + 1 .. height) // excluding the top line
311        .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        .rev().find(|&position| pixel_at(position))
313        .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    let top = first_top_left_pixel.y();
318    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    let mut min_left_x = first_top_left_pixel.x().min(first_bottom_right_pixel.x());
325    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    for y in top ..= bottom {
329
330        // escape the loop if there is nothing left to crop
331        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        if max_right_x != width - 1 {
336            max_right_x = (max_right_x + 1 .. width).rev() // excluding current max
337                .find(|&x| pixel_at(Vec2(x, y)))
338                .unwrap_or(max_right_x);
339        }
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        if min_left_x != 0 {
344            min_left_x = (0 .. min_left_x) // excluding current min
345                .find(|&x| pixel_at(Vec2(x, y)))
346                .unwrap_or(min_left_x);
347        }
348    }
349
350    // TODO add 1px margin to avoid interpolation issues?
351    let local_start = Vec2(min_left_x, top);
352    let local_end = Vec2(max_right_x + 1, bottom + 1);
353    Some(IntegerBounds::new(
354        current_bounds.position + local_start.to_i32(),
355        local_end - local_start
356    ))
357}
358
359impl<S> GetBounds for Layer<S> {
360    fn bounds(&self) -> IntegerBounds {
361        self.absolute_bounds()
362    }
363}
364
365impl<Cropped, Original> CropResult<Cropped, Original> {
366
367    /// If the image was fully empty, return `None`, otherwise return `Some(cropped_image)`.
368    pub fn or_none_if_empty(self) -> Option<Cropped> {
369        match self {
370            CropResult::Cropped (cropped) => Some(cropped),
371            CropResult::Empty { .. } => None,
372        }
373    }
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    pub fn or_crop_to_1x1_if_empty(self) -> Cropped where Original: Crop<Cropped=Cropped> + GetBounds {
378        match self {
379            CropResult::Cropped (cropped) => cropped,
380            CropResult::Empty { original } => {
381                let bounds = original.bounds();
382                if bounds.size == Vec2(0,0) { panic!("layer has width and height of zero") }
383                original.crop(IntegerBounds::new(bounds.position, Vec2(1,1)))
384            },
385        }
386    }
387}
388
389
390
391#[cfg(test)]
392mod 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