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