use crate::core::{Box, Pix, PixelDepth};
use crate::region::conncomp::{ConnectivityType, find_connected_components};
use crate::region::error::{RegionError, RegionResult};
use crate::region::seedfill::fill_holes;
use std::io::{Read, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Direction {
West = 0,
NorthWest = 1,
North = 2,
NorthEast = 3,
East = 4,
SouthEast = 5,
South = 6,
SouthWest = 7,
}
impl Direction {
#[inline]
pub fn dx(self) -> i32 {
XPOSTAB[self as usize]
}
#[inline]
pub fn dy(self) -> i32 {
YPOSTAB[self as usize]
}
pub fn from_offset(dx: i32, dy: i32) -> Option<Self> {
if dx.abs() > 1 || dy.abs() > 1 || (dx == 0 && dy == 0) {
return None;
}
let idx = DIRTAB[(1 + dy) as usize][(1 + dx) as usize];
if idx < 0 {
None
} else {
Some(Self::from_index(idx as usize))
}
}
#[inline]
fn from_index(idx: usize) -> Self {
match idx % 8 {
0 => Direction::West,
1 => Direction::NorthWest,
2 => Direction::North,
3 => Direction::NorthEast,
4 => Direction::East,
5 => Direction::SouthEast,
6 => Direction::South,
_ => Direction::SouthWest,
}
}
pub fn all() -> [Direction; 8] {
[
Direction::West,
Direction::NorthWest,
Direction::North,
Direction::NorthEast,
Direction::East,
Direction::SouthEast,
Direction::South,
Direction::SouthWest,
]
}
}
const XPOSTAB: [i32; 8] = [-1, -1, 0, 1, 1, 1, 0, -1];
const YPOSTAB: [i32; 8] = [0, -1, -1, -1, 0, 1, 1, 1];
const QPOSTAB: [usize; 8] = [6, 6, 0, 0, 2, 2, 4, 4];
const DIRTAB: [[i32; 3]; 3] = [[1, 2, 3], [0, -1, 4], [7, 6, 5]];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct BorderPoint {
pub x: i32,
pub y: i32,
}
impl BorderPoint {
#[inline]
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
#[inline]
pub fn moved(self, dir: Direction) -> Self {
Self {
x: self.x + dir.dx(),
y: self.y + dir.dy(),
}
}
#[inline]
pub fn offset(self, dx: i32, dy: i32) -> Self {
Self {
x: self.x + dx,
y: self.y + dy,
}
}
}
impl From<(i32, i32)> for BorderPoint {
fn from((x, y): (i32, i32)) -> Self {
Self::new(x, y)
}
}
impl From<(u32, u32)> for BorderPoint {
fn from((x, y): (u32, u32)) -> Self {
Self::new(x as i32, y as i32)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BorderType {
#[default]
Outer,
Hole,
}
#[derive(Debug, Clone, Default)]
pub struct Border {
pub border_type: BorderType,
pub start: BorderPoint,
pub points: Vec<BorderPoint>,
pub chain_code: Option<Vec<Direction>>,
}
impl Border {
pub fn new(border_type: BorderType, points: Vec<BorderPoint>) -> Self {
let start = points.first().copied().unwrap_or_default();
Self {
border_type,
start,
points,
chain_code: None,
}
}
pub fn empty(border_type: BorderType) -> Self {
Self {
border_type,
start: BorderPoint::default(),
points: Vec::new(),
chain_code: None,
}
}
#[inline]
pub fn len(&self) -> usize {
self.points.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn compute_chain_code(&mut self) {
self.chain_code = Some(to_chain_code(&self.points));
}
pub fn get_chain_code(&mut self) -> &[Direction] {
if self.chain_code.is_none() {
self.compute_chain_code();
}
self.chain_code.as_ref().unwrap()
}
pub fn to_global(&self, offset_x: i32, offset_y: i32) -> Border {
Border {
border_type: self.border_type,
start: self.start.offset(offset_x, offset_y),
points: self
.points
.iter()
.map(|p| p.offset(offset_x, offset_y))
.collect(),
chain_code: self.chain_code.clone(),
}
}
#[inline]
pub fn perimeter(&self) -> usize {
self.points.len()
}
pub fn bounding_box(&self) -> Option<Box> {
if self.points.is_empty() {
return None;
}
let mut min_x = i32::MAX;
let mut min_y = i32::MAX;
let mut max_x = i32::MIN;
let mut max_y = i32::MIN;
for p in &self.points {
min_x = min_x.min(p.x);
min_y = min_y.min(p.y);
max_x = max_x.max(p.x);
max_y = max_y.max(p.y);
}
Some(Box::new_unchecked(
min_x,
min_y,
max_x - min_x + 1,
max_y - min_y + 1,
))
}
}
#[derive(Debug, Clone)]
pub struct ComponentBorders {
pub bounds: Box,
pub outer: Border,
pub holes: Vec<Border>,
pub single_path: Option<Vec<BorderPoint>>,
pub global_outer: Option<Border>,
pub global_holes: Option<Vec<Border>>,
pub global_single_path: Option<Vec<BorderPoint>>,
}
impl ComponentBorders {
pub fn new(bounds: Box, outer: Border) -> Self {
Self {
bounds,
outer,
holes: Vec::new(),
single_path: None,
global_outer: None,
global_holes: None,
global_single_path: None,
}
}
pub fn border_count(&self) -> usize {
1 + self.holes.len()
}
pub fn has_holes(&self) -> bool {
!self.holes.is_empty()
}
pub fn outer_global(&self) -> Border {
self.outer.to_global(self.bounds.x, self.bounds.y)
}
pub fn holes_global(&self) -> Vec<Border> {
self.holes
.iter()
.map(|h| h.to_global(self.bounds.x, self.bounds.y))
.collect()
}
pub fn total_perimeter(&self) -> usize {
self.outer.perimeter() + self.holes.iter().map(|h| h.perimeter()).sum::<usize>()
}
}
#[derive(Debug, Clone)]
pub struct ImageBorders {
pub width: u32,
pub height: u32,
pub components: Vec<ComponentBorders>,
}
impl ImageBorders {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
components: Vec::new(),
}
}
pub fn component_count(&self) -> usize {
self.components.len()
}
pub fn total_border_count(&self) -> usize {
self.components.iter().map(|c| c.border_count()).sum()
}
pub fn has_holes(&self) -> bool {
self.components.iter().any(|c| c.has_holes())
}
pub fn generate_global_locs(&mut self) {
for comp in &mut self.components {
comp.global_outer = Some(comp.outer_global());
comp.global_holes = Some(comp.holes_global());
}
}
pub fn generate_sp_global_locs(&mut self) {
for comp in &mut self.components {
if let Some(ref single) = comp.single_path {
comp.global_single_path = Some(
single
.iter()
.map(|p| BorderPoint::new(p.x + comp.bounds.x, p.y + comp.bounds.y))
.collect(),
);
}
}
}
}
impl ImageBorders {
pub fn generate_step_chains(&mut self) {
for comp in &mut self.components {
comp.outer.compute_chain_code();
for hole in &mut comp.holes {
hole.compute_chain_code();
}
}
}
pub fn step_chains_to_pix_coords(&mut self) -> RegionResult<()> {
for comp in &mut self.components {
if let Some(code) = &comp.outer.chain_code {
let start = comp.outer.start;
let restored = from_chain_code(start, code);
comp.outer.points = restored;
}
for hole in &mut comp.holes {
if let Some(code) = &hole.chain_code {
let start = hole.start;
let restored = from_chain_code(start, code);
hole.points = restored;
}
}
}
Ok(())
}
pub fn generate_single_path(&mut self) -> RegionResult<()> {
for comp in &mut self.components {
let mut path = Vec::new();
if comp.holes.is_empty() {
path = comp.outer.points.clone();
} else {
path.extend(&comp.outer.points);
for hole in &comp.holes {
if !hole.points.is_empty() {
let cut_point = path.last().copied().unwrap_or(comp.outer.points[0]);
path.push(hole.points[0]);
path.extend(&hole.points);
path.push(hole.points[0]);
path.push(cut_point);
}
}
}
comp.single_path = Some(path);
}
Ok(())
}
pub fn write<W: Write>(&self, mut writer: W) -> RegionResult<()> {
writer
.write_all(b"ccba")
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let width_bytes = self.width.to_le_bytes();
let height_bytes = self.height.to_le_bytes();
writer
.write_all(&width_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
writer
.write_all(&height_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let ncc = (self.components.len() as u32).to_le_bytes();
writer
.write_all(&ncc)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
for comp in &self.components {
let bx = comp.bounds.x.to_le_bytes();
let by = comp.bounds.y.to_le_bytes();
let bw = comp.bounds.w.to_le_bytes();
let bh = comp.bounds.h.to_le_bytes();
writer
.write_all(&bx)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
writer
.write_all(&by)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
writer
.write_all(&bw)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
writer
.write_all(&bh)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let nb: u32 = comp.border_count().try_into().map_err(|_| {
RegionError::InvalidParameters(format!(
"component has too many borders to serialize: {}",
comp.border_count()
))
})?;
writer
.write_all(&nb.to_le_bytes())
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
write_border(&mut writer, &comp.outer)?;
for hole in &comp.holes {
write_border(&mut writer, hole)?;
}
}
Ok(())
}
pub fn read_from<R: Read>(mut reader: R) -> RegionResult<Self> {
let mut magic = [0u8; 4];
reader
.read_exact(&mut magic)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
if &magic != b"ccba" {
return Err(RegionError::InvalidParameters(
"invalid ccba magic header".to_string(),
));
}
let mut width_bytes = [0u8; 4];
let mut height_bytes = [0u8; 4];
let mut ncc_bytes = [0u8; 4];
reader
.read_exact(&mut width_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut height_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut ncc_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let width = u32::from_le_bytes(width_bytes);
let height = u32::from_le_bytes(height_bytes);
let ncc = u32::from_le_bytes(ncc_bytes) as usize;
let mut image_borders = ImageBorders::new(width, height);
for _ in 0..ncc {
let mut bx_bytes = [0u8; 4];
let mut by_bytes = [0u8; 4];
let mut bw_bytes = [0u8; 4];
let mut bh_bytes = [0u8; 4];
let mut nb_bytes = [0u8; 4];
reader
.read_exact(&mut bx_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut by_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut bw_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut bh_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut nb_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let bx = i32::from_le_bytes(bx_bytes);
let by = i32::from_le_bytes(by_bytes);
let bw = i32::from_le_bytes(bw_bytes);
let bh = i32::from_le_bytes(bh_bytes);
let bounds = Box::new_unchecked(bx, by, bw, bh);
let nb = u32::from_le_bytes(nb_bytes) as usize;
if nb == 0 {
continue;
}
let outer = read_border(&mut reader)?;
let mut comp = ComponentBorders::new(bounds, outer);
for _ in 1..nb {
let hole = read_border(&mut reader)?;
comp.holes.push(hole);
}
image_borders.components.push(comp);
}
Ok(image_borders)
}
pub fn to_svg_string(&self) -> RegionResult<String> {
let mut svg = String::new();
svg.push_str("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n");
svg.push_str(&format!(
"<svg width=\"{}\" height=\"{}\" viewBox=\"0 0 {} {}\">\n",
self.width, self.height, self.width, self.height
));
for comp in &self.components {
let global_points: Vec<BorderPoint>;
let points: &[BorderPoint] = if let Some(ref single) = comp.single_path {
global_points = single
.iter()
.map(|p| BorderPoint {
x: p.x + comp.bounds.x,
y: p.y + comp.bounds.y,
})
.collect();
&global_points
} else {
global_points = comp.outer_global().points;
&global_points
};
if !points.is_empty() {
svg.push_str(" <polygon style=\"stroke-width:1;stroke:black;\" points=\"");
use std::fmt::Write as FmtWrite;
for (i, p) in points.iter().enumerate() {
if i > 0 {
svg.push(' ');
}
let _ = write!(svg, "{},{}", p.x, p.y);
}
svg.push_str("\" />\n");
}
}
svg.push_str("</svg>\n");
Ok(svg)
}
pub fn write_svg<W: Write>(&self, mut writer: W) -> RegionResult<()> {
let svg = self.to_svg_string()?;
writer
.write_all(svg.as_bytes())
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
Ok(())
}
pub fn write_to_file<P: AsRef<std::path::Path>>(&self, path: P) -> RegionResult<()> {
let file = std::fs::File::create(path)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let writer = std::io::BufWriter::new(file);
self.write(writer)
}
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> RegionResult<Self> {
let file =
std::fs::File::open(path).map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let reader = std::io::BufReader::new(file);
Self::read_from(reader)
}
pub fn write_svg_to_file<P: AsRef<std::path::Path>>(&self, path: P) -> RegionResult<()> {
let file = std::fs::File::create(path)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let writer = std::io::BufWriter::new(file);
self.write_svg(writer)
}
}
fn write_border<W: Write>(writer: &mut W, border: &Border) -> RegionResult<()> {
let border_type = match border.border_type {
BorderType::Outer => 0u8,
BorderType::Hole => 1u8,
};
writer
.write_all(&[border_type])
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let sx = border.start.x.to_le_bytes();
let sy = border.start.y.to_le_bytes();
writer
.write_all(&sx)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
writer
.write_all(&sy)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let np: u32 = border.points.len().try_into().map_err(|_| {
RegionError::InvalidParameters(format!(
"border has too many points to serialize: {}",
border.points.len()
))
})?;
writer
.write_all(&np.to_le_bytes())
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
for p in &border.points {
let px = p.x.to_le_bytes();
let py = p.y.to_le_bytes();
writer
.write_all(&px)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
writer
.write_all(&py)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
}
Ok(())
}
fn read_border<R: Read>(reader: &mut R) -> RegionResult<Border> {
let mut type_byte = [0u8; 1];
let mut sx_bytes = [0u8; 4];
let mut sy_bytes = [0u8; 4];
let mut np_bytes = [0u8; 4];
reader
.read_exact(&mut type_byte)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut sx_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut sy_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut np_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let border_type = match type_byte[0] {
0 => BorderType::Outer,
1 => BorderType::Hole,
b => {
return Err(RegionError::InvalidParameters(format!(
"invalid border type byte: {b}"
)));
}
};
let sx = i32::from_le_bytes(sx_bytes);
let sy = i32::from_le_bytes(sy_bytes);
let np = u32::from_le_bytes(np_bytes) as usize;
let start = BorderPoint::new(sx, sy);
let mut points = Vec::with_capacity(np);
for _ in 0..np {
let mut px_bytes = [0u8; 4];
let mut py_bytes = [0u8; 4];
reader
.read_exact(&mut px_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
reader
.read_exact(&mut py_bytes)
.map_err(|e| RegionError::InvalidParameters(e.to_string()))?;
let px = i32::from_le_bytes(px_bytes);
let py = i32::from_le_bytes(py_bytes);
points.push(BorderPoint::new(px, py));
}
let mut border = Border::new(border_type, points);
border.start = start;
Ok(border)
}
fn find_next_border_pixel(
pix: &Pix,
width: u32,
height: u32,
px: i32,
py: i32,
qpos: usize,
) -> Option<(BorderPoint, usize)> {
for i in 1..8 {
let pos = (qpos + i) % 8;
let npx = px + XPOSTAB[pos];
let npy = py + YPOSTAB[pos];
if npx < 0 || npx >= width as i32 || npy < 0 || npy >= height as i32 {
continue;
}
if let Some(val) = pix.get_pixel(npx as u32, npy as u32)
&& val != 0
{
return Some((BorderPoint::new(npx, npy), QPOSTAB[pos]));
}
}
None
}
fn find_first_on_pixel(pix: &Pix) -> Option<BorderPoint> {
let width = pix.width();
let height = pix.height();
for y in 0..height {
for x in 0..width {
if let Some(val) = pix.get_pixel(x, y)
&& val != 0
{
return Some(BorderPoint::new(x as i32, y as i32));
}
}
}
None
}
pub fn get_outer_border(pix: &Pix, bounds: Option<&Box>) -> RegionResult<Border> {
if pix.depth() != PixelDepth::Bit1 {
return Err(RegionError::UnsupportedDepth {
expected: "1-bit",
actual: pix.depth().bits(),
});
}
let width = pix.width();
let height = pix.height();
if width == 0 || height == 0 {
return Err(RegionError::EmptyImage);
}
let bordered = add_border(pix, 1)?;
let bwidth = bordered.width();
let bheight = bordered.height();
let start = match find_first_on_pixel(&bordered) {
Some(p) => p,
None => return Err(RegionError::EmptyImage),
};
let mut points = Vec::new();
points.push(BorderPoint::new(start.x - 1, start.y - 1));
let (fpx, fpy) = (start.x, start.y);
let mut qpos = 0usize;
let second = match find_next_border_pixel(&bordered, bwidth, bheight, fpx, fpy, qpos) {
Some((p, q)) => {
qpos = q;
p
}
None => {
let mut border = Border::new(BorderType::Outer, points);
if let Some(b) = bounds {
border = border.to_global(b.x, b.y);
}
return Ok(border);
}
};
let (spx, spy) = (second.x, second.y);
points.push(BorderPoint::new(spx - 1, spy - 1));
let (mut px, mut py) = (spx, spy);
while let Some((next, new_qpos)) =
find_next_border_pixel(&bordered, bwidth, bheight, px, py, qpos)
{
if px == fpx && py == fpy && next.x == spx && next.y == spy {
break;
}
points.push(BorderPoint::new(next.x - 1, next.y - 1));
px = next.x;
py = next.y;
qpos = new_qpos;
}
let border = Border::new(BorderType::Outer, points);
if let Some(b) = bounds {
Ok(border.to_global(b.x, b.y))
} else {
Ok(border)
}
}
fn add_border(pix: &Pix, border_size: u32) -> RegionResult<Pix> {
let width = pix.width();
let height = pix.height();
let new_width = width + 2 * border_size;
let new_height = height + 2 * border_size;
let mut output = Pix::new(new_width, new_height, pix.depth())
.map_err(RegionError::Core)?
.try_into_mut()
.unwrap_or_else(|p| p.to_mut());
for y in 0..height {
for x in 0..width {
if let Some(val) = pix.get_pixel(x, y) {
let _ = output.set_pixel(x + border_size, y + border_size, val);
}
}
}
Ok(output.into())
}
pub fn get_outer_borders(pix: &Pix) -> RegionResult<Vec<Border>> {
if pix.depth() != PixelDepth::Bit1 {
return Err(RegionError::UnsupportedDepth {
expected: "1-bit",
actual: pix.depth().bits(),
});
}
let width = pix.width();
let height = pix.height();
if width == 0 || height == 0 {
return Ok(Vec::new());
}
let components = find_connected_components(pix, ConnectivityType::EightWay)?;
if components.is_empty() {
return Ok(Vec::new());
}
let mut borders = Vec::with_capacity(components.len());
for comp in &components {
let comp_pix = extract_component_image(pix, &comp.bounds)?;
match get_outer_border(&comp_pix, Some(&comp.bounds)) {
Ok(border) => borders.push(border),
Err(RegionError::EmptyImage) => continue,
Err(e) => return Err(e),
}
}
Ok(borders)
}
fn extract_component_image(pix: &Pix, bounds: &Box) -> RegionResult<Pix> {
let mut output = Pix::new(bounds.w as u32, bounds.h as u32, pix.depth())
.map_err(RegionError::Core)?
.try_into_mut()
.unwrap_or_else(|p| p.to_mut());
for y in 0..bounds.h {
for x in 0..bounds.w {
let src_x = (bounds.x + x) as u32;
let src_y = (bounds.y + y) as u32;
if let Some(val) = pix.get_pixel(src_x, src_y) {
let _ = output.set_pixel(x as u32, y as u32, val);
}
}
}
Ok(output.into())
}
pub fn get_component_borders(pix: &Pix, bounds: Box) -> RegionResult<ComponentBorders> {
if pix.depth() != PixelDepth::Bit1 {
return Err(RegionError::UnsupportedDepth {
expected: "1-bit",
actual: pix.depth().bits(),
});
}
let outer = get_outer_border(pix, None)?;
let filled = fill_holes(pix, ConnectivityType::FourWay)?;
let width = pix.width();
let height = pix.height();
let mut holes_pix = Pix::new(width, height, PixelDepth::Bit1)
.map_err(RegionError::Core)?
.try_into_mut()
.unwrap_or_else(|p| p.to_mut());
let mut has_holes = false;
for y in 0..height {
for x in 0..width {
let orig = pix.get_pixel(x, y).unwrap_or(0);
let fill = filled.get_pixel(x, y).unwrap_or(0);
let hole_pixel = fill ^ orig;
if hole_pixel != 0 {
has_holes = true;
let _ = holes_pix.set_pixel(x, y, 1);
}
}
}
let mut component_borders = ComponentBorders::new(bounds, outer);
if !has_holes {
return Ok(component_borders);
}
let holes_pix: Pix = holes_pix.into();
let hole_components = find_connected_components(&holes_pix, ConnectivityType::FourWay)?;
for hole_comp in &hole_components {
if let Some(start) = locate_outside_seed_pixel(pix, &hole_comp.bounds) {
match trace_hole_border(pix, start, &hole_comp.bounds) {
Ok(border) => component_borders.holes.push(border),
Err(_) => continue,
}
}
}
Ok(component_borders)
}
pub fn locate_outside_seed_pixel(pix: &Pix, hole_bounds: &Box) -> Option<BorderPoint> {
let width = pix.width() as i32;
let ys = hole_bounds.y;
for x in hole_bounds.x..width {
if let Some(val) = pix.get_pixel(x as u32, ys as u32)
&& val != 0
{
return Some(BorderPoint::new(x, ys));
}
}
None
}
pub fn pix_get_hole_border(pix: &Pix, hole_bounds: &Box) -> RegionResult<Border> {
let start = locate_outside_seed_pixel(pix, hole_bounds).ok_or_else(|| {
RegionError::InvalidParameters("no seed pixel found for hole border".to_string())
})?;
trace_hole_border(pix, start, hole_bounds)
}
fn trace_hole_border(pix: &Pix, start: BorderPoint, _hole_bounds: &Box) -> RegionResult<Border> {
let width = pix.width();
let height = pix.height();
let mut points = Vec::new();
points.push(start);
let (fpx, fpy) = (start.x, start.y);
let mut qpos = 0usize;
let second = match find_next_border_pixel(pix, width, height, fpx, fpy, qpos) {
Some((p, q)) => {
qpos = q;
p
}
None => {
return Err(RegionError::InvalidParameters(
"hole has only single pixel".to_string(),
));
}
};
let (spx, spy) = (second.x, second.y);
points.push(second);
let (mut px, mut py) = (spx, spy);
while let Some((next, new_qpos)) = find_next_border_pixel(pix, width, height, px, py, qpos) {
if px == fpx && py == fpy && next.x == spx && next.y == spy {
break;
}
points.push(next);
px = next.x;
py = next.y;
qpos = new_qpos;
}
Ok(Border::new(BorderType::Hole, points))
}
pub fn get_cut_path_for_hole(outer: &Border, hole: &Border, _bounds: &Box) -> Vec<BorderPoint> {
if outer.is_empty() || hole.is_empty() {
return Vec::new();
}
let mut min_dist = i64::MAX;
let mut best_outer_pt = outer.points[0];
let mut best_hole_pt = hole.points[0];
for op in &outer.points {
for hp in &hole.points {
let dx = (op.x - hp.x) as i64;
let dy = (op.y - hp.y) as i64;
let dist = dx * dx + dy * dy;
if dist < min_dist {
min_dist = dist;
best_outer_pt = *op;
best_hole_pt = *hp;
}
}
}
let mut path = Vec::new();
let mut x = best_outer_pt.x;
let mut y = best_outer_pt.y;
let tx = best_hole_pt.x;
let ty = best_hole_pt.y;
path.push(BorderPoint::new(x, y));
while x != tx || y != ty {
if x < tx {
x += 1;
} else if x > tx {
x -= 1;
}
if y < ty {
y += 1;
} else if y > ty {
y -= 1;
}
path.push(BorderPoint::new(x, y));
}
path
}
pub fn get_all_borders(pix: &Pix) -> RegionResult<ImageBorders> {
if pix.depth() != PixelDepth::Bit1 {
return Err(RegionError::UnsupportedDepth {
expected: "1-bit",
actual: pix.depth().bits(),
});
}
let width = pix.width();
let height = pix.height();
let mut image_borders = ImageBorders::new(width, height);
if width == 0 || height == 0 {
return Ok(image_borders);
}
let components = find_connected_components(pix, ConnectivityType::EightWay)?;
for comp in &components {
let comp_pix = extract_component_image(pix, &comp.bounds)?;
match get_component_borders(&comp_pix, comp.bounds) {
Ok(borders) => image_borders.components.push(borders),
Err(RegionError::EmptyImage) => continue,
Err(e) => return Err(e),
}
}
Ok(image_borders)
}
pub fn to_chain_code(points: &[BorderPoint]) -> Vec<Direction> {
if points.len() < 2 {
return Vec::new();
}
let mut chain = Vec::with_capacity(points.len() - 1);
for i in 0..points.len() - 1 {
let p1 = points[i];
let p2 = points[i + 1];
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
if let Some(dir) = Direction::from_offset(dx, dy) {
chain.push(dir);
}
}
chain
}
pub fn from_chain_code(start: BorderPoint, chain: &[Direction]) -> Vec<BorderPoint> {
let mut points = Vec::with_capacity(chain.len() + 1);
points.push(start);
let mut current = start;
for &dir in chain {
current = current.moved(dir);
points.push(current);
}
points
}
pub fn render_borders(borders: &ImageBorders) -> RegionResult<Pix> {
let mut output = Pix::new(borders.width, borders.height, PixelDepth::Bit1)
.map_err(RegionError::Core)?
.try_into_mut()
.unwrap_or_else(|p| p.to_mut());
for comp in &borders.components {
let outer_global = comp.outer_global();
for p in &outer_global.points {
if p.x >= 0 && p.y >= 0 && (p.x as u32) < borders.width && (p.y as u32) < borders.height
{
let _ = output.set_pixel(p.x as u32, p.y as u32, 1);
}
}
for hole in comp.holes_global() {
for p in &hole.points {
if p.x >= 0
&& p.y >= 0
&& (p.x as u32) < borders.width
&& (p.y as u32) < borders.height
{
let _ = output.set_pixel(p.x as u32, p.y as u32, 1);
}
}
}
}
Ok(output.into())
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_image(width: u32, height: u32, pixels: &[(u32, u32)]) -> Pix {
let pix = Pix::new(width, height, PixelDepth::Bit1).unwrap();
let mut pix_mut = pix.try_into_mut().unwrap();
for &(x, y) in pixels {
let _ = pix_mut.set_pixel(x, y, 1);
}
pix_mut.into()
}
#[test]
fn test_direction_offsets() {
assert_eq!(Direction::West.dx(), -1);
assert_eq!(Direction::West.dy(), 0);
assert_eq!(Direction::East.dx(), 1);
assert_eq!(Direction::East.dy(), 0);
assert_eq!(Direction::North.dy(), -1);
assert_eq!(Direction::South.dy(), 1);
}
#[test]
fn test_direction_from_offset() {
assert_eq!(Direction::from_offset(-1, 0), Some(Direction::West));
assert_eq!(Direction::from_offset(1, 0), Some(Direction::East));
assert_eq!(Direction::from_offset(0, -1), Some(Direction::North));
assert_eq!(Direction::from_offset(0, 1), Some(Direction::South));
assert_eq!(Direction::from_offset(1, 1), Some(Direction::SouthEast));
assert_eq!(Direction::from_offset(0, 0), None);
assert_eq!(Direction::from_offset(2, 0), None);
}
#[test]
fn test_border_point_moved() {
let p = BorderPoint::new(5, 5);
assert_eq!(p.moved(Direction::East), BorderPoint::new(6, 5));
assert_eq!(p.moved(Direction::South), BorderPoint::new(5, 6));
assert_eq!(p.moved(Direction::NorthWest), BorderPoint::new(4, 4));
}
#[test]
fn test_single_pixel_border() {
let pix = create_test_image(5, 5, &[(2, 2)]);
let border = get_outer_border(&pix, None).unwrap();
assert_eq!(border.len(), 1);
assert_eq!(border.points[0], BorderPoint::new(2, 2));
assert_eq!(border.border_type, BorderType::Outer);
}
#[test]
fn test_horizontal_line_border() {
let pix = create_test_image(10, 5, &[(2, 2), (3, 2), (4, 2)]);
let border = get_outer_border(&pix, None).unwrap();
assert!(!border.is_empty());
assert!(border.len() >= 3);
}
#[test]
fn test_square_border() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let border = get_outer_border(&pix, None).unwrap();
assert!(!border.is_empty());
assert!(
border.len() >= 8 && border.len() <= 12,
"got {}",
border.len()
);
}
#[test]
fn test_outer_borders_multiple() {
let mut pixels = Vec::new();
for y in 1..3 {
for x in 1..3 {
pixels.push((x, y));
}
}
for y in 1..3 {
for x in 5..7 {
pixels.push((x, y));
}
}
let pix = create_test_image(10, 5, &pixels);
let borders = get_outer_borders(&pix).unwrap();
assert_eq!(borders.len(), 2);
}
#[test]
fn test_chain_code_conversion() {
let points = vec![
BorderPoint::new(0, 0),
BorderPoint::new(1, 0),
BorderPoint::new(2, 0),
BorderPoint::new(2, 1),
];
let chain = to_chain_code(&points);
assert_eq!(chain.len(), 3);
assert_eq!(chain[0], Direction::East);
assert_eq!(chain[1], Direction::East);
assert_eq!(chain[2], Direction::South);
let restored = from_chain_code(points[0], &chain);
assert_eq!(restored, points);
}
#[test]
fn test_chain_code_empty() {
let chain = to_chain_code(&[]);
assert!(chain.is_empty());
let chain = to_chain_code(&[BorderPoint::new(0, 0)]);
assert!(chain.is_empty());
}
#[test]
fn test_border_bounding_box() {
let border = Border::new(
BorderType::Outer,
vec![
BorderPoint::new(1, 1),
BorderPoint::new(3, 1),
BorderPoint::new(3, 4),
BorderPoint::new(1, 4),
],
);
let bbox = border.bounding_box().unwrap();
assert_eq!(bbox.x, 1);
assert_eq!(bbox.y, 1);
assert_eq!(bbox.w, 3);
assert_eq!(bbox.h, 4);
}
#[test]
fn test_border_to_global() {
let border = Border::new(
BorderType::Outer,
vec![BorderPoint::new(0, 0), BorderPoint::new(1, 0)],
);
let global = border.to_global(10, 20);
assert_eq!(global.points[0], BorderPoint::new(10, 20));
assert_eq!(global.points[1], BorderPoint::new(11, 20));
}
#[test]
fn test_empty_image() {
let pix = create_test_image(5, 5, &[]);
let result = get_outer_border(&pix, None);
assert!(matches!(result, Err(RegionError::EmptyImage)));
}
#[test]
fn test_unsupported_depth() {
let pix = Pix::new(5, 5, PixelDepth::Bit8).unwrap();
let result = get_outer_border(&pix, None);
assert!(matches!(result, Err(RegionError::UnsupportedDepth { .. })));
}
#[test]
fn test_get_all_borders() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let borders = get_all_borders(&pix).unwrap();
assert_eq!(borders.component_count(), 1);
assert!(!borders.components[0].outer.is_empty());
}
#[test]
fn test_component_with_hole() {
let mut pixels = Vec::new();
for y in 0..5 {
for x in 0..5 {
if y == 0 || y == 4 || x == 0 || x == 4 {
pixels.push((x, y));
}
}
}
let pix = create_test_image(5, 5, &pixels);
let borders = get_all_borders(&pix).unwrap();
assert_eq!(borders.component_count(), 1);
let comp = &borders.components[0];
assert!(!comp.outer.is_empty());
assert!(comp.has_holes());
}
#[test]
fn test_generate_step_chains() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_step_chains();
let comp = &borders.components[0];
assert!(comp.outer.chain_code.is_some());
let chain = comp.outer.chain_code.as_ref().unwrap();
assert!(!chain.is_empty());
}
#[test]
fn test_step_chains_to_pix_coords_roundtrip() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
let original_points = borders.components[0].outer.points.clone();
borders.generate_step_chains();
borders.step_chains_to_pix_coords().unwrap();
assert_eq!(borders.components[0].outer.points, original_points);
}
#[test]
fn test_generate_single_path_no_holes() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_single_path().unwrap();
let comp = &borders.components[0];
assert!(comp.single_path.is_some());
let path = comp.single_path.as_ref().unwrap();
assert_eq!(*path, comp.outer.points);
}
#[test]
fn test_generate_single_path_with_holes() {
let mut pixels = Vec::new();
for y in 0..7u32 {
for x in 0..7u32 {
if y == 0 || y == 6 || x == 0 || x == 6 {
pixels.push((x, y));
}
}
}
let pix = create_test_image(8, 8, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_single_path().unwrap();
let comp = &borders.components[0];
assert!(comp.single_path.is_some());
let path = comp.single_path.as_ref().unwrap();
assert!(!path.is_empty());
assert!(path.len() >= comp.outer.points.len());
}
#[test]
fn test_write_read_roundtrip() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_step_chains();
let mut buf = Vec::new();
borders.write(&mut buf).unwrap();
let read_back = ImageBorders::read_from(std::io::Cursor::new(&buf)).unwrap();
assert_eq!(read_back.width, borders.width);
assert_eq!(read_back.height, borders.height);
assert_eq!(read_back.component_count(), borders.component_count());
assert_eq!(
read_back.components[0].outer.points,
borders.components[0].outer.points
);
}
#[test]
fn test_to_svg_string() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_single_path().unwrap();
let svg = borders.to_svg_string().unwrap();
assert!(svg.contains("<svg "));
assert!(svg.contains("polygon"));
assert!(svg.contains("</svg>"));
}
#[test]
fn test_write_svg() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_single_path().unwrap();
let mut buf = Vec::new();
borders.write_svg(&mut buf).unwrap();
let svg_str = String::from_utf8(buf).unwrap();
assert!(svg_str.contains("<svg "));
assert!(svg_str.contains("</svg>"));
}
#[test]
fn test_write_read_with_holes() {
let mut pixels = Vec::new();
for y in 0..5u32 {
for x in 0..5u32 {
if y == 0 || y == 4 || x == 0 || x == 4 {
pixels.push((x, y));
}
}
}
let pix = create_test_image(5, 5, &pixels);
let mut borders = get_all_borders(&pix).unwrap();
borders.generate_step_chains();
let mut buf = Vec::new();
borders.write(&mut buf).unwrap();
let read_back = ImageBorders::read_from(std::io::Cursor::new(&buf)).unwrap();
assert_eq!(read_back.component_count(), 1);
assert_eq!(
read_back.components[0].holes.len(),
borders.components[0].holes.len()
);
}
#[test]
fn test_write_read_empty() {
let borders = ImageBorders::new(100, 200);
let mut buf = Vec::new();
borders.write(&mut buf).unwrap();
let read_back = ImageBorders::read_from(std::io::Cursor::new(&buf)).unwrap();
assert_eq!(read_back.width, 100);
assert_eq!(read_back.height, 200);
assert_eq!(read_back.component_count(), 0);
}
#[test]
fn test_render_borders() {
let mut pixels = Vec::new();
for y in 1..4 {
for x in 1..4 {
pixels.push((x, y));
}
}
let pix = create_test_image(6, 6, &pixels);
let borders = get_all_borders(&pix).unwrap();
let rendered = render_borders(&borders).unwrap();
assert_eq!(rendered.width(), 6);
assert_eq!(rendered.height(), 6);
assert_eq!(rendered.get_pixel(1, 1), Some(1));
}
}