From 3bdd8a57b36c6c05f5227711a57fa4de15a44b4f Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sat, 6 Jan 2024 02:29:25 +0100 Subject: [PATCH 01/28] Enabled loda-rust-arc feature --- rust_project/loda-rust-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust_project/loda-rust-cli/Cargo.toml b/rust_project/loda-rust-cli/Cargo.toml index b02beb4a..a6c8f9f1 100644 --- a/rust_project/loda-rust-cli/Cargo.toml +++ b/rust_project/loda-rust-cli/Cargo.toml @@ -12,7 +12,7 @@ name = "loda-rust" path = "src/main.rs" [features] -# default = ["loda-rust-arc"] +default = ["loda-rust-arc"] loda-rust-arc = ["dep:petgraph", "dep:image_crate", "dep:linfa", "dep:linfa-logistic", "dep:linfa-preprocessing", "dep:ndarray", "dep:rayon"] [dependencies] From 8b233cadd63a557bff42a271a1c553640a8750e4 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sun, 4 Feb 2024 21:59:04 +0100 Subject: [PATCH 02/28] Undo a 45 degree rotation --- .../loda-rust-cli/src/arc/image_rotate45.rs | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index ab20973f..6e4b5706 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -75,7 +75,8 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re #[cfg(test)] mod tests { use super::*; - use crate::arc::ImageTryCreate; + use crate::arc::{Histogram, ImageHistogram, ImageRemoveRowColumn, ImageTryCreate}; + use bit_set::BitSet; #[test] fn test_10000_rotate_ccw_square() { @@ -216,4 +217,68 @@ mod tests { let expected: Image = Image::try_create(4, 4, expected_pixels).expect("image"); assert_eq!(actual, expected); } + + #[test] + fn test_30000_reversable_rotate_cw() { + // Arrange + let pixels: Vec = vec![ + 0, 0, 1, 0, + 0, 2, 0, 4, + 3, 0, 5, 0, + 0, 6, 0, 0, + ]; + let input: Image = Image::try_create(4, 4, pixels).expect("image"); + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(0).expect("image"); + let expected_pixels0: Vec = vec![ + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 5, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ]; + let expected0: Image = Image::try_create(7, 7, expected_pixels0).expect("image"); + assert_eq!(actual0, expected0); + + // Act - part 2 + let histogram_columns: Vec = actual0.histogram_columns(); + let histogram_rows: Vec = actual0.histogram_rows(); + + let space_color: u8 = 0; + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + for (index, histogram) in histogram_rows.iter().enumerate() { + if histogram.number_of_counters_greater_than_zero() > 1 { + continue; + } + if histogram.most_popular_color_disallow_ambiguous() == Some(space_color) { + delete_row_indexes.insert(index as usize); + } + } + let mut delete_column_indexes = BitSet::new(); + for (index, histogram) in histogram_columns.iter().enumerate() { + if histogram.number_of_counters_greater_than_zero() > 1 { + continue; + } + if histogram.most_popular_color_disallow_ambiguous() == Some(space_color) { + delete_column_indexes.insert(index as usize); + } + } + + // Remove the rows and columns + let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + + // Assert + let expected_pixels1: Vec = vec![ + 1, 4, + 2, 5, + 3, 6, + ]; + let expected1: Image = Image::try_create(2, 3, expected_pixels1).expect("image"); + assert_eq!(actual1, expected1); + } } From 7c90907715503d557aa2c836387add00299ee408 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sun, 4 Feb 2024 23:07:50 +0100 Subject: [PATCH 03/28] Undo a 45 degree rotation, and preserve empty diagonal lines. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 6e4b5706..799f5ac4 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -75,8 +75,9 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re #[cfg(test)] mod tests { use super::*; - use crate::arc::{Histogram, ImageHistogram, ImageRemoveRowColumn, ImageTryCreate}; + use crate::arc::{Histogram, ImageHistogram, ImageRemoveRowColumn, ImageTrim, ImageTryCreate, Rectangle}; use bit_set::BitSet; + use num_integer::Integer; #[test] fn test_10000_rotate_ccw_square() { @@ -219,7 +220,7 @@ mod tests { } #[test] - fn test_30000_reversable_rotate_cw() { + fn test_30000_reversable_rotate_remove_empty_lines() { // Arrange let pixels: Vec = vec![ 0, 0, 1, 0, @@ -281,4 +282,78 @@ mod tests { let expected1: Image = Image::try_create(2, 3, expected_pixels1).expect("image"); assert_eq!(actual1, expected1); } + + #[test] + fn test_30001_reversable_rotate_keep_empty_lines_inside_object() { + // Arrange + let pixels: Vec = vec![ + 0, 0, 1, 0, + 0, 0, 0, 4, + 3, 0, 0, 0, + 0, 6, 0, 0, + ]; + let input: Image = Image::try_create(4, 4, pixels).expect("image"); + + let space_color: u8 = 0; + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + let expected_pixels0: Vec = vec![ + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ]; + let expected0: Image = Image::try_create(7, 7, expected_pixels0).expect("image"); + assert_eq!(actual0, expected0); + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + assert_eq!(rect, Rectangle::new(2, 1, 3, 5)); + + // Keep every second row and column + let mut keep_ys = BitSet::new(); + for y in 0..rect.height() { + if y.is_even() { + keep_ys.insert((y as usize) + rect.y() as usize); + } + } + let mut keep_xs = BitSet::new(); + for x in 0..rect.width() { + if x.is_even() { + keep_xs.insert((x as usize) + rect.x() as usize); + } + } + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if keep_xs.contains(x as usize) { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if keep_ys.contains(y as usize) { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + + // Assert + let expected_pixels1: Vec = vec![ + 1, 4, + 0, 0, + 3, 6, + ]; + let expected1: Image = Image::try_create(2, 3, expected_pixels1).expect("image"); + assert_eq!(actual1, expected1); + } } From b1c7a3d917310176e0ffdf02abeffe6d7177d3c3 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Sun, 4 Feb 2024 23:14:12 +0100 Subject: [PATCH 04/28] Verify that the reversal was correct. --- rust_project/loda-rust-cli/src/arc/image_rotate45.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 799f5ac4..a15b104a 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -355,5 +355,9 @@ mod tests { ]; let expected1: Image = Image::try_create(2, 3, expected_pixels1).expect("image"); assert_eq!(actual1, expected1); + + // Rotating again, should yield the input image + let actual2: Image = actual1.rotate_cw_45(space_color).expect("image"); + assert_eq!(actual2, input); } } From f382a88e7e72e494a2e3f05ffd58cf1be068854d Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 00:14:53 +0100 Subject: [PATCH 05/28] Thanks to parapraxis (Jack Cole) for neat trick to eliminate cos/sin operations when rotating by 45 degrees. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index a15b104a..6e2c69d8 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -1,5 +1,5 @@ //! Rotate an image by 45 degrees. -use super::Image; +use super::{Image, ImageSymmetry}; pub trait ImageRotate45 { /// Rotate an image by 45 degrees. clockwise (CW) @@ -36,39 +36,27 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re return Err(anyhow::anyhow!("Unable to rotate image. The combined width and height is too large: {}", combined_u16)); } - // Rotate by 45 degrees - let rads_amount: f32 = std::f32::consts::FRAC_PI_4; // pi divided by 4 - let rads: f32 = if is_clockwise { -rads_amount } else { rads_amount }; - - let source_center_x: f32 = ((original.width() - 1) as f32) / 2.0; - let source_center_y: f32 = ((original.height() - 1) as f32) / 2.0; - let dest_center_x: f32 = ((combined_u16 - 1) as f32) / 2.0; - let dest_center_y: f32 = ((combined_u16 - 1) as f32) / 2.0; - - // Increase the spacing between the points in the grid from 1 to sqrt(2) - let scale: f32 = std::f32::consts::SQRT_2; - let mut image = Image::color(combined_u16 as u8, combined_u16 as u8, fill_color); + + // Copy the element from the original image to the rotated image for get_y in 0..original.height() { for get_x in 0..original.width() { let pixel_value: u8 = original.get(get_x as i32, get_y as i32).unwrap_or(255); - - let x = (get_x as f32) - source_center_x; - let y = (get_y as f32) - source_center_y; - - let rotated_x: f32 = (rads.cos() * x + rads.sin() * y) * scale; - let rotated_y: f32 = (rads.cos() * y - rads.sin() * x) * scale; - - let set_x: i32 = (dest_center_x + rotated_x).round() as i32; - let set_y: i32 = (dest_center_y + rotated_y).round() as i32; + let set_x: i32 = get_x as i32 + get_y as i32; + let set_y: i32 = get_x as i32 - get_y as i32 + (original.height() - 1) as i32; match image.set(set_x, set_y, pixel_value) { Some(()) => {}, None => { - return Err(anyhow::anyhow!("Integrity error. Unable to set pixel ({}, {}) inside the result image", set_x, y)); + return Err(anyhow::anyhow!("Integrity error. Unable to set pixel ({}, {}) inside the result image", set_x, set_y)); } } } } + if is_clockwise { + image = image.flip_diagonal_a()?; + } else { + image = image.flip_y()?; + } Ok(image) } From 23f3769c9dec45be632cf575a49a804930209a2d Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 00:26:57 +0100 Subject: [PATCH 06/28] rotate_45(), improved error handling. I didn't handle the scenario when the image size was 0 on one side, and non-zero on another side. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 6e2c69d8..94730100 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -26,8 +26,12 @@ impl ImageRotate45 for Image { } fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Result { - if original.width() <= 1 && original.height() <= 1 { - // No point in processing an empty image or a 1x1 image. + if original.is_empty() { + // No point in processing an empty image. + return Ok(original.clone()); + } + if original.width() == 1 && original.height() == 1 { + // No point in processing an 1x1 image. return Ok(original.clone()); } @@ -38,7 +42,7 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re let mut image = Image::color(combined_u16 as u8, combined_u16 as u8, fill_color); - // Copy the element from the original image to the rotated image + // Copy pixels from the original image to the rotated image for get_y in 0..original.height() { for get_x in 0..original.width() { let pixel_value: u8 = original.get(get_x as i32, get_y as i32).unwrap_or(255); @@ -68,7 +72,19 @@ mod tests { use num_integer::Integer; #[test] - fn test_10000_rotate_ccw_square() { + fn test_10000_rotate_tiny_images() { + { + let actual: Image = Image::empty().rotate_cw_45(0).expect("image"); + assert_eq!(actual, Image::empty()); + } + { + let actual: Image = Image::color(1, 1, 9).rotate_cw_45(0).expect("image"); + assert_eq!(actual, Image::color(1, 1, 9)); + } + } + + #[test] + fn test_10001_rotate_ccw_square() { // Arrange let pixels: Vec = vec![ 1, 2, 3, @@ -93,7 +109,7 @@ mod tests { } #[test] - fn test_10001_rotate_ccw_landscape_onerow() { + fn test_10002_rotate_ccw_landscape_onerow() { // Arrange let pixels: Vec = vec![ 1, 2, 3, @@ -114,7 +130,7 @@ mod tests { } #[test] - fn test_10002_rotate_ccw_landscape_tworows() { + fn test_10003_rotate_ccw_landscape_tworows() { // Arrange let pixels: Vec = vec![ 1, 2, 3, @@ -137,7 +153,7 @@ mod tests { } #[test] - fn test_10002_rotate_ccw_portrait_onecolumn() { + fn test_10004_rotate_ccw_portrait_onecolumn() { // Arrange let pixels: Vec = vec![ 1, @@ -160,7 +176,7 @@ mod tests { } #[test] - fn test_10003_rotate_ccw_portrait_twocolumns() { + fn test_10005_rotate_ccw_portrait_twocolumns() { // Arrange let pixels: Vec = vec![ 1, 4, From bc72bad9f57b81037a5162123faeb05e007f59f3 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 01:13:39 +0100 Subject: [PATCH 07/28] Undo a 45 degree rotation --- .../loda-rust-cli/src/arc/image_rotate45.rs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 94730100..b254df95 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -364,4 +364,79 @@ mod tests { let actual2: Image = actual1.rotate_cw_45(space_color).expect("image"); assert_eq!(actual2, input); } + + #[test] + fn test_30002_reversable_rotate_keep_empty_lines_inside_object() { + // Arrange + let pixels: Vec = vec![ + 0, 0, 3, + 0, 2, 0, + 1, 0, 0, + ]; + let input: Image = Image::try_create(3, 3, pixels).expect("image"); + + let space_color: u8 = 0; + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + let expected_pixels0: Vec = vec![ + 0, 0, 3, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + ]; + let expected0: Image = Image::try_create(5, 5, expected_pixels0).expect("image"); + assert_eq!(actual0, expected0); + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + assert_eq!(rect, Rectangle::new(2, 0, 1, 5)); + + // Keep every second row and column + let mut keep_ys = BitSet::new(); + for y in 0..rect.height() { + if y.is_even() { + keep_ys.insert((y as usize) + rect.y() as usize); + } + } + let mut keep_xs = BitSet::new(); + for x in 0..rect.width() { + if x.is_even() { + keep_xs.insert((x as usize) + rect.x() as usize); + } + } + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if keep_xs.contains(x as usize) { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if keep_ys.contains(y as usize) { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + + // Assert + let expected_pixels1: Vec = vec![ + 3, + 2, + 1, + ]; + let expected1: Image = Image::try_create(1, 3, expected_pixels1).expect("image"); + assert_eq!(actual1, expected1); + + // Rotating again, should yield the input image + let actual2: Image = actual1.rotate_cw_45(space_color).expect("image"); + assert_eq!(actual2, input); + } } From 0d3725cef8d17ea2dde2b3e2b71868a6118c5040 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 02:05:50 +0100 Subject: [PATCH 08/28] Undo a 45 degree rotation with a non-square image. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index b254df95..5142e1d0 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -439,4 +439,118 @@ mod tests { let actual2: Image = actual1.rotate_cw_45(space_color).expect("image"); assert_eq!(actual2, input); } + + #[test] + fn test_30003_reversable_rotate_two_images_interleaved() { + // Arrange + let pixels: Vec = vec![ + 0, 0, 3, 8, + 0, 2, 7, 8, + 1, 0, 5, 8, + ]; + let input: Image = Image::try_create(4, 3, pixels).expect("image"); + + let space_color: u8 = 0; + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + let expected_pixels0: Vec = vec![ + 0, 0, 0, 8, 0, 0, + 0, 0, 3, 0, 8, 0, + 0, 0, 0, 7, 0, 8, + 0, 0, 2, 0, 5, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, + ]; + let expected0: Image = Image::try_create(6, 6, expected_pixels0).expect("image"); + assert_eq!(actual0, expected0); + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + assert_eq!(rect, Rectangle::new(2, 0, 4, 6)); + + // Keep every second row and column + let mut keep_ys_even = BitSet::new(); + let mut keep_ys_odd = BitSet::new(); + for y in 0..rect.height() { + if y.is_even() { + keep_ys_even.insert((y as usize) + rect.y() as usize); + } else { + keep_ys_odd.insert((y as usize) + rect.y() as usize); + } + } + let mut keep_xs_even = BitSet::new(); + let mut keep_xs_odd = BitSet::new(); + for x in 0..rect.width() { + if x.is_even() { + keep_xs_even.insert((x as usize) + rect.x() as usize); + } else { + keep_xs_odd.insert((x as usize) + rect.x() as usize); + } + } + + let mut images = Vec::::new(); + for i in 0..=3u8 { + let keep_ys: &BitSet = if (i & 1) == 1 { &keep_ys_even } else { &keep_ys_odd }; + let keep_xs: &BitSet = if (i & 2) == 2 { &keep_xs_even } else { &keep_xs_odd }; + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if keep_xs.contains(x as usize) { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if keep_ys.contains(y as usize) { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let image: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + images.push(image); + } + + // Assert + { + let expected_pixels: Vec = vec![ + 0, 0, + 0, 0, + 0, 0, + ]; + let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); + assert_eq!(images[0], expected); + } + { + let expected_pixels: Vec = vec![ + 8, 0, + 7, 8, + 0, 0, + ]; + let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); + assert_eq!(images[1], expected); + } + { + let expected_pixels: Vec = vec![ + 3, 8, + 2, 5, + 1, 0, + ]; + let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); + assert_eq!(images[2], expected); + } + { + let expected_pixels: Vec = vec![ + 0, 0, + 0, 0, + 0, 0, + ]; + let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); + assert_eq!(images[3], expected); + } + } } From 0eedf2c66b2a6c977b9cb3f4c391507fdce870c5 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 10:00:25 +0100 Subject: [PATCH 09/28] Experiments investigating how width x height impacts the reverse rotate 45 degree images. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 5142e1d0..0a77a2a4 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -67,7 +67,7 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re #[cfg(test)] mod tests { use super::*; - use crate::arc::{Histogram, ImageHistogram, ImageRemoveRowColumn, ImageTrim, ImageTryCreate, Rectangle}; + use crate::arc::{Histogram, HtmlLog, ImageHistogram, ImageRemoveRowColumn, ImageTrim, ImageTryCreate, Rectangle}; use bit_set::BitSet; use num_integer::Integer; @@ -553,4 +553,66 @@ mod tests { assert_eq!(images[3], expected); } } + + // #[test] + fn test_30004_reversable_rotate_two_images_interleaved() { + // Arrange + let input: Image = Image::color(7, 8, 1); + + let space_color: u8 = 0; + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + + // Keep every second row and column + let mut keep_ys_even = BitSet::new(); + let mut keep_ys_odd = BitSet::new(); + for y in 0..rect.height() { + if y.is_even() { + keep_ys_even.insert((y as usize) + rect.y() as usize); + } else { + keep_ys_odd.insert((y as usize) + rect.y() as usize); + } + } + let mut keep_xs_even = BitSet::new(); + let mut keep_xs_odd = BitSet::new(); + for x in 0..rect.width() { + if x.is_even() { + keep_xs_even.insert((x as usize) + rect.x() as usize); + } else { + keep_xs_odd.insert((x as usize) + rect.x() as usize); + } + } + + let mut images = Vec::::new(); + for i in 0..=3u8 { + let keep_ys: &BitSet = if (i & 1) == 1 { &keep_ys_even } else { &keep_ys_odd }; + let keep_xs: &BitSet = if (i & 2) == 2 { &keep_xs_even } else { &keep_xs_odd }; + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if keep_xs.contains(x as usize) { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if keep_ys.contains(y as usize) { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let image: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + HtmlLog::text(&format!("Image {}", i)); + HtmlLog::image(&image); + images.push(image); + } + } } From ffb002bf1dcf1000260e7597a493b72b8af97a9a Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 15:33:30 +0100 Subject: [PATCH 10/28] Checkerboard generator --- .../loda-rust-cli/src/arc/checkerboard.rs | 44 +++++++++++++++++++ rust_project/loda-rust-cli/src/arc/mod.rs | 5 +++ 2 files changed, 49 insertions(+) create mode 100644 rust_project/loda-rust-cli/src/arc/checkerboard.rs diff --git a/rust_project/loda-rust-cli/src/arc/checkerboard.rs b/rust_project/loda-rust-cli/src/arc/checkerboard.rs new file mode 100644 index 00000000..ee6ead12 --- /dev/null +++ b/rust_project/loda-rust-cli/src/arc/checkerboard.rs @@ -0,0 +1,44 @@ +use super::{Image, ImageTryCreate}; +use num_integer::Integer; + +#[allow(dead_code)] +pub struct Checkerboard; + +impl Checkerboard { + #[allow(dead_code)] + pub fn checkerboard(width: u8, height: u8, color0: u8, color1: u8) -> Image { + if width == 0 || height == 0 { + return Image::empty(); + } + let mut pixels = Vec::::new(); + for y in 0..(height as u16) { + for x in 0..(width as u16) { + let color = if (x + y).is_even() { color0 } else { color1 }; + pixels.push(color); + } + } + assert_eq!(pixels.len(), (width as usize) * (height as usize)); + Image::try_create(width, height, pixels).expect("image") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_10000_checkerboard() { + // Act + let actual: Image = Checkerboard::checkerboard(5, 4, 0, 1); + + // Assert + let expected_pixels: Vec = vec![ + 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, + ]; + let expected: Image = Image::try_create(5, 4, expected_pixels).expect("image"); + assert_eq!(actual, expected); + } +} diff --git a/rust_project/loda-rust-cli/src/arc/mod.rs b/rust_project/loda-rust-cli/src/arc/mod.rs index 53316367..0a5ab738 100644 --- a/rust_project/loda-rust-cli/src/arc/mod.rs +++ b/rust_project/loda-rust-cli/src/arc/mod.rs @@ -17,6 +17,7 @@ mod arcathon_solution_json; mod auto_repair_symmetry; mod cellular_automaton; mod center_of_mass; +mod checkerboard; mod color; mod color_map; mod compare_input_output; @@ -186,6 +187,10 @@ pub use auto_repair_symmetry::AutoRepairSymmetry; pub use cellular_automaton::{CellularAutomaton, CARule, rule}; pub use center_of_mass::CenterOfMass; + +#[allow(unused_imports)] +pub use checkerboard::Checkerboard; + pub use color::Color; pub use color_map::ColorMap; pub use compare_input_output::CompareInputOutput; From 243b8a6acdc8f8781a718b2162558c84c5fbc8d7 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 16:12:25 +0100 Subject: [PATCH 11/28] Undo a 45 degree rotation using two checkerboard masks, seems to be the simplest way. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 138 +++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 0a77a2a4..46cafec4 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -67,8 +67,9 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re #[cfg(test)] mod tests { use super::*; - use crate::arc::{Histogram, HtmlLog, ImageHistogram, ImageRemoveRowColumn, ImageTrim, ImageTryCreate, Rectangle}; + use crate::arc::{checkerboard, Histogram, HtmlLog, ImageHistogram, ImageMask, ImageRemoveRowColumn, ImageReplaceColor, ImageTrim, ImageTryCreate, Rectangle}; use bit_set::BitSet; + use checkerboard::Checkerboard; use num_integer::Integer; #[test] @@ -554,6 +555,7 @@ mod tests { } } + #[allow(dead_code)] // #[test] fn test_30004_reversable_rotate_two_images_interleaved() { // Arrange @@ -615,4 +617,138 @@ mod tests { images.push(image); } } + + #[allow(dead_code)] + // #[test] + fn test_30005_reversable_rotate_two_images_interleaved_using_checkerboard() { + // Arrange + let space_color: u8 = 255; + let staircase_color: u8 = 2; + + // let input_raw: Image = Checkerboard::checkerboard(7, 8, 1, 2); + let input_raw: Image = Image::color(7, 8, 1); + // HtmlLog::image(&input_raw); + + let extract_second = false; + + let color0: u8 = if extract_second { 0 } else { 1 }; + let color1: u8 = if extract_second { 1 } else { 0 }; + let checkerboard: Image = Checkerboard::checkerboard(input_raw.width(), input_raw.height(), color0, color1); + let input: Image = checkerboard.select_from_image_and_color(&input_raw, space_color).expect("image"); + HtmlLog::image(&input); + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + HtmlLog::image(&actual0); + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + + // Keep every second row and column + let mut keep_ys = BitSet::new(); + for y in 0..rect.height() { + if y.is_even() { + keep_ys.insert((y as usize) + rect.y() as usize); + } + } + let mut keep_xs = BitSet::new(); + for x in 0..rect.width() { + if x.is_even() { + keep_xs.insert((x as usize) + rect.x() as usize); + } + } + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if keep_xs.contains(x as usize) { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if keep_ys.contains(y as usize) { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + HtmlLog::image(&actual1); + + // Replace the spacer color with the staircase color + let actual2: Image = actual1.replace_color(space_color, staircase_color).expect("image"); + HtmlLog::image(&actual2); + } + + #[allow(dead_code)] + // #[test] + fn test_30006_reversable_rotate_two_images_interleaved_using_checkerboard() { + // Arrange + let space_color: u8 = 0; + + // let input: Image = Checkerboard::checkerboard(7, 8, 1, 2); + let input: Image = Image::color(7, 8, 1); + // HtmlLog::image(&input); + + + // Act - part 1 + let input_rotated: Image = input.rotate_ccw_45(space_color).expect("image"); + let checkerboard: Image = Checkerboard::checkerboard(input_rotated.width(), input_rotated.height(), 0, 1); + let actual0: Image = checkerboard.select_from_image_and_color(&input_rotated, space_color).expect("image"); + HtmlLog::image(&actual0); + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + + // Keep every second row and column + let mut keep_ys_even = BitSet::new(); + let mut keep_ys_odd = BitSet::new(); + for y in 0..rect.height() { + if y.is_even() { + keep_ys_even.insert((y as usize) + rect.y() as usize); + } else { + keep_ys_odd.insert((y as usize) + rect.y() as usize); + } + } + let mut keep_xs_even = BitSet::new(); + let mut keep_xs_odd = BitSet::new(); + for x in 0..rect.width() { + if x.is_even() { + keep_xs_even.insert((x as usize) + rect.x() as usize); + } else { + keep_xs_odd.insert((x as usize) + rect.x() as usize); + } + } + + let mut images = Vec::::new(); + for i in 0..=3u8 { + let keep_ys: &BitSet = if (i & 1) == 1 { &keep_ys_even } else { &keep_ys_odd }; + let keep_xs: &BitSet = if (i & 2) == 2 { &keep_xs_even } else { &keep_xs_odd }; + + // Identify the rows and columns that can be removed + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if keep_xs.contains(x as usize) { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if keep_ys.contains(y as usize) { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let image: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + HtmlLog::text(&format!("Image {}", i)); + HtmlLog::image(&image); + images.push(image); + } + } } From 865105e8bb16017a41f2cb8e6fbe2a1de366e336 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 16:28:24 +0100 Subject: [PATCH 12/28] removed trimming code. I don't want the reversed 45 degree rotated image to have been trimmed. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 46cafec4..495f7a16 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -625,9 +625,9 @@ mod tests { let space_color: u8 = 255; let staircase_color: u8 = 2; - // let input_raw: Image = Checkerboard::checkerboard(7, 8, 1, 2); - let input_raw: Image = Image::color(7, 8, 1); - // HtmlLog::image(&input_raw); + let input_raw: Image = Checkerboard::checkerboard(7, 8, 1, 3); + // let input_raw: Image = Image::color(7, 8, 1); + HtmlLog::image(&input_raw); let extract_second = false; @@ -644,31 +644,19 @@ mod tests { // Act - part 2 let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - // Keep every second row and column - let mut keep_ys = BitSet::new(); - for y in 0..rect.height() { - if y.is_even() { - keep_ys.insert((y as usize) + rect.y() as usize); - } - } - let mut keep_xs = BitSet::new(); - for x in 0..rect.width() { - if x.is_even() { - keep_xs.insert((x as usize) + rect.x() as usize); - } - } - - // Identify the rows and columns that can be removed + // Keep every second row and column + let keep_x: u8 = rect.x() & 1; + let keep_y: u8 = rect.y() & 1; let mut delete_row_indexes = BitSet::new(); let mut delete_column_indexes = BitSet::new(); for x in 0..actual0.width() { - if keep_xs.contains(x as usize) { + if x & 1 == keep_x { continue; } delete_column_indexes.insert(x as usize); } for y in 0..actual0.height() { - if keep_ys.contains(y as usize) { + if y & 1 == keep_y { continue; } delete_row_indexes.insert(y as usize); From 759a0bbc9b7fb7d3cc7bedf2baacd04e3433b8e2 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 16:39:35 +0100 Subject: [PATCH 13/28] Removed dead code --- .../loda-rust-cli/src/arc/image_rotate45.rs | 133 ------------------ 1 file changed, 133 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 495f7a16..6a1e2989 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -224,70 +224,6 @@ mod tests { assert_eq!(actual, expected); } - #[test] - fn test_30000_reversable_rotate_remove_empty_lines() { - // Arrange - let pixels: Vec = vec![ - 0, 0, 1, 0, - 0, 2, 0, 4, - 3, 0, 5, 0, - 0, 6, 0, 0, - ]; - let input: Image = Image::try_create(4, 4, pixels).expect("image"); - - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(0).expect("image"); - let expected_pixels0: Vec = vec![ - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 4, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 2, 0, 5, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 6, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ]; - let expected0: Image = Image::try_create(7, 7, expected_pixels0).expect("image"); - assert_eq!(actual0, expected0); - - // Act - part 2 - let histogram_columns: Vec = actual0.histogram_columns(); - let histogram_rows: Vec = actual0.histogram_rows(); - - let space_color: u8 = 0; - - // Identify the rows and columns that can be removed - let mut delete_row_indexes = BitSet::new(); - for (index, histogram) in histogram_rows.iter().enumerate() { - if histogram.number_of_counters_greater_than_zero() > 1 { - continue; - } - if histogram.most_popular_color_disallow_ambiguous() == Some(space_color) { - delete_row_indexes.insert(index as usize); - } - } - let mut delete_column_indexes = BitSet::new(); - for (index, histogram) in histogram_columns.iter().enumerate() { - if histogram.number_of_counters_greater_than_zero() > 1 { - continue; - } - if histogram.most_popular_color_disallow_ambiguous() == Some(space_color) { - delete_column_indexes.insert(index as usize); - } - } - - // Remove the rows and columns - let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); - - // Assert - let expected_pixels1: Vec = vec![ - 1, 4, - 2, 5, - 3, 6, - ]; - let expected1: Image = Image::try_create(2, 3, expected_pixels1).expect("image"); - assert_eq!(actual1, expected1); - } - #[test] fn test_30001_reversable_rotate_keep_empty_lines_inside_object() { // Arrange @@ -670,73 +606,4 @@ mod tests { let actual2: Image = actual1.replace_color(space_color, staircase_color).expect("image"); HtmlLog::image(&actual2); } - - #[allow(dead_code)] - // #[test] - fn test_30006_reversable_rotate_two_images_interleaved_using_checkerboard() { - // Arrange - let space_color: u8 = 0; - - // let input: Image = Checkerboard::checkerboard(7, 8, 1, 2); - let input: Image = Image::color(7, 8, 1); - // HtmlLog::image(&input); - - - // Act - part 1 - let input_rotated: Image = input.rotate_ccw_45(space_color).expect("image"); - let checkerboard: Image = Checkerboard::checkerboard(input_rotated.width(), input_rotated.height(), 0, 1); - let actual0: Image = checkerboard.select_from_image_and_color(&input_rotated, space_color).expect("image"); - HtmlLog::image(&actual0); - - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - - // Keep every second row and column - let mut keep_ys_even = BitSet::new(); - let mut keep_ys_odd = BitSet::new(); - for y in 0..rect.height() { - if y.is_even() { - keep_ys_even.insert((y as usize) + rect.y() as usize); - } else { - keep_ys_odd.insert((y as usize) + rect.y() as usize); - } - } - let mut keep_xs_even = BitSet::new(); - let mut keep_xs_odd = BitSet::new(); - for x in 0..rect.width() { - if x.is_even() { - keep_xs_even.insert((x as usize) + rect.x() as usize); - } else { - keep_xs_odd.insert((x as usize) + rect.x() as usize); - } - } - - let mut images = Vec::::new(); - for i in 0..=3u8 { - let keep_ys: &BitSet = if (i & 1) == 1 { &keep_ys_even } else { &keep_ys_odd }; - let keep_xs: &BitSet = if (i & 2) == 2 { &keep_xs_even } else { &keep_xs_odd }; - - // Identify the rows and columns that can be removed - let mut delete_row_indexes = BitSet::new(); - let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { - if keep_xs.contains(x as usize) { - continue; - } - delete_column_indexes.insert(x as usize); - } - for y in 0..actual0.height() { - if keep_ys.contains(y as usize) { - continue; - } - delete_row_indexes.insert(y as usize); - } - - // Remove the rows and columns - let image: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); - HtmlLog::text(&format!("Image {}", i)); - HtmlLog::image(&image); - images.push(image); - } - } } From 35eb5ef218ac274674221a7318bd75922edb82ac Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 18:14:17 +0100 Subject: [PATCH 14/28] Extracted dual lattice reverse rotate 45 code to a struct. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 6a1e2989..729a815b 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -1,5 +1,6 @@ //! Rotate an image by 45 degrees. -use super::{Image, ImageSymmetry}; +use super::{Checkerboard, HtmlLog, Image, ImageMask, ImageRemoveRowColumn, ImageReplaceColor, ImageSymmetry, ImageTrim, Rectangle}; +use bit_set::BitSet; pub trait ImageRotate45 { /// Rotate an image by 45 degrees. clockwise (CW) @@ -64,6 +65,79 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re Ok(image) } +#[allow(dead_code)] +struct ReverseRotate45 { + rotated_a: Image, + rotated_b: Image, +} + +impl ReverseRotate45 { + #[allow(dead_code)] + fn process(image: &Image, verbose: bool) -> anyhow::Result { + let rotated_a: Image = Self::extract_lattice(verbose, image, false)?; + let rotated_b: Image = Self::extract_lattice(verbose, image, true)?; + if verbose { + HtmlLog::compare_images(vec![rotated_a.clone(), rotated_b.clone()]); + } + let instance = Self { + rotated_a, + rotated_b, + }; + Ok(instance) + } + + #[allow(dead_code)] + fn extract_lattice(verbose: bool, input_raw: &Image, extract_second: bool) -> anyhow::Result { + let space_color: u8 = 255; + let staircase_color: u8 = 2; + + let color0: u8 = if extract_second { 0 } else { 1 }; + let color1: u8 = if extract_second { 1 } else { 0 }; + let checkerboard: Image = Checkerboard::checkerboard(input_raw.width(), input_raw.height(), color0, color1); + let input: Image = checkerboard.select_from_image_and_color(&input_raw, space_color).expect("image"); + // if verbose { + // HtmlLog::image(&input); + // } + + // Act - part 1 + let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + // if verbose { + // HtmlLog::image(&actual0); + // } + + // Act - part 2 + let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + + // Keep every second row and column + let keep_x: u8 = rect.x() & 1; + let keep_y: u8 = rect.y() & 1; + let mut delete_row_indexes = BitSet::new(); + let mut delete_column_indexes = BitSet::new(); + for x in 0..actual0.width() { + if x & 1 == keep_x { + continue; + } + delete_column_indexes.insert(x as usize); + } + for y in 0..actual0.height() { + if y & 1 == keep_y { + continue; + } + delete_row_indexes.insert(y as usize); + } + + // Remove the rows and columns + let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + // if verbose { + // HtmlLog::image(&actual1); + // } + + // Replace the spacer color with the staircase color + let actual2: Image = actual1.replace_color(space_color, staircase_color).expect("image"); + Ok(actual2) + } +} + #[cfg(test)] mod tests { use super::*; @@ -606,4 +680,14 @@ mod tests { let actual2: Image = actual1.replace_color(space_color, staircase_color).expect("image"); HtmlLog::image(&actual2); } + + #[allow(dead_code)] + // #[test] + fn test_30006_reversable_rotate_two_images_interleaved_using_checkerboard() { + let input_raw: Image = Checkerboard::checkerboard(7, 1, 1, 3); + HtmlLog::image(&input_raw); + + let verbose = true; + let actual: ReverseRotate45 = ReverseRotate45::process(&input_raw, verbose).expect("reverse rotate"); + } } From 74a384d439cb43362be7dc6c70068988f4177dd3 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 22:29:51 +0100 Subject: [PATCH 15/28] Made a good test for ReverseRotate45 --- .../loda-rust-cli/src/arc/image_rotate45.rs | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 729a815b..43a30589 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -73,9 +73,12 @@ struct ReverseRotate45 { impl ReverseRotate45 { #[allow(dead_code)] - fn process(image: &Image, verbose: bool) -> anyhow::Result { - let rotated_a: Image = Self::extract_lattice(verbose, image, false)?; - let rotated_b: Image = Self::extract_lattice(verbose, image, true)?; + fn process(image: &Image, verbose: bool, triangle_color: u8) -> anyhow::Result { + if verbose { + HtmlLog::image(&image); + } + let rotated_a: Image = Self::extract_lattice(image, verbose, triangle_color, false)?; + let rotated_b: Image = Self::extract_lattice(image, verbose, triangle_color, true)?; if verbose { HtmlLog::compare_images(vec![rotated_a.clone(), rotated_b.clone()]); } @@ -87,9 +90,8 @@ impl ReverseRotate45 { } #[allow(dead_code)] - fn extract_lattice(verbose: bool, input_raw: &Image, extract_second: bool) -> anyhow::Result { + fn extract_lattice(input_raw: &Image, verbose: bool, triangle_color: u8, extract_second: bool) -> anyhow::Result { let space_color: u8 = 255; - let staircase_color: u8 = 2; let color0: u8 = if extract_second { 0 } else { 1 }; let color1: u8 = if extract_second { 1 } else { 0 }; @@ -132,8 +134,8 @@ impl ReverseRotate45 { // HtmlLog::image(&actual1); // } - // Replace the spacer color with the staircase color - let actual2: Image = actual1.replace_color(space_color, staircase_color).expect("image"); + // Assign color to the corner triangles + let actual2: Image = actual1.replace_color(space_color, triangle_color).expect("image"); Ok(actual2) } } @@ -681,13 +683,51 @@ mod tests { HtmlLog::image(&actual2); } + #[test] + fn test_30006_reversable_ccw() { + let pixels: Vec = vec![ + 0, 0, 3, 0, 0, + 0, 2, 0, 6, 0, + 1, 0, 5, 0, 9, + 0, 4, 0, 8, 0, + 0, 0, 7, 0, 0, + ]; + let input: Image = Image::try_create(5, 5, pixels).expect("image"); + + // let input: Image = Checkerboard::checkerboard(6, 3, 1, 3); + + let verbose = false; + let triangle_color: u8 = 11; + let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color).expect("reverse rotate"); + + // Assert + let expected_pixels0: Vec = vec![ + 11, 0, 0, 11, + 0, 0, 0, 0, + 0, 0, 0, 0, + 11, 0, 0, 11, + ]; + let expected0: Image = Image::try_create(4, 4, expected_pixels0).expect("image"); + assert_eq!(actual.rotated_a, expected0); + + let expected_pixels1: Vec = vec![ + 11, 11, 0, 11, 11, + 11, 3, 6, 9, 11, + 0, 2, 5, 8, 0, + 11, 1, 4, 7, 11, + 11, 11, 0, 11, 11, + ]; + let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); + assert_eq!(actual.rotated_b, expected1); + } + #[allow(dead_code)] // #[test] - fn test_30006_reversable_rotate_two_images_interleaved_using_checkerboard() { - let input_raw: Image = Checkerboard::checkerboard(7, 1, 1, 3); - HtmlLog::image(&input_raw); + fn test_30007_reversable_ccw() { + let input: Image = Checkerboard::checkerboard(6, 3, 1, 3); let verbose = true; - let actual: ReverseRotate45 = ReverseRotate45::process(&input_raw, verbose).expect("reverse rotate"); + let triangle_color: u8 = 11; + let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color).expect("reverse rotate"); } } From e2cfd589ee6a872616071db64df5254422524e5b Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 22:57:36 +0100 Subject: [PATCH 16/28] Reverse rotate 45 can now do both clockwise and counter clockwise. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 43a30589..66c06996 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -73,12 +73,12 @@ struct ReverseRotate45 { impl ReverseRotate45 { #[allow(dead_code)] - fn process(image: &Image, verbose: bool, triangle_color: u8) -> anyhow::Result { + fn process(image: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool) -> anyhow::Result { if verbose { HtmlLog::image(&image); } - let rotated_a: Image = Self::extract_lattice(image, verbose, triangle_color, false)?; - let rotated_b: Image = Self::extract_lattice(image, verbose, triangle_color, true)?; + let rotated_a: Image = Self::extract_lattice(image, verbose, triangle_color, is_clockwise, false)?; + let rotated_b: Image = Self::extract_lattice(image, verbose, triangle_color, is_clockwise, true)?; if verbose { HtmlLog::compare_images(vec![rotated_a.clone(), rotated_b.clone()]); } @@ -90,46 +90,48 @@ impl ReverseRotate45 { } #[allow(dead_code)] - fn extract_lattice(input_raw: &Image, verbose: bool, triangle_color: u8, extract_second: bool) -> anyhow::Result { + fn extract_lattice(input: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool, extract_second: bool) -> anyhow::Result { let space_color: u8 = 255; let color0: u8 = if extract_second { 0 } else { 1 }; let color1: u8 = if extract_second { 1 } else { 0 }; - let checkerboard: Image = Checkerboard::checkerboard(input_raw.width(), input_raw.height(), color0, color1); - let input: Image = checkerboard.select_from_image_and_color(&input_raw, space_color).expect("image"); + let mask: Image = Checkerboard::checkerboard(input.width(), input.height(), color0, color1); + let masked_input: Image = mask.select_from_image_and_color(&input, space_color).expect("image"); // if verbose { - // HtmlLog::image(&input); + // HtmlLog::image(&masked_input); // } - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); + // Rotate CW or CCW + let rotated_image: Image = rotate_45(&masked_input, space_color, is_clockwise)?; // if verbose { - // HtmlLog::image(&actual0); + // HtmlLog::image(&rotated_image); // } - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + // Bounding box + let rect: Rectangle = rotated_image.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - // Keep every second row and column + // Determine where in the lattice the image is located let keep_x: u8 = rect.x() & 1; let keep_y: u8 = rect.y() & 1; + + // Keep every second row and column let mut delete_row_indexes = BitSet::new(); let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { + for x in 0..rotated_image.width() { if x & 1 == keep_x { continue; } delete_column_indexes.insert(x as usize); } - for y in 0..actual0.height() { + for y in 0..rotated_image.height() { if y & 1 == keep_y { continue; } delete_row_indexes.insert(y as usize); } - // Remove the rows and columns - let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + // Remove rows and columns + let actual1: Image = rotated_image.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); // if verbose { // HtmlLog::image(&actual1); // } @@ -685,6 +687,7 @@ mod tests { #[test] fn test_30006_reversable_ccw() { + // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, 0, 2, 0, 6, 0, @@ -694,11 +697,12 @@ mod tests { ]; let input: Image = Image::try_create(5, 5, pixels).expect("image"); - // let input: Image = Checkerboard::checkerboard(6, 3, 1, 3); - let verbose = false; + let is_clockwise = false; let triangle_color: u8 = 11; - let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color).expect("reverse rotate"); + + // Act + let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); // Assert let expected_pixels0: Vec = vec![ @@ -728,6 +732,7 @@ mod tests { let verbose = true; let triangle_color: u8 = 11; - let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color).expect("reverse rotate"); + let is_clockwise = false; + let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); } } From aab19310ba17e75866e6a262eb39a46f2b2ac739 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 23:02:20 +0100 Subject: [PATCH 17/28] picked a better name --- .../loda-rust-cli/src/arc/image_rotate45.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 66c06996..382c5f92 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -66,12 +66,12 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re } #[allow(dead_code)] -struct ReverseRotate45 { +struct Rotate45Extract { rotated_a: Image, rotated_b: Image, } -impl ReverseRotate45 { +impl Rotate45Extract { #[allow(dead_code)] fn process(image: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool) -> anyhow::Result { if verbose { @@ -686,7 +686,7 @@ mod tests { } #[test] - fn test_30006_reversable_ccw() { + fn test_30006_rotate45extract_ccw() { // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, @@ -702,7 +702,7 @@ mod tests { let triangle_color: u8 = 11; // Act - let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); // Assert let expected_pixels0: Vec = vec![ @@ -712,7 +712,6 @@ mod tests { 11, 0, 0, 11, ]; let expected0: Image = Image::try_create(4, 4, expected_pixels0).expect("image"); - assert_eq!(actual.rotated_a, expected0); let expected_pixels1: Vec = vec![ 11, 11, 0, 11, 11, @@ -722,7 +721,7 @@ mod tests { 11, 11, 0, 11, 11, ]; let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); - assert_eq!(actual.rotated_b, expected1); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } #[allow(dead_code)] @@ -733,6 +732,6 @@ mod tests { let verbose = true; let triangle_color: u8 = 11; let is_clockwise = false; - let actual: ReverseRotate45 = ReverseRotate45::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); } } From cb8cc785a074cef22d6f0d4248602a35b992dce6 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 23:08:02 +0100 Subject: [PATCH 18/28] Removed dead code --- .../loda-rust-cli/src/arc/image_rotate45.rs | 407 ++---------------- 1 file changed, 30 insertions(+), 377 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 382c5f92..b861f90f 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -145,10 +145,7 @@ impl Rotate45Extract { #[cfg(test)] mod tests { use super::*; - use crate::arc::{checkerboard, Histogram, HtmlLog, ImageHistogram, ImageMask, ImageRemoveRowColumn, ImageReplaceColor, ImageTrim, ImageTryCreate, Rectangle}; - use bit_set::BitSet; - use checkerboard::Checkerboard; - use num_integer::Integer; + use crate::arc::{Checkerboard, ImageTryCreate}; #[test] fn test_10000_rotate_tiny_images() { @@ -303,390 +300,46 @@ mod tests { } #[test] - fn test_30001_reversable_rotate_keep_empty_lines_inside_object() { + fn test_30000_rotate45extract_ccw() { // Arrange let pixels: Vec = vec![ - 0, 0, 1, 0, - 0, 0, 0, 4, - 3, 0, 0, 0, - 0, 6, 0, 0, - ]; - let input: Image = Image::try_create(4, 4, pixels).expect("image"); - - let space_color: u8 = 0; - - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); - let expected_pixels0: Vec = vec![ - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 4, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - 0, 0, 3, 0, 6, 0, 0, - 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, + 0, 2, 0, 6, 0, + 1, 0, 5, 0, 9, + 0, 4, 0, 8, 0, + 0, 0, 7, 0, 0, ]; - let expected0: Image = Image::try_create(7, 7, expected_pixels0).expect("image"); - assert_eq!(actual0, expected0); - - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - assert_eq!(rect, Rectangle::new(2, 1, 3, 5)); - - // Keep every second row and column - let mut keep_ys = BitSet::new(); - for y in 0..rect.height() { - if y.is_even() { - keep_ys.insert((y as usize) + rect.y() as usize); - } - } - let mut keep_xs = BitSet::new(); - for x in 0..rect.width() { - if x.is_even() { - keep_xs.insert((x as usize) + rect.x() as usize); - } - } + let input: Image = Image::try_create(5, 5, pixels).expect("image"); - // Identify the rows and columns that can be removed - let mut delete_row_indexes = BitSet::new(); - let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { - if keep_xs.contains(x as usize) { - continue; - } - delete_column_indexes.insert(x as usize); - } - for y in 0..actual0.height() { - if keep_ys.contains(y as usize) { - continue; - } - delete_row_indexes.insert(y as usize); - } + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; - // Remove the rows and columns - let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); // Assert - let expected_pixels1: Vec = vec![ - 1, 4, - 0, 0, - 3, 6, - ]; - let expected1: Image = Image::try_create(2, 3, expected_pixels1).expect("image"); - assert_eq!(actual1, expected1); - - // Rotating again, should yield the input image - let actual2: Image = actual1.rotate_cw_45(space_color).expect("image"); - assert_eq!(actual2, input); - } - - #[test] - fn test_30002_reversable_rotate_keep_empty_lines_inside_object() { - // Arrange - let pixels: Vec = vec![ - 0, 0, 3, - 0, 2, 0, - 1, 0, 0, - ]; - let input: Image = Image::try_create(3, 3, pixels).expect("image"); - - let space_color: u8 = 0; - - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); let expected_pixels0: Vec = vec![ - 0, 0, 3, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 2, 0, 0, - 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, + 11, 0, 0, 11, + 0, 0, 0, 0, + 0, 0, 0, 0, + 11, 0, 0, 11, ]; - let expected0: Image = Image::try_create(5, 5, expected_pixels0).expect("image"); - assert_eq!(actual0, expected0); - - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - assert_eq!(rect, Rectangle::new(2, 0, 1, 5)); - - // Keep every second row and column - let mut keep_ys = BitSet::new(); - for y in 0..rect.height() { - if y.is_even() { - keep_ys.insert((y as usize) + rect.y() as usize); - } - } - let mut keep_xs = BitSet::new(); - for x in 0..rect.width() { - if x.is_even() { - keep_xs.insert((x as usize) + rect.x() as usize); - } - } - - // Identify the rows and columns that can be removed - let mut delete_row_indexes = BitSet::new(); - let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { - if keep_xs.contains(x as usize) { - continue; - } - delete_column_indexes.insert(x as usize); - } - for y in 0..actual0.height() { - if keep_ys.contains(y as usize) { - continue; - } - delete_row_indexes.insert(y as usize); - } - - // Remove the rows and columns - let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + let expected0: Image = Image::try_create(4, 4, expected_pixels0).expect("image"); - // Assert let expected_pixels1: Vec = vec![ - 3, - 2, - 1, - ]; - let expected1: Image = Image::try_create(1, 3, expected_pixels1).expect("image"); - assert_eq!(actual1, expected1); - - // Rotating again, should yield the input image - let actual2: Image = actual1.rotate_cw_45(space_color).expect("image"); - assert_eq!(actual2, input); - } - - #[test] - fn test_30003_reversable_rotate_two_images_interleaved() { - // Arrange - let pixels: Vec = vec![ - 0, 0, 3, 8, - 0, 2, 7, 8, - 1, 0, 5, 8, - ]; - let input: Image = Image::try_create(4, 3, pixels).expect("image"); - - let space_color: u8 = 0; - - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); - let expected_pixels0: Vec = vec![ - 0, 0, 0, 8, 0, 0, - 0, 0, 3, 0, 8, 0, - 0, 0, 0, 7, 0, 8, - 0, 0, 2, 0, 5, 0, - 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, + 11, 11, 0, 11, 11, + 11, 3, 6, 9, 11, + 0, 2, 5, 8, 0, + 11, 1, 4, 7, 11, + 11, 11, 0, 11, 11, ]; - let expected0: Image = Image::try_create(6, 6, expected_pixels0).expect("image"); - assert_eq!(actual0, expected0); - - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - assert_eq!(rect, Rectangle::new(2, 0, 4, 6)); - - // Keep every second row and column - let mut keep_ys_even = BitSet::new(); - let mut keep_ys_odd = BitSet::new(); - for y in 0..rect.height() { - if y.is_even() { - keep_ys_even.insert((y as usize) + rect.y() as usize); - } else { - keep_ys_odd.insert((y as usize) + rect.y() as usize); - } - } - let mut keep_xs_even = BitSet::new(); - let mut keep_xs_odd = BitSet::new(); - for x in 0..rect.width() { - if x.is_even() { - keep_xs_even.insert((x as usize) + rect.x() as usize); - } else { - keep_xs_odd.insert((x as usize) + rect.x() as usize); - } - } - - let mut images = Vec::::new(); - for i in 0..=3u8 { - let keep_ys: &BitSet = if (i & 1) == 1 { &keep_ys_even } else { &keep_ys_odd }; - let keep_xs: &BitSet = if (i & 2) == 2 { &keep_xs_even } else { &keep_xs_odd }; - - // Identify the rows and columns that can be removed - let mut delete_row_indexes = BitSet::new(); - let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { - if keep_xs.contains(x as usize) { - continue; - } - delete_column_indexes.insert(x as usize); - } - for y in 0..actual0.height() { - if keep_ys.contains(y as usize) { - continue; - } - delete_row_indexes.insert(y as usize); - } - - // Remove the rows and columns - let image: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); - images.push(image); - } - - // Assert - { - let expected_pixels: Vec = vec![ - 0, 0, - 0, 0, - 0, 0, - ]; - let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); - assert_eq!(images[0], expected); - } - { - let expected_pixels: Vec = vec![ - 8, 0, - 7, 8, - 0, 0, - ]; - let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); - assert_eq!(images[1], expected); - } - { - let expected_pixels: Vec = vec![ - 3, 8, - 2, 5, - 1, 0, - ]; - let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); - assert_eq!(images[2], expected); - } - { - let expected_pixels: Vec = vec![ - 0, 0, - 0, 0, - 0, 0, - ]; - let expected: Image = Image::try_create(2, 3, expected_pixels).expect("image"); - assert_eq!(images[3], expected); - } - } - - #[allow(dead_code)] - // #[test] - fn test_30004_reversable_rotate_two_images_interleaved() { - // Arrange - let input: Image = Image::color(7, 8, 1); - - let space_color: u8 = 0; - - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); - - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - - // Keep every second row and column - let mut keep_ys_even = BitSet::new(); - let mut keep_ys_odd = BitSet::new(); - for y in 0..rect.height() { - if y.is_even() { - keep_ys_even.insert((y as usize) + rect.y() as usize); - } else { - keep_ys_odd.insert((y as usize) + rect.y() as usize); - } - } - let mut keep_xs_even = BitSet::new(); - let mut keep_xs_odd = BitSet::new(); - for x in 0..rect.width() { - if x.is_even() { - keep_xs_even.insert((x as usize) + rect.x() as usize); - } else { - keep_xs_odd.insert((x as usize) + rect.x() as usize); - } - } - - let mut images = Vec::::new(); - for i in 0..=3u8 { - let keep_ys: &BitSet = if (i & 1) == 1 { &keep_ys_even } else { &keep_ys_odd }; - let keep_xs: &BitSet = if (i & 2) == 2 { &keep_xs_even } else { &keep_xs_odd }; - - // Identify the rows and columns that can be removed - let mut delete_row_indexes = BitSet::new(); - let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { - if keep_xs.contains(x as usize) { - continue; - } - delete_column_indexes.insert(x as usize); - } - for y in 0..actual0.height() { - if keep_ys.contains(y as usize) { - continue; - } - delete_row_indexes.insert(y as usize); - } - - // Remove the rows and columns - let image: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); - HtmlLog::text(&format!("Image {}", i)); - HtmlLog::image(&image); - images.push(image); - } - } - - #[allow(dead_code)] - // #[test] - fn test_30005_reversable_rotate_two_images_interleaved_using_checkerboard() { - // Arrange - let space_color: u8 = 255; - let staircase_color: u8 = 2; - - let input_raw: Image = Checkerboard::checkerboard(7, 8, 1, 3); - // let input_raw: Image = Image::color(7, 8, 1); - HtmlLog::image(&input_raw); - - let extract_second = false; - - let color0: u8 = if extract_second { 0 } else { 1 }; - let color1: u8 = if extract_second { 1 } else { 0 }; - let checkerboard: Image = Checkerboard::checkerboard(input_raw.width(), input_raw.height(), color0, color1); - let input: Image = checkerboard.select_from_image_and_color(&input_raw, space_color).expect("image"); - HtmlLog::image(&input); - - // Act - part 1 - let actual0: Image = input.rotate_ccw_45(space_color).expect("image"); - HtmlLog::image(&actual0); - - // Act - part 2 - let rect: Rectangle = actual0.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); - - // Keep every second row and column - let keep_x: u8 = rect.x() & 1; - let keep_y: u8 = rect.y() & 1; - let mut delete_row_indexes = BitSet::new(); - let mut delete_column_indexes = BitSet::new(); - for x in 0..actual0.width() { - if x & 1 == keep_x { - continue; - } - delete_column_indexes.insert(x as usize); - } - for y in 0..actual0.height() { - if y & 1 == keep_y { - continue; - } - delete_row_indexes.insert(y as usize); - } - - // Remove the rows and columns - let actual1: Image = actual0.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); - HtmlLog::image(&actual1); - - // Replace the spacer color with the staircase color - let actual2: Image = actual1.replace_color(space_color, staircase_color).expect("image"); - HtmlLog::image(&actual2); + let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } #[test] - fn test_30006_rotate45extract_ccw() { + fn test_30001_rotate45extract_cw() { // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, @@ -698,7 +351,7 @@ mod tests { let input: Image = Image::try_create(5, 5, pixels).expect("image"); let verbose = false; - let is_clockwise = false; + let is_clockwise = true; let triangle_color: u8 = 11; // Act @@ -715,9 +368,9 @@ mod tests { let expected_pixels1: Vec = vec![ 11, 11, 0, 11, 11, - 11, 3, 6, 9, 11, - 0, 2, 5, 8, 0, - 11, 1, 4, 7, 11, + 11, 1, 2, 3, 11, + 0, 4, 5, 6, 0, + 11, 7, 8, 9, 11, 11, 11, 0, 11, 11, ]; let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); From 61d528df48bf9df3bcebaf0a90474ee97785111e Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 23:15:44 +0100 Subject: [PATCH 19/28] Extract 2 squares from the input image. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index b861f90f..82768ac9 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -300,7 +300,7 @@ mod tests { } #[test] - fn test_30000_rotate45extract_ccw() { + fn test_30000_rotate45extract_ccw_square() { // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, @@ -339,7 +339,7 @@ mod tests { } #[test] - fn test_30001_rotate45extract_cw() { + fn test_30001_rotate45extract_cw_square() { // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, @@ -377,6 +377,46 @@ mod tests { assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } + #[test] + fn test_30002_rotate45extract_ccw_nonsquare() { + // Arrange + let pixels: Vec = vec![ + 0, 0, 1, 2, 0, 0, + 0, 1, 2, 1, 2, 0, + 1, 2, 0, 0, 1, 2, + 0, 1, 2, 1, 2, 0, + 0, 0, 1, 2, 0, 0, + ]; + let input: Image = Image::try_create(6, 5, pixels).expect("image"); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + let expected_pixels0: Vec = vec![ + 11, 11, 0, 11, 11, + 11, 2, 2, 2, 11, + 0, 2, 0, 2, 0, + 0, 2, 2, 2, 11, + 11, 0, 0, 11, 11, + ]; + let expected0: Image = Image::try_create(5, 5, expected_pixels0).expect("image"); + + let expected_pixels1: Vec = vec![ + 11, 11, 0, 0, 11, + 11, 1, 1, 1, 0, + 0, 1, 0, 1, 0, + 11, 1, 1, 1, 11, + 11, 11, 0, 11, 11, + ]; + let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); + } + #[allow(dead_code)] // #[test] fn test_30007_reversable_ccw() { From bad2a2605dc16c60db10ce436aee5d13d42ec9af Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 23:49:50 +0100 Subject: [PATCH 20/28] More accurate handling of tiny images with size 0x0 and 1x1. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 137 ++++++++++++++---- 1 file changed, 112 insertions(+), 25 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 82768ac9..57f87fdb 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -77,8 +77,8 @@ impl Rotate45Extract { if verbose { HtmlLog::image(&image); } - let rotated_a: Image = Self::extract_lattice(image, verbose, triangle_color, is_clockwise, false)?; - let rotated_b: Image = Self::extract_lattice(image, verbose, triangle_color, is_clockwise, true)?; + let rotated_a: Image = Self::extract_lattice(image, triangle_color, is_clockwise, false)?; + let rotated_b: Image = Self::extract_lattice(image, triangle_color, is_clockwise, true)?; if verbose { HtmlLog::compare_images(vec![rotated_a.clone(), rotated_b.clone()]); } @@ -89,26 +89,34 @@ impl Rotate45Extract { Ok(instance) } - #[allow(dead_code)] - fn extract_lattice(input: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool, extract_second: bool) -> anyhow::Result { - let space_color: u8 = 255; + fn extract_lattice(input: &Image, triangle_color: u8, is_clockwise: bool, extract_second: bool) -> anyhow::Result { + if input.is_empty() { + // Nothing to extract from an empty image. + return Ok(input.clone()); + } + if input.width() == 1 && input.height() == 1 { + // When the input is 1x1, then there is a special case. + if extract_second { + // For the secondary lattice, return an empty image. Since there is no secondary lattice to extract. + return Ok(Image::empty()); + } else { + // For the primary lattice, return the input 1x1 image. + return Ok(input.clone()); + } + } + + let magic_space_color: u8 = 255; let color0: u8 = if extract_second { 0 } else { 1 }; let color1: u8 = if extract_second { 1 } else { 0 }; let mask: Image = Checkerboard::checkerboard(input.width(), input.height(), color0, color1); - let masked_input: Image = mask.select_from_image_and_color(&input, space_color).expect("image"); - // if verbose { - // HtmlLog::image(&masked_input); - // } + let masked_input: Image = mask.select_from_image_and_color(&input, magic_space_color).expect("image"); // Rotate CW or CCW - let rotated_image: Image = rotate_45(&masked_input, space_color, is_clockwise)?; - // if verbose { - // HtmlLog::image(&rotated_image); - // } + let rotated_image: Image = rotate_45(&masked_input, magic_space_color, is_clockwise)?; // Bounding box - let rect: Rectangle = rotated_image.outer_bounding_box_after_trim_with_color(space_color).expect("rectangle"); + let rect: Rectangle = rotated_image.outer_bounding_box_after_trim_with_color(magic_space_color).expect("rectangle"); // Determine where in the lattice the image is located let keep_x: u8 = rect.x() & 1; @@ -131,14 +139,11 @@ impl Rotate45Extract { } // Remove rows and columns - let actual1: Image = rotated_image.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); - // if verbose { - // HtmlLog::image(&actual1); - // } + let extracted_image: Image = rotated_image.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); // Assign color to the corner triangles - let actual2: Image = actual1.replace_color(space_color, triangle_color).expect("image"); - Ok(actual2) + let extracted_image_with_corner_triangles: Image = extracted_image.replace_color(magic_space_color, triangle_color).expect("image"); + Ok(extracted_image_with_corner_triangles) } } @@ -300,7 +305,89 @@ mod tests { } #[test] - fn test_30000_rotate45extract_ccw_square() { + fn test_30000_rotate45extract_empty() { + // Arrange + let input: Image = Image::empty(); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::empty(), Image::empty()]); + } + + #[test] + fn test_30001_rotate45extract_tiny1x1() { + // Arrange + let input: Image = Image::color(1, 1, 7); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 7), Image::empty()]); + } + + #[test] + fn test_30002_rotate45extract_tiny2x1() { + // Arrange + let input: Image = Image::try_create(2, 1, vec![7, 8]).expect("image"); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 8), Image::color(1, 1, 7)]); + } + + #[test] + fn test_30003_rotate45extract_tiny1x2() { + // Arrange + let input: Image = Image::try_create(1, 2, vec![7, 8]).expect("image"); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 8), Image::color(1, 1, 7)]); + } + + #[test] + fn test_30004_rotate45extract_tiny2x2() { + // Arrange + let input: Image = Image::try_create(2, 2, vec![1, 2, 3, 4]).expect("image"); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + let expected0: Image = Image::try_create(1, 2, vec![2, 3]).expect("image"); + let expected1: Image = Image::try_create(2, 1, vec![1, 4]).expect("image"); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); + } + + #[test] + fn test_30005_rotate45extract_ccw_square() { // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, @@ -339,7 +426,7 @@ mod tests { } #[test] - fn test_30001_rotate45extract_cw_square() { + fn test_30006_rotate45extract_cw_square() { // Arrange let pixels: Vec = vec![ 0, 0, 3, 0, 0, @@ -378,7 +465,7 @@ mod tests { } #[test] - fn test_30002_rotate45extract_ccw_nonsquare() { + fn test_30007_rotate45extract_ccw_nonsquare() { // Arrange let pixels: Vec = vec![ 0, 0, 1, 2, 0, 0, @@ -419,12 +506,12 @@ mod tests { #[allow(dead_code)] // #[test] - fn test_30007_reversable_ccw() { + fn test_30008_reversable_ccw() { let input: Image = Checkerboard::checkerboard(6, 3, 1, 3); let verbose = true; let triangle_color: u8 = 11; let is_clockwise = false; - let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + let _actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); } } From e58b32bc25f4c2b3112ca8108f2ac797dc3f108d Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Mon, 5 Feb 2024 23:53:08 +0100 Subject: [PATCH 21/28] rename --- rust_project/loda-rust-cli/src/arc/image_rotate45.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 57f87fdb..0b9e1d0a 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -109,8 +109,8 @@ impl Rotate45Extract { let color0: u8 = if extract_second { 0 } else { 1 }; let color1: u8 = if extract_second { 1 } else { 0 }; - let mask: Image = Checkerboard::checkerboard(input.width(), input.height(), color0, color1); - let masked_input: Image = mask.select_from_image_and_color(&input, magic_space_color).expect("image"); + let checkerboard_mask: Image = Checkerboard::checkerboard(input.width(), input.height(), color0, color1); + let masked_input: Image = checkerboard_mask.select_from_image_and_color(&input, magic_space_color).expect("image"); // Rotate CW or CCW let rotated_image: Image = rotate_45(&masked_input, magic_space_color, is_clockwise)?; From c046f8302d6086545a78328559f7e5039c85d4b0 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 00:00:04 +0100 Subject: [PATCH 22/28] swapped primary/secondary lattice, so it's consistent with what happens when the input image is a tiny 1x1, and there is no secondary lattice. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 0b9e1d0a..c124f811 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -107,8 +107,8 @@ impl Rotate45Extract { let magic_space_color: u8 = 255; - let color0: u8 = if extract_second { 0 } else { 1 }; - let color1: u8 = if extract_second { 1 } else { 0 }; + let color0: u8 = if extract_second { 1 } else { 0 }; + let color1: u8 = if extract_second { 0 } else { 1 }; let checkerboard_mask: Image = Checkerboard::checkerboard(input.width(), input.height(), color0, color1); let masked_input: Image = checkerboard_mask.select_from_image_and_color(&input, magic_space_color).expect("image"); @@ -349,7 +349,7 @@ mod tests { let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); // Assert - assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 8), Image::color(1, 1, 7)]); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 7), Image::color(1, 1, 8)]); } #[test] @@ -365,7 +365,7 @@ mod tests { let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); // Assert - assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 8), Image::color(1, 1, 7)]); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![Image::color(1, 1, 7), Image::color(1, 1, 8)]); } #[test] @@ -381,8 +381,8 @@ mod tests { let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); // Assert - let expected0: Image = Image::try_create(1, 2, vec![2, 3]).expect("image"); - let expected1: Image = Image::try_create(2, 1, vec![1, 4]).expect("image"); + let expected0: Image = Image::try_create(2, 1, vec![1, 4]).expect("image"); + let expected1: Image = Image::try_create(1, 2, vec![2, 3]).expect("image"); assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } @@ -407,21 +407,21 @@ mod tests { // Assert let expected_pixels0: Vec = vec![ - 11, 0, 0, 11, - 0, 0, 0, 0, - 0, 0, 0, 0, - 11, 0, 0, 11, - ]; - let expected0: Image = Image::try_create(4, 4, expected_pixels0).expect("image"); - - let expected_pixels1: Vec = vec![ 11, 11, 0, 11, 11, 11, 3, 6, 9, 11, 0, 2, 5, 8, 0, 11, 1, 4, 7, 11, 11, 11, 0, 11, 11, ]; - let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); + let expected0: Image = Image::try_create(5, 5, expected_pixels0).expect("image"); + + let expected_pixels1: Vec = vec![ + 11, 0, 0, 11, + 0, 0, 0, 0, + 0, 0, 0, 0, + 11, 0, 0, 11, + ]; + let expected1: Image = Image::try_create(4, 4, expected_pixels1).expect("image"); assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } @@ -446,21 +446,22 @@ mod tests { // Assert let expected_pixels0: Vec = vec![ - 11, 0, 0, 11, - 0, 0, 0, 0, - 0, 0, 0, 0, - 11, 0, 0, 11, - ]; - let expected0: Image = Image::try_create(4, 4, expected_pixels0).expect("image"); - - let expected_pixels1: Vec = vec![ 11, 11, 0, 11, 11, 11, 1, 2, 3, 11, 0, 4, 5, 6, 0, 11, 7, 8, 9, 11, 11, 11, 0, 11, 11, ]; - let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); + let expected0: Image = Image::try_create(5, 5, expected_pixels0).expect("image"); + + let expected_pixels1: Vec = vec![ + 11, 0, 0, 11, + 0, 0, 0, 0, + 0, 0, 0, 0, + 11, 0, 0, 11, + ]; + let expected1: Image = Image::try_create(4, 4, expected_pixels1).expect("image"); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } @@ -485,20 +486,20 @@ mod tests { // Assert let expected_pixels0: Vec = vec![ + 11, 11, 0, 0, 11, + 11, 1, 1, 1, 0, + 0, 1, 0, 1, 0, + 11, 1, 1, 1, 11, 11, 11, 0, 11, 11, - 11, 2, 2, 2, 11, - 0, 2, 0, 2, 0, - 0, 2, 2, 2, 11, - 11, 0, 0, 11, 11, ]; let expected0: Image = Image::try_create(5, 5, expected_pixels0).expect("image"); let expected_pixels1: Vec = vec![ - 11, 11, 0, 0, 11, - 11, 1, 1, 1, 0, - 0, 1, 0, 1, 0, - 11, 1, 1, 1, 11, 11, 11, 0, 11, 11, + 11, 2, 2, 2, 11, + 0, 2, 0, 2, 0, + 0, 2, 2, 2, 11, + 11, 0, 0, 11, 11, ]; let expected1: Image = Image::try_create(5, 5, expected_pixels1).expect("image"); assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); From 8ff6f9293a961a07887f97784de27b41582cbcd9 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 00:09:48 +0100 Subject: [PATCH 23/28] Better error handling --- rust_project/loda-rust-cli/src/arc/image_rotate45.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index c124f811..bf25394e 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -110,13 +110,13 @@ impl Rotate45Extract { let color0: u8 = if extract_second { 1 } else { 0 }; let color1: u8 = if extract_second { 0 } else { 1 }; let checkerboard_mask: Image = Checkerboard::checkerboard(input.width(), input.height(), color0, color1); - let masked_input: Image = checkerboard_mask.select_from_image_and_color(&input, magic_space_color).expect("image"); + let masked_input: Image = checkerboard_mask.select_from_image_and_color(&input, magic_space_color)?; // Rotate CW or CCW let rotated_image: Image = rotate_45(&masked_input, magic_space_color, is_clockwise)?; // Bounding box - let rect: Rectangle = rotated_image.outer_bounding_box_after_trim_with_color(magic_space_color).expect("rectangle"); + let rect: Rectangle = rotated_image.outer_bounding_box_after_trim_with_color(magic_space_color)?; // Determine where in the lattice the image is located let keep_x: u8 = rect.x() & 1; @@ -139,10 +139,10 @@ impl Rotate45Extract { } // Remove rows and columns - let extracted_image: Image = rotated_image.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes).expect("image"); + let extracted_image: Image = rotated_image.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes)?; // Assign color to the corner triangles - let extracted_image_with_corner_triangles: Image = extracted_image.replace_color(magic_space_color, triangle_color).expect("image"); + let extracted_image_with_corner_triangles: Image = extracted_image.replace_color(magic_space_color, triangle_color)?; Ok(extracted_image_with_corner_triangles) } } From 6ade9c11cb814ba49f9a2d9cec75cae1abb9fe20 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 00:10:01 +0100 Subject: [PATCH 24/28] more tests --- .../loda-rust-cli/src/arc/image_rotate45.rs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index bf25394e..43cf9325 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -505,6 +505,44 @@ mod tests { assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } + #[test] + fn test_30008_rotate45extract_cw_nonsquare() { + // Arrange + let pixels: Vec = vec![ + 1, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 6, + 0, 0, 3, 0, 5, 0, + 0, 0, 0, 4, 0, 0, + ]; + let input: Image = Image::try_create(6, 4, pixels).expect("image"); + + let verbose = false; + let is_clockwise = false; + let triangle_color: u8 = 11; + + // Act + let actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); + + // Assert + let expected_pixels0: Vec = vec![ + 11, 11, 0, 6, 11, + 11, 0, 0, 5, 0, + 1, 2, 3, 4, 11, + 11, 0, 0, 11, 11, + ]; + let expected0: Image = Image::try_create(5, 4, expected_pixels0).expect("image"); + + let expected_pixels1: Vec = vec![ + 11, 11, 0, 11, + 11, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 11, + 11, 0, 11, 11, + ]; + let expected1: Image = Image::try_create(4, 5, expected_pixels1).expect("image"); + assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); + } + #[allow(dead_code)] // #[test] fn test_30008_reversable_ccw() { From fa21c8797c0d7de47609951eaa003205aa4594f3 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 00:26:33 +0100 Subject: [PATCH 25/28] Removed dead code --- .../loda-rust-cli/src/arc/image_rotate45.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 43cf9325..57b10dea 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -150,7 +150,7 @@ impl Rotate45Extract { #[cfg(test)] mod tests { use super::*; - use crate::arc::{Checkerboard, ImageTryCreate}; + use crate::arc::ImageTryCreate; #[test] fn test_10000_rotate_tiny_images() { @@ -542,15 +542,4 @@ mod tests { let expected1: Image = Image::try_create(4, 5, expected_pixels1).expect("image"); assert_eq!(vec![actual.rotated_a, actual.rotated_b], vec![expected0, expected1]); } - - #[allow(dead_code)] - // #[test] - fn test_30008_reversable_ccw() { - let input: Image = Checkerboard::checkerboard(6, 3, 1, 3); - - let verbose = true; - let triangle_color: u8 = 11; - let is_clockwise = false; - let _actual: Rotate45Extract = Rotate45Extract::process(&input, verbose, triangle_color, is_clockwise).expect("reverse rotate"); - } } From 5863e95d2a10fe019af8cb88976e2812a56c2825 Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 00:26:51 +0100 Subject: [PATCH 26/28] Made Rotate45Extract public. --- .../loda-rust-cli/src/arc/image_rotate45.rs | 21 ++++++++++++------- rust_project/loda-rust-cli/src/arc/mod.rs | 5 ++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 57b10dea..7cf93464 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -66,19 +66,19 @@ fn rotate_45(original: &Image, fill_color: u8, is_clockwise: bool) -> anyhow::Re } #[allow(dead_code)] -struct Rotate45Extract { - rotated_a: Image, - rotated_b: Image, +pub struct Rotate45Extract { + pub rotated_a: Image, + pub rotated_b: Image, } impl Rotate45Extract { #[allow(dead_code)] - fn process(image: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool) -> anyhow::Result { + pub fn process(image: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool) -> anyhow::Result { if verbose { HtmlLog::image(&image); } - let rotated_a: Image = Self::extract_lattice(image, triangle_color, is_clockwise, false)?; - let rotated_b: Image = Self::extract_lattice(image, triangle_color, is_clockwise, true)?; + let rotated_a: Image = Self::rotate_and_extract(image, triangle_color, is_clockwise, false)?; + let rotated_b: Image = Self::rotate_and_extract(image, triangle_color, is_clockwise, true)?; if verbose { HtmlLog::compare_images(vec![rotated_a.clone(), rotated_b.clone()]); } @@ -89,7 +89,14 @@ impl Rotate45Extract { Ok(instance) } - fn extract_lattice(input: &Image, triangle_color: u8, is_clockwise: bool, extract_second: bool) -> anyhow::Result { + /// Rotate by 45 degrees, and extract the primary/secondary lattice. + /// + /// - When `extract_second == false`, then extract the primary lattice. + /// - When `extract_second == true`, then extract the secondary lattice. + /// + /// The `triangle_color` is assigned to the corner triangles. + /// When rotating by 45 degrees, the bigger images usually gets triangles in the corners. + fn rotate_and_extract(input: &Image, triangle_color: u8, is_clockwise: bool, extract_second: bool) -> anyhow::Result { if input.is_empty() { // Nothing to extract from an empty image. return Ok(input.clone()); diff --git a/rust_project/loda-rust-cli/src/arc/mod.rs b/rust_project/loda-rust-cli/src/arc/mod.rs index 0a5ab738..4bbeb880 100644 --- a/rust_project/loda-rust-cli/src/arc/mod.rs +++ b/rust_project/loda-rust-cli/src/arc/mod.rs @@ -280,7 +280,10 @@ pub use image_replace_regex::{ImageReplaceRegex, ImageReplaceRegexToColor}; pub use image_replace_simple::ImageReplaceSimple; pub use image_resize::ImageResize; -pub use image_rotate45::ImageRotate45; + +#[allow(unused_imports)] +pub use image_rotate45::{ImageRotate45, Rotate45Extract}; + pub use image_rotate90::ImageRotate90; pub use image_rowcolumn_order::ImageRowColumnOrder; pub use image_set_pixel_where::ImageSetPixelWhere; From f331490f7b6537b66886e0c7dda0405582e0866a Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 00:36:22 +0100 Subject: [PATCH 27/28] Documentation --- rust_project/loda-rust-cli/src/arc/image_rotate45.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs index 7cf93464..5d85050b 100644 --- a/rust_project/loda-rust-cli/src/arc/image_rotate45.rs +++ b/rust_project/loda-rust-cli/src/arc/image_rotate45.rs @@ -72,6 +72,11 @@ pub struct Rotate45Extract { } impl Rotate45Extract { + /// The usual rotate by 45 degrees introduces a checkerboard of gaps in the image. + /// An question is, can the gaps be eliminated? + /// The answer is `Yes`, this is the code. + /// + /// Rotate by 45 degrees, and extract the primary/secondary lattice. #[allow(dead_code)] pub fn process(image: &Image, verbose: bool, triangle_color: u8, is_clockwise: bool) -> anyhow::Result { if verbose { @@ -125,11 +130,11 @@ impl Rotate45Extract { // Bounding box let rect: Rectangle = rotated_image.outer_bounding_box_after_trim_with_color(magic_space_color)?; - // Determine where in the lattice the image is located + // Determine where in the lattice is located inside the image let keep_x: u8 = rect.x() & 1; let keep_y: u8 = rect.y() & 1; - // Keep every second row and column + // Keep every second row and column let mut delete_row_indexes = BitSet::new(); let mut delete_column_indexes = BitSet::new(); for x in 0..rotated_image.width() { @@ -145,7 +150,7 @@ impl Rotate45Extract { delete_row_indexes.insert(y as usize); } - // Remove rows and columns + // Remove rows and columns from the lattice. let extracted_image: Image = rotated_image.remove_rowcolumn(&delete_row_indexes, &delete_column_indexes)?; // Assign color to the corner triangles From 0bcb7aa13824cfb4f35f74ecf75d00997ae9ef3c Mon Sep 17 00:00:00 2001 From: Simon Strandgaard Date: Tue, 6 Feb 2024 13:52:34 +0100 Subject: [PATCH 28/28] Disabled loda-rust-arc feature. Bumped version. --- rust_project/Cargo.lock | 2 +- rust_project/loda-rust-cli/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust_project/Cargo.lock b/rust_project/Cargo.lock index b0a571d9..60f39f00 100644 --- a/rust_project/Cargo.lock +++ b/rust_project/Cargo.lock @@ -2124,7 +2124,7 @@ dependencies = [ [[package]] name = "loda-rust-cli" -version = "2024.1.6" +version = "2024.2.6" dependencies = [ "ahash", "alphanumeric-sort", diff --git a/rust_project/loda-rust-cli/Cargo.toml b/rust_project/loda-rust-cli/Cargo.toml index a6c8f9f1..0e3412a8 100644 --- a/rust_project/loda-rust-cli/Cargo.toml +++ b/rust_project/loda-rust-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "loda-rust-cli" -version = "2024.1.6" +version = "2024.2.6" authors = ["Simon Strandgaard "] description = "Command line interface for LODA Rust" repository = "https://github.com/loda-lang/loda-rust" @@ -12,7 +12,7 @@ name = "loda-rust" path = "src/main.rs" [features] -default = ["loda-rust-arc"] +# default = ["loda-rust-arc"] loda-rust-arc = ["dep:petgraph", "dep:image_crate", "dep:linfa", "dep:linfa-logistic", "dep:linfa-preprocessing", "dep:ndarray", "dep:rayon"] [dependencies]