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: IntegerBounds) => 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(new_bounds: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: Option = {
105 let keep_if: impl Fn(Vec2) -> bool = |position: Vec2| !discard_if(self.inspect_sample(local_index:position));
106 try_find_smaller_bounds(self.bounds(), pixel_at: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| sample == discard_color)
115 }
116
117 fn crop_nowhere(self) -> Self::Cropped {
118 let current_bounds: IntegerBounds = 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: Vec2 = (self.cropped_bounds.position - self.full_bounds.position)
177 .to_usize(error_message:"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 = 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(position: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(position: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: 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) => cropped,
380 CropResult::Empty { original: Original } => {
381 let bounds: IntegerBounds = original.bounds();
382 if bounds.size == Vec2(0,0) { panic!("layer has width and height of zero") }
383 original.crop(bounds:IntegerBounds::new(start:bounds.position, size: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
802