use crate::opt::Config;
use crate::sizes::{KicadPos, PixelDim, PixelPos};
use image::{GenericImageView, LumaA, Pixel};
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PixelKind {
Light,
Dark,
}
impl PixelKind {
pub fn from_pixel(pixel: impl Pixel<Subpixel = u8>, config: &Config) -> Option<Self> {
let LumaA([luma, alpha]) = pixel.to_luma_alpha();
let is_light = match () {
_ if alpha < u8::MAX / 2 => return None,
_ if luma > u8::MAX / 2 => true,
_ => false,
};
match is_light ^ config.invert {
true => Some(Self::Light),
false => Some(Self::Dark),
}
}
}
#[derive(Debug)]
pub struct Extents {
pub min: PixelPos,
pub max: PixelPos,
}
impl Extents {
pub fn from_image(
image: &impl GenericImageView<Pixel = impl Pixel<Subpixel = u8>>,
config: &Config,
) -> Self {
let mut min_x = u32::MAX;
let mut max_x = 0;
let mut min_y = u32::MAX;
let mut max_y = 0;
for (x, y, pixel) in image.pixels() {
if PixelKind::from_pixel(pixel, config).is_some() {
min_x = min_x.min(x);
max_x = max_x.max(x);
min_y = min_y.min(y);
max_y = max_y.max(y);
}
}
Self {
min: PixelPos {
x: PixelDim(min_x),
y: PixelDim(min_y),
},
max: PixelPos {
x: PixelDim(max_x),
y: PixelDim(max_y),
},
}
}
pub fn center(&self) -> PixelPos {
let x = self.min.x.0 + (self.max.x.0 - self.min.x.0) / 2;
let y = self.min.y.0 + (self.max.y.0 - self.min.y.0) / 2;
PixelPos {
x: PixelDim(x),
y: PixelDim(y),
}
}
}
#[derive(Debug)]
pub struct Nearby {
pub top: Option<PixelKind>,
pub bot: Option<PixelKind>,
pub left: Option<PixelKind>,
pub right: Option<PixelKind>,
pub top_left: Option<PixelKind>,
pub top_right: Option<PixelKind>,
pub bot_left: Option<PixelKind>,
pub bot_right: Option<PixelKind>,
}
impl Nearby {
pub fn from_pos(
image: &impl GenericImageView<Pixel = impl Pixel<Subpixel = u8>>,
PixelPos {
x: PixelDim(x),
y: PixelDim(y),
}: PixelPos,
config: &Config,
) -> Self {
let try_get = |x, y| {
let x = x?;
let y = y?;
if image.in_bounds(x, y) {
PixelKind::from_pixel(image.get_pixel(x, y), config)
} else {
None
}
};
Self {
top: try_get(Some(x), y.checked_sub(1)),
bot: try_get(Some(x), y.checked_add(1)),
left: try_get(x.checked_sub(1), Some(y)),
right: try_get(x.checked_add(1), Some(y)),
top_left: try_get(x.checked_sub(1), y.checked_sub(1)),
top_right: try_get(x.checked_add(1), y.checked_sub(1)),
bot_left: try_get(x.checked_sub(1), y.checked_add(1)),
bot_right: try_get(x.checked_add(1), y.checked_add(1)),
}
}
}
pub fn for_each_point_in_pixel<E>(
top_left: PixelPos,
kind: PixelKind,
nearby: &Nearby,
extents: &Extents,
config: &Config,
mut f: impl FnMut(KicadPos) -> Result<(), E>,
) -> Result<(), E> {
let center = extents.center() + PixelPos::X1 + PixelPos::Y1;
let top = top_left.y;
let bot = top + PixelDim(1);
let left = top_left.x;
let right = left + PixelDim(1);
let kicad_pos = |pos: PixelDim, center_pos: PixelDim| {
let relative_pos = config.pixel_pitch * pos.abs_diff(center_pos);
if pos < center_pos {
-relative_pos
} else {
relative_pos
}
};
let top = kicad_pos(top, center.y);
let bot = kicad_pos(bot, center.y);
let left = kicad_pos(left, center.x);
let right = kicad_pos(right, center.x);
let mut add_points_from =
|mut x, mut y, horiz, vert, diag, horiz_is_positive, vert_is_positive| {
let sub_or_add = |lhs, should_sub, rhs| {
if should_sub {
lhs - rhs
} else {
lhs + rhs
}
};
match kind {
PixelKind::Dark => {
f(KicadPos { x, y })
}
PixelKind::Light => {
let x_inset = sub_or_add(x, horiz_is_positive, config.clearance);
let y_inset = sub_or_add(y, vert_is_positive, config.clearance);
if horiz == Some(PixelKind::Dark) {
x = x_inset;
}
if vert == Some(PixelKind::Dark) {
y = y_inset;
}
if x == x_inset || y == y_inset || diag != Some(PixelKind::Dark) {
f(KicadPos { x, y })
} else {
let mut new_points = [
KicadPos { x, y: y_inset },
KicadPos {
x: x_inset,
y: y_inset,
},
KicadPos { x: x_inset, y },
];
if horiz_is_positive != vert_is_positive {
new_points.reverse();
}
for point in new_points {
f(point)?;
}
Ok(())
}
}
}
};
add_points_from(
left,
top,
nearby.left,
nearby.top,
nearby.top_left,
false,
false,
)?;
add_points_from(
right,
top,
nearby.right,
nearby.top,
nearby.top_right,
true,
false,
)?;
add_points_from(
right,
bot,
nearby.right,
nearby.bot,
nearby.bot_right,
true,
true,
)?;
add_points_from(
left,
bot,
nearby.left,
nearby.bot,
nearby.bot_left,
false,
true,
)?;
Ok(())
}