pub mod gaussian;
pub mod hysteresis;
pub mod kernel;
pub mod nms;
pub mod pipeline;
pub mod workspace;
#[cfg(target_arch = "x86_64")]
pub mod avx2_kernel {
pub use crate::kernel::avx2::*;
}
mod pipeline_ptr;
pub use workspace::CannyWorkspace;
use gaussian::apply as gaussian_apply;
use hysteresis::track_edges;
use pipeline::execute_tiled_pipeline;
#[derive(Debug, Clone)]
pub struct CannyConfig {
pub sigma: f32,
pub low_thresh: f32,
pub high_thresh: f32,
}
impl Default for CannyConfig {
fn default() -> Self {
Self {
sigma: 1.0,
low_thresh: 50.0,
high_thresh: 150.0,
}
}
}
impl CannyConfig {
pub fn builder() -> CannyConfigBuilder {
CannyConfigBuilder::default()
}
}
#[derive(Default)]
pub struct CannyConfigBuilder {
sigma: Option<f32>,
low_thresh: Option<f32>,
high_thresh: Option<f32>,
}
impl CannyConfigBuilder {
pub fn sigma(mut self, v: f32) -> Self {
self.sigma = Some(v);
self
}
pub fn thresholds(mut self, low: f32, high: f32) -> Self {
self.low_thresh = Some(low);
self.high_thresh = Some(high);
self
}
pub fn build(self) -> Result<CannyConfig, CannyError> {
let cfg = CannyConfig {
sigma: self.sigma.unwrap_or(1.0),
low_thresh: self.low_thresh.unwrap_or(50.0),
high_thresh: self.high_thresh.unwrap_or(150.0),
};
if cfg.low_thresh > cfg.high_thresh {
return Err(CannyError::InvalidThresholds {
low: cfg.low_thresh,
high: cfg.high_thresh,
});
}
Ok(cfg)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CannyError {
InvalidDimensions { width: usize, height: usize },
InputLengthMismatch { expected: usize, actual: usize },
InvalidThresholds { low: f32, high: f32 },
NullPointer,
}
impl std::fmt::Display for CannyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidDimensions { width, height } => {
write!(f, "invalid dimensions: {}x{} (min 3x3)", width, height)
}
Self::InputLengthMismatch { expected, actual } => {
write!(f, "input length: expected {}, got {}", expected, actual)
}
Self::InvalidThresholds { low, high } => {
write!(f, "thresholds: low={} > high={}", low, high)
}
Self::NullPointer => write!(f, "null pointer"),
}
}
}
impl std::error::Error for CannyError {}
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CannyStatus {
Ok = 0,
NullPointer = -1,
InvalidDimensions = -2,
InputLengthMismatch = -3,
InvalidThresholds = -4,
}
impl From<&CannyError> for CannyStatus {
fn from(e: &CannyError) -> Self {
match e {
CannyError::NullPointer => Self::NullPointer,
CannyError::InvalidDimensions { .. } => Self::InvalidDimensions,
CannyError::InputLengthMismatch { .. } => Self::InputLengthMismatch,
CannyError::InvalidThresholds { .. } => Self::InvalidThresholds,
}
}
}
pub fn canny<'ws>(
src: &[f32],
ws: &'ws mut CannyWorkspace,
cfg: &CannyConfig,
) -> Result<&'ws [u8], CannyError> {
let expected = ws.width * ws.height;
if src.len() != expected {
return Err(CannyError::InputLengthMismatch {
expected,
actual: src.len(),
});
}
if cfg.low_thresh > cfg.high_thresh {
return Err(CannyError::InvalidThresholds {
low: cfg.low_thresh,
high: cfg.high_thresh,
});
}
ws.reset();
let w = ws.width;
let h = ws.height;
if cfg.sigma > 0.0 {
gaussian_apply(src, &mut ws.buffer_b, w, h, cfg.sigma);
let blurred_ptr = ws.buffer_b.as_ptr();
let blurred_len = ws.buffer_b.len();
let blurred: &[f32] = unsafe { std::slice::from_raw_parts(blurred_ptr, blurred_len) };
execute_tiled_pipeline(blurred, ws, cfg.low_thresh, cfg.high_thresh);
} else {
execute_tiled_pipeline(src, ws, cfg.low_thresh, cfg.high_thresh);
}
track_edges(&mut ws.edge_map, w, h, &ws.arena);
Ok(&ws.edge_map)
}
pub fn canny_u8<'ws>(
src: &[u8],
ws: &'ws mut CannyWorkspace,
cfg: &CannyConfig,
) -> Result<&'ws [u8], CannyError> {
let expected = ws.width * ws.height;
if src.len() != expected {
return Err(CannyError::InputLengthMismatch {
expected,
actual: src.len(),
});
}
if cfg.low_thresh > cfg.high_thresh {
return Err(CannyError::InvalidThresholds {
low: cfg.low_thresh,
high: cfg.high_thresh,
});
}
ws.reset();
let w = ws.width;
let h = ws.height;
{
let dst = &mut ws.buffer_b;
for (d, &s) in dst.iter_mut().zip(src.iter()) {
*d = s as f32;
}
}
if cfg.sigma > 0.0 {
let src_f32: Vec<f32> = ws.buffer_b.clone();
gaussian_apply(&src_f32, &mut ws.buffer_b, w, h, cfg.sigma);
}
let blurred = ws.buffer_b.as_slice();
let blurred_ptr = blurred.as_ptr();
let blurred_len = blurred.len();
let blurred_static: &'static [f32] = unsafe {
std::slice::from_raw_parts(blurred_ptr, blurred_len)
};
execute_tiled_pipeline(blurred_static, ws, cfg.low_thresh, cfg.high_thresh);
track_edges(&mut ws.edge_map, w, h, &ws.arena);
Ok(&ws.edge_map)
}
#[unsafe(no_mangle)]
pub extern "C" fn canny_workspace_new(width: usize, height: usize) -> *mut CannyWorkspace {
match CannyWorkspace::new(width, height) {
Ok(ws) => Box::into_raw(Box::new(ws)),
Err(e) => {
log::error!("[canny_workspace_new] {}", e);
std::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn canny_workspace_free(ws: *mut CannyWorkspace) {
if !ws.is_null() {
drop(unsafe { Box::from_raw(ws) });
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn canny_process_ex(
ws: *mut CannyWorkspace,
src: *const f32,
src_len: usize,
sigma: f32,
low_thresh: f32,
high_thresh: f32,
out_edge: *mut *const u8,
) -> i32 {
if ws.is_null() || src.is_null() || out_edge.is_null() {
log::error!("[canny_process_ex] null pointer");
return CannyStatus::NullPointer as i32;
}
let ws_ref = unsafe { &mut *ws };
let src_slice = unsafe { std::slice::from_raw_parts(src, src_len) };
let cfg = match CannyConfig::builder()
.sigma(sigma)
.thresholds(low_thresh, high_thresh)
.build()
{
Ok(c) => c,
Err(e) => {
log::error!("[canny_process_ex] config: {}", e);
return CannyStatus::from(&e) as i32;
}
};
match canny(src_slice, ws_ref, &cfg) {
Ok(edge) => {
unsafe {
*out_edge = edge.as_ptr();
}
CannyStatus::Ok as i32
}
Err(e) => {
log::error!("[canny_process_ex] {}", e);
CannyStatus::from(&e) as i32
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn canny_process_u8(
ws: *mut CannyWorkspace,
src: *const u8,
src_len: usize,
sigma: f32,
low_thresh: f32,
high_thresh: f32,
out_edge: *mut *const u8,
) -> i32 {
if ws.is_null() || src.is_null() || out_edge.is_null() {
log::error!("[canny_process_u8] null pointer");
return CannyStatus::NullPointer as i32;
}
let ws_ref = unsafe { &mut *ws };
let src_slice = unsafe { std::slice::from_raw_parts(src, src_len) };
let cfg = match CannyConfig::builder()
.sigma(sigma)
.thresholds(low_thresh, high_thresh)
.build()
{
Ok(c) => c,
Err(e) => {
log::error!("[canny_process_u8] config: {}", e);
return CannyStatus::from(&e) as i32;
}
};
match canny_u8(src_slice, ws_ref, &cfg) {
Ok(edge) => {
unsafe {
*out_edge = edge.as_ptr();
}
CannyStatus::Ok as i32
}
Err(e) => {
log::error!("[canny_process_u8] {}", e);
CannyStatus::from(&e) as i32
}
}
}