fast-canny 0.1.0

Industrial-grade Zero-Allocation SIMD Canny Edge Detector
Documentation
use crate::kernel::{self, SobelKernel};
use bumpalo::Bump;
use std::time::Instant;

impl std::fmt::Debug for CannyWorkspace {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CannyWorkspace")
            .field("width", &self.width)
            .field("height", &self.height)
            .field("capacity", &self.capacity)
            .finish_non_exhaustive()
    }
}

pub struct CannyWorkspace {
    pub width: usize,
    pub height: usize,
    pub capacity: usize,
    pub buffer_a: Vec<f32>, // 梯度幅值
    pub buffer_b: Vec<f32>, // 高斯平滑输出
    pub dir_map: Vec<u8>,   // 梯度方向(独立缓冲区)
    pub edge_map: Vec<u8>,  // 最终边缘图
    pub arena: Bump,
    pub(crate) kernel: Box<dyn SobelKernel>,
}

impl CannyWorkspace {
    pub fn new(width: usize, height: usize) -> Result<Self, crate::CannyError> {
        if width < 3 || height < 3 {
            return Err(crate::CannyError::InvalidDimensions { width, height });
        }
        let t0 = Instant::now();
        let capacity = width * height;
        let arena_bytes = (capacity / 20) * std::mem::size_of::<usize>();
        let ws = Self {
            width,
            height,
            capacity,
            buffer_a: vec![0.0f32; capacity],
            buffer_b: vec![0.0f32; capacity],
            dir_map: vec![0u8; capacity],
            edge_map: vec![0u8; capacity],
            arena: Bump::with_capacity(arena_bytes),
            kernel: kernel::detect(),
        };
        log::info!(
            "[CannyWorkspace::new] {}x{}, ~{} KB, {:?}",
            width,
            height,
            (capacity * 10 + arena_bytes) / 1024,
            t0.elapsed()
        );
        Ok(ws)
    }

    /// 帧间重置:O(W×H) 清零 edge_map,O(1) 重置 arena。
    #[inline(always)]
    pub fn reset(&mut self) {
        self.arena.reset();
        self.edge_map.fill(0);
    }

    #[inline(always)]
    pub(crate) fn kernel(&self) -> &dyn SobelKernel {
        self.kernel.as_ref()
    }
}