/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 | | |