From 6e244a08ab115f77d44f5a7971164f4f04aff21e Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 3 Jun 2022 21:01:20 +0200 Subject: [PATCH] utils: move damage algorithms to algorithm crate --- Cargo.lock | 13 +- Cargo.toml | 9 +- {qoi => algorithms}/Cargo.toml | 3 +- algorithms/src/lib.rs | 5 + qoi/src/lib.rs => algorithms/src/qoi.rs | 0 algorithms/src/rect.rs | 35 ++ algorithms/src/rect/region.rs | 466 +++++++++++++++++++++++ algorithms/src/windows.rs | 35 ++ src/cli/screenshot.rs | 2 +- src/rect.rs | 132 ++++--- src/rect/region.rs | 477 +----------------------- src/rect/tests.rs | 75 +++- 12 files changed, 710 insertions(+), 542 deletions(-) rename {qoi => algorithms}/Cargo.toml (60%) create mode 100644 algorithms/src/lib.rs rename qoi/src/lib.rs => algorithms/src/qoi.rs (100%) create mode 100644 algorithms/src/rect.rs create mode 100644 algorithms/src/rect/region.rs create mode 100644 algorithms/src/windows.rs diff --git a/Cargo.lock b/Cargo.lock index e9e43c96..c552838f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,13 @@ dependencies = [ "version_check", ] +[[package]] +name = "algorithms" +version = "0.1.0" +dependencies = [ + "smallvec", +] + [[package]] name = "anyhow" version = "1.0.56" @@ -308,6 +315,7 @@ name = "jay" version = "0.1.0" dependencies = [ "ahash", + "algorithms", "anyhow", "backtrace", "bincode", @@ -329,7 +337,6 @@ dependencies = [ "num-traits", "once_cell", "pin-project", - "qoi", "rand", "repc", "smallvec", @@ -517,10 +524,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "qoi" -version = "0.1.0" - [[package]] name = "quote" version = "1.0.16" diff --git a/Cargo.toml b/Cargo.toml index 5d14ca93..e1eeaa89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" build = "build/build.rs" [workspace] -members = ["jay-config", "default-config", "qoi"] +members = ["jay-config", "default-config", "algorithms"] [profile.release] panic = "abort" @@ -32,7 +32,7 @@ byteorder = "1.4.3" bincode = "2.0.0-rc.1" jay-config = { path = "jay-config" } default-config = { path = "default-config" } -qoi = { path = "qoi" } +algorithms = { path = "algorithms" } pin-project = "1.0.10" clap = { version = "3.1.6", features = ["derive", "wrap_help"] } clap_complete = "3.1.1" @@ -49,7 +49,10 @@ bstr = { version = "0.2.17", default-features = false, features = ["std"] } #[profile.dev.build-override] #opt-level = 3 -[profile.dev.package."qoi"] +[profile.dev.package."algorithms"] +opt-level = 3 + +[profile.dev.package."smallvec"] opt-level = 3 [features] diff --git a/qoi/Cargo.toml b/algorithms/Cargo.toml similarity index 60% rename from qoi/Cargo.toml rename to algorithms/Cargo.toml index d9b4ba3b..7a908e8d 100644 --- a/qoi/Cargo.toml +++ b/algorithms/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "qoi" +name = "algorithms" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +smallvec = { version = "1.8.0", features = ["const_generics", "const_new", "union"] } diff --git a/algorithms/src/lib.rs b/algorithms/src/lib.rs new file mode 100644 index 00000000..62413f59 --- /dev/null +++ b/algorithms/src/lib.rs @@ -0,0 +1,5 @@ +#![feature(generic_associated_types)] + +pub mod qoi; +pub mod rect; +mod windows; diff --git a/qoi/src/lib.rs b/algorithms/src/qoi.rs similarity index 100% rename from qoi/src/lib.rs rename to algorithms/src/qoi.rs diff --git a/algorithms/src/rect.rs b/algorithms/src/rect.rs new file mode 100644 index 00000000..a0cc6e28 --- /dev/null +++ b/algorithms/src/rect.rs @@ -0,0 +1,35 @@ +pub mod region; + +use { + smallvec::SmallVec, + std::fmt::{Debug, Formatter}, +}; + +#[derive(Copy, Clone, Eq, PartialEq, Default)] +pub struct RectRaw { + pub x1: i32, + pub y1: i32, + pub x2: i32, + pub y2: i32, +} + +impl Debug for RectRaw { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Rect") + .field("x1", &self.x1) + .field("y1", &self.y1) + .field("x2", &self.x2) + .field("y2", &self.y2) + .field("width", &(self.x2 - self.x1)) + .field("height", &(self.y2 - self.y1)) + .finish() + } +} + +impl RectRaw { + fn is_empty(&self) -> bool { + self.x1 == self.x2 || self.y1 == self.y2 + } +} + +type Container = SmallVec<[RectRaw; 1]>; diff --git a/algorithms/src/rect/region.rs b/algorithms/src/rect/region.rs new file mode 100644 index 00000000..ebca5597 --- /dev/null +++ b/algorithms/src/rect/region.rs @@ -0,0 +1,466 @@ +use { + crate::{ + rect::{Container, RectRaw}, + windows::WindowsExt, + }, + std::{cmp::Ordering, collections::BinaryHeap, mem, ops::Deref}, +}; + +pub fn union(left: &Container, right: &Container) -> Container { + op::(left, &right) +} + +pub fn subtract(left: &Container, right: &Container) -> Container { + op::(left, right) +} + +struct Bands<'a> { + rects: &'a [RectRaw], +} + +#[derive(Copy, Clone)] +struct Band<'a> { + rects: &'a [RectRaw], + y1: i32, + y2: i32, +} + +impl<'a> Band<'a> { + fn can_merge_with(&self, next: &Band) -> bool { + next.rects.len() == self.rects.len() + && next.y1 == self.y2 + && next + .rects + .iter() + .zip(self.rects.iter()) + .all(|(a, b)| (a.x1, a.x2) == (b.x1, b.x2)) + } +} + +impl<'a> Iterator for Bands<'a> { + type Item = Band<'a>; + + fn next(&mut self) -> Option { + if self.rects.is_empty() { + return None; + } + let y1 = self.rects[0].y1; + let y2 = self.rects[0].y2; + for (pos, rect) in self.rects[1..].iter().enumerate() { + if rect.y1 != y1 { + let (res, rects) = self.rects.split_at(pos + 1); + self.rects = rects; + return Some(Band { rects: res, y1, y2 }); + } + } + Some(Band { + rects: mem::replace(&mut self.rects, &[]), + y1, + y2, + }) + } +} + +#[inline] +pub fn extents(a: &[RectRaw]) -> RectRaw { + let mut a = a.iter(); + let mut res = match a.next() { + Some(a) => *a, + _ => return RectRaw::default(), + }; + for a in a { + res.x1 = res.x1.min(a.x1); + res.y1 = res.y1.min(a.y1); + res.x2 = res.x2.max(a.x2); + res.y2 = res.y2.max(a.y2); + } + res +} + +fn op(a: &[RectRaw], b: &[RectRaw]) -> Container { + let mut res = Container::new(); + + let mut prev_band_y2 = 0; + let mut prev_band_start = 0; + let mut cur_band_start; + + let mut a_bands = Bands { rects: a }; + let mut b_bands = Bands { rects: b }; + + let mut a_opt = a_bands.next(); + let mut b_opt = b_bands.next(); + + macro_rules! fixup_new_band { + ($y1:expr, $y2:expr) => {{ + if prev_band_y2 != $y1 || !coalesce(&mut res, prev_band_start, cur_band_start, $y2) { + prev_band_start = cur_band_start; + } + prev_band_y2 = $y2; + }}; + } + + macro_rules! append_nonoverlapping { + ($append_opt:expr, $a:expr, $a_opt:expr, $a_bands:expr, $b:expr) => {{ + if $append_opt { + let y2 = $a.y2.min($b.y1); + cur_band_start = res.len(); + res.reserve($a.rects.len()); + for rect in $a.rects { + res.push(RectRaw { + x1: rect.x1, + y1: $a.y1, + x2: rect.x2, + y2, + }); + } + fixup_new_band!($a.y1, y2); + } + if $a.y2 <= $b.y1 { + $a_opt = $a_bands.next(); + } else { + $a.y1 = $b.y1; + } + }}; + } + + while let (Some(a), Some(b)) = (&mut a_opt, &mut b_opt) { + if a.y1 < b.y1 { + append_nonoverlapping!(O::APPEND_NON_A, a, a_opt, a_bands, b); + } else if b.y1 < a.y1 { + append_nonoverlapping!(O::APPEND_NON_B, b, b_opt, b_bands, a); + } else { + let y2 = a.y2.min(b.y2); + cur_band_start = res.len(); + O::handle_band(&mut res, a.rects, b.rects, a.y1, y2); + if res.len() > cur_band_start { + fixup_new_band!(a.y1, y2); + } + if a.y2 == y2 { + a_opt = a_bands.next(); + } else { + a.y1 = y2; + } + if b.y2 == y2 { + b_opt = b_bands.next(); + } else { + b.y1 = y2; + } + } + } + + macro_rules! push_trailing { + ($a_opt:expr, $a_bands:expr) => {{ + while let Some(a) = $a_opt { + cur_band_start = res.len(); + res.reserve(a.rects.len()); + for rect in a.rects { + res.push(RectRaw { + x1: rect.x1, + y1: a.y1, + x2: rect.x2, + y2: a.y2, + }); + } + fixup_new_band!(a.y1, a.y2); + $a_opt = $a_bands.next(); + } + }}; + } + + if O::APPEND_NON_A { + push_trailing!(a_opt, a_bands); + } + + if O::APPEND_NON_B { + push_trailing!(b_opt, b_bands); + } + + res.shrink_to_fit(); + res +} + +fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool { + if new.len() - b != b - a { + return false; + } + let slice_a = &new[a..b]; + let slice_b = &new[b..]; + for (a, b) in slice_a.iter().zip(slice_b.iter()) { + if (a.x1, a.x2) != (b.x1, b.x2) { + return false; + } + } + for rect in &mut new[a..b] { + rect.y2 = y2; + } + new.truncate(b); + true +} + +trait Op { + const APPEND_NON_A: bool; + const APPEND_NON_B: bool; + + fn handle_band(new: &mut Container, a: &[RectRaw], b: &[RectRaw], y1: i32, y2: i32); +} + +struct Union; + +impl Op for Union { + const APPEND_NON_A: bool = true; + const APPEND_NON_B: bool = true; + + fn handle_band(new: &mut Container, mut a: &[RectRaw], mut b: &[RectRaw], y1: i32, y2: i32) { + let mut x1; + let mut x2; + + macro_rules! push { + () => { + new.push(RectRaw { x1, y1, x2, y2 }); + }; + } + + macro_rules! merge { + ($r:expr) => { + if $r.x1 <= x2 { + if $r.x2 > x2 { + x2 = $r.x2; + } + } else { + push!(); + x1 = $r.x1; + x2 = $r.x2; + } + }; + } + + if a[0].x1 < b[0].x1 { + x1 = a[0].x1; + x2 = a[0].x2; + a = &a[1..]; + } else { + x1 = b[0].x1; + x2 = b[0].x2; + b = &b[1..]; + } + + let mut a_iter = a.iter(); + let mut b_iter = b.iter(); + + let mut a_opt = a_iter.next(); + let mut b_opt = b_iter.next(); + + while let (Some(a), Some(b)) = (a_opt, b_opt) { + if a.x1 < b.x1 { + merge!(a); + a_opt = a_iter.next(); + } else { + merge!(b); + b_opt = b_iter.next(); + } + } + + while let Some(a) = a_opt { + merge!(a); + a_opt = a_iter.next(); + } + + while let Some(b) = b_opt { + merge!(b); + b_opt = b_iter.next(); + } + + push!(); + } +} + +struct Subtract; + +impl Op for Subtract { + const APPEND_NON_A: bool = true; + const APPEND_NON_B: bool = false; + + fn handle_band(new: &mut Container, a: &[RectRaw], b: &[RectRaw], y1: i32, y2: i32) { + let mut x1; + let mut x2; + + macro_rules! push { + ($x2:expr) => { + new.push(RectRaw { + x1, + y1, + x2: $x2, + y2, + }); + }; + } + + let mut a_iter = a.iter(); + let mut b_iter = b.iter(); + + macro_rules! pull { + () => { + match a_iter.next() { + Some(n) => { + x1 = n.x1; + x2 = n.x2; + } + _ => return, + } + }; + } + + pull!(); + + let mut b_opt = b_iter.next(); + + while let Some(b) = b_opt { + if b.x2 <= x1 { + b_opt = b_iter.next(); + } else if b.x1 >= x2 { + push!(x2); + pull!(); + } else { + if b.x1 > x1 { + push!(b.x1); + } + if b.x2 < x2 { + x1 = b.x2; + } else { + pull!(); + } + } + } + + loop { + push!(x2); + pull!(); + } + } +} + +pub fn rects_to_bands(rects_tmp: &[RectRaw]) -> Container { + #[derive(Copy, Clone)] + struct W(RectRaw); + impl Eq for W {} + impl PartialEq for W { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl PartialOrd for W { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + impl Ord for W { + fn cmp(&self, other: &Self) -> Ordering { + self.0 + .y1 + .cmp(&other.0.y1) + .then_with(|| self.0.x1.cmp(&other.0.x1)) + .reverse() + } + } + impl Deref for W { + type Target = RectRaw; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + let ys = { + let mut tmp: Vec<_> = rects_tmp.iter().flat_map(|r| [r.y1, r.y2]).collect(); + tmp.sort_unstable(); + let mut last = None; + let mut res = vec![]; + for y in tmp { + if Some(y) != last { + last = Some(y); + res.push(y); + } + } + res + }; + + let mut rects = BinaryHeap::with_capacity(rects_tmp.len()); + for rect in rects_tmp.iter().copied() { + if !rect.is_empty() { + rects.push(W(rect)); + } + } + + let mut res = Container::new(); + + for &[y1, y2] in ys.array_windows_ext::<2>() { + loop { + macro_rules! check_rect { + ($rect:expr) => {{ + if $rect.y1 != y1 { + break; + } + rects.pop(); + if y2 < $rect.y2 { + $rect.0.y1 = y2; + rects.push($rect); + } + }}; + } + if let Some(mut rect) = rects.peek().copied() { + check_rect!(rect); + let mut x1 = rect.x1; + let mut x2 = rect.x2; + while let Some(mut rect) = rects.peek().copied() { + check_rect!(rect); + if rect.x1 > x2 { + res.push(RectRaw { x1, x2, y1, y2 }); + x1 = rect.x1; + x2 = rect.x2; + } else { + x2 = x2.max(rect.x2); + } + } + res.push(RectRaw { x1, x2, y1, y2 }); + } + break; + } + } + + let mut needs_merge = false; + let mut num_elements = res.len(); + let mut bands = Bands { rects: &res }.peekable(); + while let Some(band) = bands.next() { + let next = match bands.peek() { + Some(next) => next, + _ => break, + }; + if band.can_merge_with(next) { + needs_merge = true; + num_elements -= band.rects.len(); + } + } + + if !needs_merge { + res.shrink_to_fit(); + return res; + } + + let mut merged = Container::with_capacity(num_elements); + let mut bands = Bands { rects: &res }.peekable(); + while let Some(mut band) = bands.next() { + while let Some(next) = bands.peek() { + if band.can_merge_with(next) { + band.y2 = next.y2; + bands.next(); + } else { + break; + } + } + for mut rect in band.rects.iter().copied() { + rect.y2 = band.y2; + merged.push(rect); + } + } + + merged +} diff --git a/algorithms/src/windows.rs b/algorithms/src/windows.rs new file mode 100644 index 00000000..8332ec66 --- /dev/null +++ b/algorithms/src/windows.rs @@ -0,0 +1,35 @@ +pub trait WindowsExt { + type Windows<'a, const N: usize>: Iterator + where + Self: 'a, + T: 'a; + + fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N>; +} + +impl WindowsExt for [T] { + type Windows<'a, const N: usize> = WindowsIter<'a, T, N> where T: 'a; + + fn array_windows_ext<'a, const N: usize>(&'a self) -> Self::Windows<'a, N> { + WindowsIter { slice: self } + } +} + +pub struct WindowsIter<'a, T, const N: usize> { + slice: &'a [T], +} + +impl<'a, T, const N: usize> Iterator for WindowsIter<'a, T, N> { + type Item = &'a [T; N]; + + fn next(&mut self) -> Option { + if self.slice.len() < N { + return None; + } + let res = unsafe { &*self.slice.as_ptr().cast::<[T; N]>() }; + if N > 0 { + self.slice = &self.slice[1..]; + } + Some(res) + } +} diff --git a/src/cli/screenshot.rs b/src/cli/screenshot.rs index c269455f..7c9d5469 100644 --- a/src/cli/screenshot.rs +++ b/src/cli/screenshot.rs @@ -15,8 +15,8 @@ use { jay_screenshot::{Dmabuf, Error}, }, }, + algorithms::qoi::xrgb8888_encode_qoi, chrono::Local, - qoi::xrgb8888_encode_qoi, std::rc::Rc, }; diff --git a/src/rect.rs b/src/rect.rs index 8bd03191..c582d619 100644 --- a/src/rect.rs +++ b/src/rect.rs @@ -5,36 +5,26 @@ mod tests; pub use region::RegionBuilder; use { + algorithms::rect::RectRaw, smallvec::SmallVec, std::fmt::{Debug, Formatter}, }; #[derive(Copy, Clone, Eq, PartialEq, Default)] +#[repr(transparent)] pub struct Rect { - x1: i32, - y1: i32, - x2: i32, - y2: i32, + raw: RectRaw, } -type Container = SmallVec<[Rect; 1]>; - #[derive(Clone, Eq, PartialEq, Debug)] pub struct Region { - rects: Container, + rects: SmallVec<[RectRaw; 1]>, extents: Rect, } impl Debug for Rect { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Rect") - .field("x1", &self.x1) - .field("y1", &self.y1) - .field("x2", &self.x2) - .field("y2", &self.y2) - .field("width", &(self.x2 - self.x1)) - .field("height", &(self.y2 - self.y1)) - .finish() + Debug::fmt(&self.raw, f) } } @@ -64,10 +54,12 @@ impl Rect { #[allow(dead_code)] pub fn new_empty(x: i32, y: i32) -> Self { Self { - x1: x, - y1: y, - x2: x, - y2: y, + raw: RectRaw { + x1: x, + y1: y, + x2: x, + y2: y, + }, } } @@ -75,12 +67,16 @@ impl Rect { if x2 < x1 || y2 < y1 { return None; } - Some(Self { x1, y1, x2, y2 }) + Some(Self { + raw: RectRaw { x1, y1, x2, y2 }, + }) } #[allow(dead_code)] fn new_unchecked(x1: i32, y1: i32, x2: i32, y2: i32) -> Self { - Self { x1, y1, x2, y2 } + Self { + raw: RectRaw { x1, y1, x2, y2 }, + } } pub fn new_sized(x1: i32, y1: i32, width: i32, height: i32) -> Option { @@ -92,113 +88,129 @@ impl Rect { pub fn union(&self, other: Self) -> Self { Self { - x1: self.x1.min(other.x1), - y1: self.y1.min(other.y1), - x2: self.x2.max(other.x2), - y2: self.y2.max(other.y2), + raw: RectRaw { + x1: self.raw.x1.min(other.raw.x1), + y1: self.raw.y1.min(other.raw.y1), + x2: self.raw.x2.max(other.raw.x2), + y2: self.raw.y2.max(other.raw.y2), + }, } } pub fn intersects(&self, other: &Self) -> bool { - self.x1 < other.x2 && other.x1 < self.x2 && self.y1 < other.y2 && other.y1 < self.y2 + self.raw.x1 < other.raw.x2 + && other.raw.x1 < self.raw.x2 + && self.raw.y1 < other.raw.y2 + && other.raw.y1 < self.raw.y2 } pub fn intersect(&self, other: Self) -> Self { - let x1 = self.x1.max(other.x1); - let y1 = self.y1.max(other.y1); - let x2 = self.x2.min(other.x2).max(x1); - let y2 = self.y2.min(other.y2).max(y1); - Self { x1, y1, x2, y2 } + let x1 = self.raw.x1.max(other.raw.x1); + let y1 = self.raw.y1.max(other.raw.y1); + let x2 = self.raw.x2.min(other.raw.x2).max(x1); + let y2 = self.raw.y2.min(other.raw.y2).max(y1); + Self { + raw: RectRaw { x1, y1, x2, y2 }, + } } pub fn contains(&self, x: i32, y: i32) -> bool { - self.x1 <= x && self.y1 <= y && self.x2 > x && self.y2 > y + self.raw.x1 <= x && self.raw.y1 <= y && self.raw.x2 > x && self.raw.y2 > y } #[allow(dead_code)] pub fn contains_rect(&self, rect: &Self) -> bool { - self.x1 <= rect.x1 && self.y1 <= rect.x1 && rect.x2 <= self.x2 && rect.y2 <= self.y2 + self.raw.x1 <= rect.raw.x1 + && self.raw.y1 <= rect.raw.x1 + && rect.raw.x2 <= self.raw.x2 + && rect.raw.y2 <= self.raw.y2 } pub fn get_overflow(&self, child: &Self) -> RectOverflow { RectOverflow { - left: self.x1 - child.x1, - right: child.x2 - self.x2, - top: self.y1 - child.y1, - bottom: child.y2 - self.y2, + left: self.raw.x1 - child.raw.x1, + right: child.raw.x2 - self.raw.x2, + top: self.raw.y1 - child.raw.y1, + bottom: child.raw.y2 - self.raw.y2, } } pub fn is_empty(&self) -> bool { - self.x1 == self.x2 || self.y1 == self.y2 + self.raw.x1 == self.raw.x2 || self.raw.y1 == self.raw.y2 } #[allow(dead_code)] pub fn to_origin(&self) -> Self { Self { - x1: 0, - y1: 0, - x2: self.x2 - self.x1, - y2: self.y2 - self.y1, + raw: RectRaw { + x1: 0, + y1: 0, + x2: self.raw.x2 - self.raw.x1, + y2: self.raw.y2 - self.raw.y1, + }, } } pub fn move_(&self, dx: i32, dy: i32) -> Self { Self { - x1: self.x1.saturating_add(dx), - y1: self.y1.saturating_add(dy), - x2: self.x2.saturating_add(dx), - y2: self.y2.saturating_add(dy), + raw: RectRaw { + x1: self.raw.x1.saturating_add(dx), + y1: self.raw.y1.saturating_add(dy), + x2: self.raw.x2.saturating_add(dx), + y2: self.raw.y2.saturating_add(dy), + }, } } pub fn at_point(&self, x1: i32, y1: i32) -> Self { Self { - x1, - y1, - x2: x1 + self.x2 - self.x1, - y2: y1 + self.y2 - self.y1, + raw: RectRaw { + x1, + y1, + x2: x1 + self.raw.x2 - self.raw.x1, + y2: y1 + self.raw.y2 - self.raw.y1, + }, } } pub fn with_size(&self, width: i32, height: i32) -> Option { - Self::new_sized(self.x1, self.y1, width, height) + Self::new_sized(self.raw.x1, self.raw.y1, width, height) } pub fn translate(&self, x: i32, y: i32) -> (i32, i32) { - (x.wrapping_sub(self.x1), y.wrapping_sub(self.y1)) + (x.wrapping_sub(self.raw.x1), y.wrapping_sub(self.raw.y1)) } pub fn translate_inv(&self, x: i32, y: i32) -> (i32, i32) { - (x.wrapping_add(self.x1), y.wrapping_add(self.y1)) + (x.wrapping_add(self.raw.x1), y.wrapping_add(self.raw.y1)) } pub fn x1(&self) -> i32 { - self.x1 + self.raw.x1 } pub fn x2(&self) -> i32 { - self.x2 + self.raw.x2 } pub fn y1(&self) -> i32 { - self.y1 + self.raw.y1 } pub fn y2(&self) -> i32 { - self.y2 + self.raw.y2 } pub fn width(&self) -> i32 { - self.x2 - self.x1 + self.raw.x2 - self.raw.x1 } pub fn height(&self) -> i32 { - self.y2 - self.y1 + self.raw.y2 - self.raw.y1 } pub fn position(&self) -> (i32, i32) { - (self.x1, self.y1) + (self.raw.x1, self.raw.y1) } pub fn size(&self) -> (i32, i32) { diff --git a/src/rect/region.rs b/src/rect/region.rs index 0d85d4e2..16212691 100644 --- a/src/rect/region.rs +++ b/src/rect/region.rs @@ -1,11 +1,12 @@ use { - crate::{ - rect::{Container, Rect, Region}, - utils::windows::WindowsExt, + crate::rect::{Rect, Region}, + algorithms::rect::{ + region::{extents, rects_to_bands, subtract, union}, + RectRaw, }, once_cell::unsync::Lazy, smallvec::SmallVec, - std::{cmp::Ordering, collections::BinaryHeap, mem, ops::Deref, rc::Rc}, + std::{mem, ops::Deref, rc::Rc}, }; #[thread_local] @@ -19,7 +20,7 @@ static EMPTY: Lazy> = Lazy::new(|| { impl Region { pub fn new(rect: Rect) -> Rc { let mut rects = SmallVec::new(); - rects.push(rect); + rects.push(rect.raw); Rc::new(Self { rects, extents: rect, @@ -37,9 +38,11 @@ impl Region { if rects.len() == 1 { return Self::new(rects[0]); } - let rects = rects_to_bands(rects); + let rects = rects_to_bands(unsafe { mem::transmute(rects) }); Rc::new(Self { - extents: extents(&rects), + extents: Rect { + raw: extents(&rects), + }, rects, }) } @@ -51,7 +54,7 @@ impl Region { if other.extents.is_empty() { return self.clone(); } - let rects = op::(&self.rects, &other.rects); + let rects = union(&self.rects, &other.rects); Rc::new(Self { rects, extents: self.extents.union(other.extents), @@ -62,9 +65,11 @@ impl Region { if self.extents.is_empty() || other.extents.is_empty() { return self.clone(); } - let rects = op::(&self.rects, &other.rects); + let rects = subtract(&self.rects, &other.rects); Rc::new(Self { - extents: extents(&rects), + extents: Rect { + raw: extents(&rects), + }, rects, }) } @@ -79,460 +84,10 @@ impl Deref for Region { type Target = [Rect]; fn deref(&self) -> &Self::Target { - &self.rects + unsafe { mem::transmute::<&[RectRaw], _>(&self.rects) } } } -struct Bands<'a> { - rects: &'a [Rect], -} - -#[derive(Copy, Clone)] -struct Band<'a> { - rects: &'a [Rect], - y1: i32, - y2: i32, -} - -impl<'a> Band<'a> { - fn can_merge_with(&self, next: &Band) -> bool { - next.rects.len() == self.rects.len() - && next.y1 == self.y2 - && next - .rects - .iter() - .zip(self.rects.iter()) - .all(|(a, b)| (a.x1, a.x2) == (b.x1, b.x2)) - } -} - -impl<'a> Iterator for Bands<'a> { - type Item = Band<'a>; - - fn next(&mut self) -> Option { - if self.rects.is_empty() { - return None; - } - let y1 = self.rects[0].y1(); - let y2 = self.rects[0].y2(); - for (pos, rect) in self.rects[1..].iter().enumerate() { - if rect.y1() != y1 { - let (res, rects) = self.rects.split_at(pos + 1); - self.rects = rects; - return Some(Band { rects: res, y1, y2 }); - } - } - Some(Band { - rects: mem::replace(&mut self.rects, &[]), - y1, - y2, - }) - } -} - -fn extents(a: &[Rect]) -> Rect { - let mut a = a.iter(); - let mut res = match a.next() { - Some(a) => *a, - _ => return Rect::default(), - }; - for a in a { - res.x1 = res.x1.min(a.x1); - res.y1 = res.y1.min(a.y1); - res.x2 = res.x2.max(a.x2); - res.y2 = res.y2.max(a.y2); - } - res -} - -fn op(a: &[Rect], b: &[Rect]) -> Container { - let mut res = Container::new(); - - let mut prev_band_y2 = 0; - let mut prev_band_start = 0; - let mut cur_band_start; - - let mut a_bands = Bands { rects: a }; - let mut b_bands = Bands { rects: b }; - - let mut a_opt = a_bands.next(); - let mut b_opt = b_bands.next(); - - macro_rules! fixup_new_band { - ($y1:expr, $y2:expr) => {{ - if prev_band_y2 != $y1 || !coalesce(&mut res, prev_band_start, cur_band_start, $y2) { - prev_band_start = cur_band_start; - } - prev_band_y2 = $y2; - }}; - } - - macro_rules! append_nonoverlapping { - ($append_opt:expr, $a:expr, $a_opt:expr, $a_bands:expr, $b:expr) => {{ - if $append_opt { - let y2 = $a.y2.min($b.y1); - cur_band_start = res.len(); - res.reserve($a.rects.len()); - for rect in $a.rects { - res.push(Rect { - x1: rect.x1, - y1: $a.y1, - x2: rect.x2, - y2, - }); - } - fixup_new_band!($a.y1, y2); - } - if $a.y2 <= $b.y1 { - $a_opt = $a_bands.next(); - } else { - $a.y1 = $b.y1; - } - }}; - } - - while let (Some(a), Some(b)) = (&mut a_opt, &mut b_opt) { - if a.y1 < b.y1 { - append_nonoverlapping!(O::APPEND_NON_A, a, a_opt, a_bands, b); - } else if b.y1 < a.y1 { - append_nonoverlapping!(O::APPEND_NON_B, b, b_opt, b_bands, a); - } else { - let y2 = a.y2.min(b.y2); - cur_band_start = res.len(); - O::handle_band(&mut res, a.rects, b.rects, a.y1, y2); - if res.len() > cur_band_start { - fixup_new_band!(a.y1, y2); - } - if a.y2 == y2 { - a_opt = a_bands.next(); - } else { - a.y1 = y2; - } - if b.y2 == y2 { - b_opt = b_bands.next(); - } else { - b.y1 = y2; - } - } - } - - macro_rules! push_trailing { - ($a_opt:expr, $a_bands:expr) => {{ - while let Some(a) = $a_opt { - cur_band_start = res.len(); - res.reserve(a.rects.len()); - for rect in a.rects { - res.push(Rect { - x1: rect.x1, - y1: a.y1, - x2: rect.x2, - y2: a.y2, - }); - } - fixup_new_band!(a.y1, a.y2); - $a_opt = $a_bands.next(); - } - }}; - } - - if O::APPEND_NON_A { - push_trailing!(a_opt, a_bands); - } - - if O::APPEND_NON_B { - push_trailing!(b_opt, b_bands); - } - - res.shrink_to_fit(); - res -} - -fn coalesce(new: &mut Container, a: usize, b: usize, y2: i32) -> bool { - if new.len() - b != b - a { - return false; - } - let slice_a = &new[a..b]; - let slice_b = &new[b..]; - for (a, b) in slice_a.iter().zip(slice_b.iter()) { - if (a.x1(), a.x2()) != (b.x1(), b.x2()) { - return false; - } - } - for rect in &mut new[a..b] { - rect.y2 = y2; - } - new.truncate(b); - true -} - -trait Op { - const APPEND_NON_A: bool; - const APPEND_NON_B: bool; - - fn handle_band(new: &mut Container, a: &[Rect], b: &[Rect], y1: i32, y2: i32); -} - -struct Union; - -impl Op for Union { - const APPEND_NON_A: bool = true; - const APPEND_NON_B: bool = true; - - fn handle_band(new: &mut Container, mut a: &[Rect], mut b: &[Rect], y1: i32, y2: i32) { - let mut x1; - let mut x2; - - macro_rules! push { - () => { - new.push(Rect { x1, y1, x2, y2 }); - }; - } - - macro_rules! merge { - ($r:expr) => { - if $r.x1 <= x2 { - if $r.x2 > x2 { - x2 = $r.x2; - } - } else { - push!(); - x1 = $r.x1; - x2 = $r.x2; - } - }; - } - - if a[0].x1 < b[0].x1 { - x1 = a[0].x1; - x2 = a[0].x2; - a = &a[1..]; - } else { - x1 = b[0].x1; - x2 = b[0].x2; - b = &b[1..]; - } - - let mut a_iter = a.iter(); - let mut b_iter = b.iter(); - - let mut a_opt = a_iter.next(); - let mut b_opt = b_iter.next(); - - while let (Some(a), Some(b)) = (a_opt, b_opt) { - if a.x1 < b.x1 { - merge!(a); - a_opt = a_iter.next(); - } else { - merge!(b); - b_opt = b_iter.next(); - } - } - - while let Some(a) = a_opt { - merge!(a); - a_opt = a_iter.next(); - } - - while let Some(b) = b_opt { - merge!(b); - b_opt = b_iter.next(); - } - - push!(); - } -} - -struct Subtract; - -impl Op for Subtract { - const APPEND_NON_A: bool = true; - const APPEND_NON_B: bool = false; - - fn handle_band(new: &mut Container, a: &[Rect], b: &[Rect], y1: i32, y2: i32) { - let mut x1; - let mut x2; - - macro_rules! push { - ($x2:expr) => { - new.push(Rect { - x1, - y1, - x2: $x2, - y2, - }); - }; - } - - let mut a_iter = a.iter(); - let mut b_iter = b.iter(); - - macro_rules! pull { - () => { - match a_iter.next() { - Some(n) => { - x1 = n.x1; - x2 = n.x2; - } - _ => return, - } - }; - } - - pull!(); - - let mut b_opt = b_iter.next(); - - while let Some(b) = b_opt { - if b.x2 <= x1 { - b_opt = b_iter.next(); - } else if b.x1 >= x2 { - push!(x2); - pull!(); - } else { - if b.x1 > x1 { - push!(b.x1); - } - if b.x2 < x2 { - x1 = b.x2; - } else { - pull!(); - } - } - } - - loop { - push!(x2); - pull!(); - } - } -} - -fn rects_to_bands(rects_tmp: &[Rect]) -> Container { - #[derive(Copy, Clone)] - struct W(Rect); - impl Eq for W {} - impl PartialEq for W { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl PartialOrd for W { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - impl Ord for W { - fn cmp(&self, other: &Self) -> Ordering { - self.0 - .y1 - .cmp(&other.0.y1) - .then_with(|| self.0.x1.cmp(&other.0.x1)) - .reverse() - } - } - impl Deref for W { - type Target = Rect; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - let ys = { - let mut tmp: Vec<_> = rects_tmp.iter().flat_map(|r| [r.y1, r.y2]).collect(); - tmp.sort_unstable(); - let mut last = None; - let mut res = vec![]; - for y in tmp { - if Some(y) != last { - last = Some(y); - res.push(y); - } - } - res - }; - - let mut rects = BinaryHeap::with_capacity(rects_tmp.len()); - for rect in rects_tmp.iter().copied() { - if !rect.is_empty() { - rects.push(W(rect)); - } - } - - let mut res = Container::new(); - - for &[y1, y2] in ys.array_windows_ext::<2>() { - loop { - macro_rules! check_rect { - ($rect:expr) => {{ - if $rect.y1 != y1 { - break; - } - rects.pop(); - if y2 < $rect.y2 { - $rect.0.y1 = y2; - rects.push($rect); - } - }}; - } - if let Some(mut rect) = rects.peek().copied() { - check_rect!(rect); - let mut x1 = rect.x1; - let mut x2 = rect.x2; - while let Some(mut rect) = rects.peek().copied() { - check_rect!(rect); - if rect.x1 > x2 { - res.push(Rect { x1, x2, y1, y2 }); - x1 = rect.x1; - x2 = rect.x2; - } else { - x2 = x2.max(rect.x2); - } - } - res.push(Rect { x1, x2, y1, y2 }); - } - break; - } - } - - let mut needs_merge = false; - let mut num_elements = res.len(); - let mut bands = Bands { rects: &res }.peekable(); - while let Some(band) = bands.next() { - let next = match bands.peek() { - Some(next) => next, - _ => break, - }; - if band.can_merge_with(next) { - needs_merge = true; - num_elements -= band.rects.len(); - } - } - - if !needs_merge { - res.shrink_to_fit(); - return res; - } - - let mut merged = Container::with_capacity(num_elements); - let mut bands = Bands { rects: &res }.peekable(); - while let Some(mut band) = bands.next() { - while let Some(next) = bands.peek() { - if band.can_merge_with(next) { - band.y2 = next.y2; - bands.next(); - } else { - break; - } - } - for mut rect in band.rects.iter().copied() { - rect.y2 = band.y2; - merged.push(rect); - } - } - - merged -} - #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum BuilderOp { Add, diff --git a/src/rect/tests.rs b/src/rect/tests.rs index 8965e2e9..9eb4dccf 100644 --- a/src/rect/tests.rs +++ b/src/rect/tests.rs @@ -1,4 +1,7 @@ -use crate::rect::{Rect, Region}; +use { + crate::rect::{Rect, Region}, + algorithms::rect::RectRaw, +}; #[test] fn union1() { @@ -10,10 +13,10 @@ fn union1() { assert_eq!( &r3.rects[..], &[ - Rect::new(0, 0, 10, 5).unwrap(), - Rect::new(0, 5, 15, 10).unwrap(), - Rect::new(5, 10, 20, 15).unwrap(), - Rect::new(10, 15, 20, 20).unwrap(), + Rect::new(0, 0, 10, 5).unwrap().raw, + Rect::new(0, 5, 15, 10).unwrap().raw, + Rect::new(5, 10, 20, 15).unwrap().raw, + Rect::new(10, 15, 20, 20).unwrap().raw, ] ); } @@ -24,7 +27,7 @@ fn union2() { let r2 = Region::new(Rect::new(0, 10, 10, 20).unwrap()); let r3 = r1.union(&r2); assert_eq!(r3.extents, Rect::new(0, 0, 10, 20).unwrap()); - assert_eq!(&r3.rects[..], &[Rect::new(0, 0, 10, 20).unwrap(),]); + assert_eq!(&r3.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]); } #[test] @@ -33,7 +36,35 @@ fn subtract1() { let r2 = Region::new(Rect::new(5, 5, 15, 15).unwrap()); let r3 = r1.subtract(&r2); assert_eq!(r3.extents, Rect::new(0, 0, 20, 20).unwrap()); - assert_eq!(&r3.rects[..], &[Rect::new(0, 0, 10, 20).unwrap(),]); + assert_eq!( + &r3.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 20, + y2: 5 + }, + RectRaw { + x1: 0, + y1: 5, + x2: 5, + y2: 15 + }, + RectRaw { + x1: 15, + y1: 5, + x2: 20, + y2: 15 + }, + RectRaw { + x1: 0, + y1: 15, + x2: 20, + y2: 20 + }, + ] + ); } #[test] @@ -44,8 +75,30 @@ fn rects_to_bands() { Rect::new_unchecked(30, 5, 50, 15), ]; let r = Region::from_rects(&rects[..]); - println!("{:#?}", r.rects); - assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap(),]); + // println!("{:#?}", r.rects); + assert_eq!( + &r.rects[..], + &[ + RectRaw { + x1: 0, + y1: 0, + x2: 30, + y2: 5 + }, + RectRaw { + x1: 0, + y1: 5, + x2: 50, + y2: 10 + }, + RectRaw { + x1: 30, + y1: 10, + x2: 50, + y2: 15 + }, + ] + ); } #[test] @@ -55,6 +108,6 @@ fn rects_to_bands2() { Rect::new_unchecked(0, 10, 10, 20), ]; let r = Region::from_rects(&rects[..]); - println!("{:#?}", r.rects); - assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap(),]); + // println!("{:#?}", r.rects); + assert_eq!(&r.rects[..], &[Rect::new(0, 0, 10, 20).unwrap().raw,]); }