#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#[auto_impl::auto_impl(&, &mut, Box, Rc, Arc)]
pub trait Image {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn has_pixel(&self, x: u32, y: u32) -> bool;
}
#[derive(PartialEq, Eq)]
enum Marker {
Start,
StartOfSegment,
EndOfSegment,
End,
}
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Pixel {
pub x: u32,
pub y: u32,
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum PS {
Complete,
Object,
Incomplete,
}
#[derive(PartialEq, Eq)]
enum CS {
NonObject,
Object,
}
struct Range {
start: u32,
end: u32,
}
impl From<u32> for Range {
fn from(start: u32) -> Self {
Self { start, end: start }
}
}
struct LutzObject<Pixels> {
range: Option<Range>,
pixels: Pixels,
}
struct LutzState<Img, Pixels: PixelFolder<Img>> {
img: Img,
co: genawaiter::rc::Co<Pixels>,
marker: Box<[Option<Marker>]>,
obj_stack: Vec<LutzObject<Pixels>>,
ps: PS,
cs: CS,
ps_stack: Vec<PS>,
store: Box<[Pixels]>,
}
pub trait PixelFolder<Img>: Default {
fn push(&mut self, pixel: Pixel, image: &Img);
fn merge(&mut self, other: Self);
}
impl<T: Default + Extend<Pixel> + IntoIterator<Item = Pixel>, Img> PixelFolder<Img> for T {
fn push(&mut self, item: Pixel, _image: &Img) {
self.extend(std::iter::once(item));
}
fn merge(&mut self, other: Self) {
self.extend(other);
}
}
impl<Img: Image, Pixels: PixelFolder<Img>> LutzState<Img, Pixels> {
fn new(img: Img, co: genawaiter::rc::Co<Pixels>) -> Self {
Self {
co,
marker: std::iter::repeat_with(|| None)
.take(img.width() as usize + 1)
.collect(),
obj_stack: Vec::new(),
ps: PS::Complete,
cs: CS::NonObject,
ps_stack: Vec::new(),
store: std::iter::repeat_with(Default::default)
.take(img.width() as usize + 1)
.collect(),
img,
}
}
async fn run(mut self) {
let width = self.img.width();
for y in 0..=self.img.height() {
self.ps = PS::Complete;
self.cs = CS::NonObject;
for x in 0..=width {
let newmarker = self.marker[x as usize].take();
if x != self.img.width() && y != self.img.height() && self.img.has_pixel(x, y) {
if self.cs != CS::Object {
self.start_segment(x);
}
if let Some(marker) = newmarker {
self.process_new_marker(marker, x).await;
}
self.obj_stack
.last_mut()
.unwrap()
.pixels
.push(Pixel { x, y }, &self.img);
} else {
if let Some(marker) = newmarker {
self.process_new_marker(marker, x).await;
}
if self.cs == CS::Object {
self.end_segment(x);
}
}
}
}
}
fn start_segment(&mut self, x: u32) {
self.cs = CS::Object;
self.marker[x as usize] = Some(if self.ps == PS::Object {
let range = &mut self.obj_stack.last_mut().unwrap().range;
if range.is_none() {
*range = Some(Range::from(x));
Marker::Start
} else {
Marker::StartOfSegment
}
} else {
self.ps_stack.push(self.ps);
self.ps = PS::Complete;
self.obj_stack.push(LutzObject {
range: Some(Range::from(x)),
pixels: Default::default(),
});
Marker::Start
});
}
fn end_segment(&mut self, x: u32) {
self.cs = CS::NonObject;
self.marker[x as usize] = Some(if self.ps != PS::Complete {
self.obj_stack
.last_mut()
.unwrap()
.range
.as_mut()
.unwrap()
.end = x;
Marker::EndOfSegment
} else {
self.ps = self.ps_stack.pop().unwrap();
let obj = self.obj_stack.pop().unwrap();
self.store[obj.range.unwrap().start as usize] = obj.pixels;
Marker::End
});
}
async fn process_new_marker(&mut self, newmarker: Marker, x: u32) {
self.ps = match newmarker {
Marker::Start => {
self.ps_stack.push(self.ps);
let store = std::mem::take(&mut self.store[x as usize]);
if self.cs == CS::NonObject {
self.ps_stack.push(PS::Complete);
self.obj_stack.push(LutzObject {
range: None,
pixels: store,
});
} else {
self.obj_stack.last_mut().unwrap().pixels.merge(store);
}
PS::Object
}
Marker::StartOfSegment => {
if self.cs == CS::Object && self.ps == PS::Complete {
self.ps_stack.pop();
let obj = self.obj_stack.pop().unwrap();
let new_top = self.obj_stack.last_mut().unwrap();
new_top.pixels.merge(obj.pixels);
let k = obj.range.unwrap().start;
if new_top.range.is_none() {
new_top.range = Some(Range::from(k));
} else {
self.marker[k as usize] = Some(Marker::StartOfSegment);
}
}
PS::Object
}
Marker::EndOfSegment => PS::Incomplete,
Marker::End => {
let ps = self.ps_stack.pop().unwrap();
if self.cs == CS::NonObject && ps == PS::Complete {
let obj = self.obj_stack.pop().unwrap();
match obj.range {
None => {
self.co.yield_(obj.pixels).await;
}
Some(range) => {
self.marker[range.end as usize] = Some(Marker::End);
self.store[range.start as usize] = obj.pixels;
}
}
self.ps_stack.pop().unwrap()
} else {
ps
}
}
}
}
}
pub fn lutz<Img: Image, Pixels: PixelFolder<Img>>(img: Img) -> impl Iterator<Item = Pixels> {
genawaiter::rc::Gen::new(move |co| LutzState::new(img, co).run()).into_iter()
}
#[test]
fn test_cshape() {
struct SampleImage {
img: image::GrayImage,
}
impl Image for SampleImage {
fn width(&self) -> u32 {
self.img.width()
}
fn height(&self) -> u32 {
self.img.height()
}
fn has_pixel(&self, x: u32, y: u32) -> bool {
self.img.get_pixel(x, y).0[0] > 20
}
}
let bytes = vec![
0, 99, 99, 99, 0, 0, 99, 0, 89, 0, 0, 0, 0, 99, 0, 0, 99, 0, 99, 0, 0, 99, 99, 99, 0, ];
let image = SampleImage {
img: image::GrayImage::from_vec(5, 5, bytes).unwrap(),
};
let blobs = lutz::<_, Vec<_>>(image).collect::<Vec<_>>();
assert_eq!(1, blobs.len(), "Expected 1 blob, got {blobs:#?}");
}