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 | 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)]
|
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 | fn crop(self, bounds: IntegerBounds) -> Self::Cropped {
|
96 | CroppedChannels::crop_layer(new_bounds:bounds, self)
|
97 | }
|
98 | }
|
99 |
|
100 | impl<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)]
|
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 | 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 |
|
164 | impl<'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)]
|
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 | 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 |
|
201 | impl<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 |
|
208 | impl 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`.
|
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 | 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.
|
300 | pub 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 |
|
359 | impl<S> GetBounds for Layer<S> {
|
360 | fn bounds(&self) -> IntegerBounds {
|
361 | self.absolute_bounds()
|
362 | }
|
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 | 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)]
|
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 |
|
802 | |