#[cfg(all(target_arch = "aarch64", not(miri)))]
mod neon;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
any(feature = "std", target_feature = "ssse3"),
not(miri),
))]
mod x86_ssse3;
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
any(feature = "std", target_feature = "avx2"),
not(miri),
))]
mod x86_avx2;
#[cfg(all(target_arch = "wasm32", target_feature = "simd128", not(miri)))]
mod wasm_simd128;
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(unreachable_code)] #[allow(clippy::too_many_arguments)] pub(super) fn bgr_to_hsv_planes(
h_out: &mut [u8],
s_out: &mut [u8],
v_out: &mut [u8],
src: &[u8],
width: u32,
height: u32,
stride: u32,
use_simd: bool,
) {
if !use_simd {
return scalar::Scalar::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
#[cfg(all(target_arch = "aarch64", not(miri)))]
{
unsafe {
neon::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
return;
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128", not(miri)))]
{
unsafe {
wasm_simd128::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
return;
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "std",
not(miri)
))]
{
if std::is_x86_feature_detected!("avx2") {
unsafe {
x86_avx2::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
return;
}
if std::is_x86_feature_detected!("ssse3") {
unsafe {
x86_ssse3::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
return;
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
not(feature = "std"),
target_feature = "avx2",
not(miri),
))]
{
unsafe {
x86_avx2::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
return;
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
not(feature = "std"),
target_feature = "ssse3",
not(target_feature = "avx2"),
not(miri),
))]
{
unsafe {
x86_ssse3::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
return;
}
scalar::Scalar::bgr_to_hsv_planes(h_out, s_out, v_out, src, width, height, stride);
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(dead_code)] pub(super) fn bgr_to_hsv_pixel(b: f32, g: f32, r: f32) -> (u8, u8, u8) {
scalar::Scalar::bgr_to_hsv_pixel(b, g, r)
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(unreachable_code)]
pub(super) fn mean_abs_diff(a: &[u8], b: &[u8], n: usize, use_simd: bool) -> f64 {
debug_assert!(a.len() >= n && b.len() >= n);
if n == 0 {
return 0.0;
}
if use_simd {
#[cfg(all(target_arch = "aarch64", not(miri)))]
{
return unsafe { neon::mean_abs_diff(a, b, n) };
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "std",
not(miri)
))]
{
if std::is_x86_feature_detected!("ssse3") {
return unsafe { x86_ssse3::mean_abs_diff(a, b, n) };
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
not(feature = "std"),
target_feature = "ssse3",
not(miri),
))]
{
return unsafe { x86_ssse3::mean_abs_diff(a, b, n) };
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128", not(miri)))]
{
return unsafe { wasm_simd128::mean_abs_diff(a, b, n) };
}
}
scalar::Scalar::mean_abs_diff(a, b, n)
}
#[cfg_attr(not(tarpaulin), inline(always))]
#[allow(unreachable_code)]
pub(super) fn sobel(
input: &[u8],
mag: &mut [i32],
dir: &mut [u8],
w: usize,
h: usize,
use_simd: bool,
) {
if use_simd {
#[cfg(all(target_arch = "aarch64", not(miri)))]
{
return unsafe { neon::sobel(input, mag, dir, w, h) };
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
feature = "std",
not(miri)
))]
{
if std::is_x86_feature_detected!("ssse3") {
return unsafe { x86_ssse3::sobel(input, mag, dir, w, h) };
}
}
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
not(feature = "std"),
target_feature = "ssse3",
not(miri),
))]
{
return unsafe { x86_ssse3::sobel(input, mag, dir, w, h) };
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128", not(miri)))]
{
return unsafe { wasm_simd128::sobel(input, mag, dir, w, h) };
}
}
scalar::Scalar::sobel(input, mag, dir, w, h);
}
mod scalar {
use crate::round_32;
pub(super) struct Scalar;
impl Scalar {
#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
pub(super) fn bgr_to_hsv_planes(
h_out: &mut [u8],
s_out: &mut [u8],
v_out: &mut [u8],
src: &[u8],
width: u32,
height: u32,
stride: u32,
) {
let w = width as usize;
let h = height as usize;
let s = stride as usize;
for y in 0..h {
let row = &src[y * s..y * s + w * 3];
let dst_off = y * w;
for x in 0..w {
let b = row[x * 3] as f32;
let g = row[x * 3 + 1] as f32;
let r = row[x * 3 + 2] as f32;
let (hue, sat, val) = Self::bgr_to_hsv_pixel(b, g, r);
h_out[dst_off + x] = hue;
s_out[dst_off + x] = sat;
v_out[dst_off + x] = val;
}
}
}
#[inline]
pub(super) fn bgr_to_hsv_pixel(b: f32, g: f32, r: f32) -> (u8, u8, u8) {
let v = b.max(g).max(r);
let min = b.min(g).min(r);
let delta = v - min;
let s = if v == 0.0 { 0.0 } else { 255.0 * delta / v };
let hue = if delta == 0.0 {
0.0
} else if v == r {
let h = 60.0 * (g - b) / delta;
if h < 0.0 { h + 360.0 } else { h }
} else if v == g {
60.0 * (b - r) / delta + 120.0
} else {
60.0 * (r - g) / delta + 240.0
};
let h8 = round_32(hue * 0.5).clamp(0.0, 179.0) as u8;
(
h8,
round_32(s).clamp(0.0, 255.0) as u8,
round_32(v).clamp(0.0, 255.0) as u8,
)
}
pub(super) fn sobel(input: &[u8], mag: &mut [i32], dir: &mut [u8], w: usize, h: usize) {
mag.fill(0);
dir.fill(0);
for y in 1..h.saturating_sub(1) {
for x in 1..w.saturating_sub(1) {
let i = |yy: usize, xx: usize| input[yy * w + xx] as i32;
let gx = -i(y - 1, x - 1) - 2 * i(y, x - 1) - i(y + 1, x - 1)
+ i(y - 1, x + 1)
+ 2 * i(y, x + 1)
+ i(y + 1, x + 1);
let gy = -i(y - 1, x - 1) - 2 * i(y - 1, x) - i(y - 1, x + 1)
+ i(y + 1, x - 1)
+ 2 * i(y + 1, x)
+ i(y + 1, x + 1);
let idx = y * w + x;
mag[idx] = gx.abs() + gy.abs();
let ax = gx.abs();
let ay = gy.abs();
dir[idx] = if ay * 1000 < ax * 414 {
0
} else if ay * 1000 > ax * 2414 {
2
} else if gx.signum() == gy.signum() {
1
} else {
3
};
}
}
}
#[inline]
pub(super) fn mean_abs_diff(a: &[u8], b: &[u8], n: usize) -> f64 {
let mut sum: u64 = 0;
for i in 0..n {
let da = a[i] as i32 - b[i] as i32;
sum += da.unsigned_abs() as u64;
}
sum as f64 / n as f64
}
}
}
#[cfg(all(test, feature = "std", not(miri)))]
mod tests {
use super::*;
fn make_bgr(w: usize, h: usize) -> Vec<u8> {
let mut buf = vec![0u8; w * h * 3];
let mut rng = 0x9E3779B9u32;
for v in buf.iter_mut() {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
*v = (rng >> 24) as u8;
}
buf
}
fn make_luma(w: usize, h: usize) -> Vec<u8> {
let mut buf = vec![0u8; w * h];
let mut rng = 0xDEADBEEFu32;
for v in buf.iter_mut() {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
*v = (rng >> 24) as u8;
}
buf
}
#[test]
fn scalar_bgr_to_hsv_planes() {
let (w, h) = (32, 16);
let src = make_bgr(w, h);
let n = w * h;
let mut ho = vec![0u8; n];
let mut so = vec![0u8; n];
let mut vo = vec![0u8; n];
scalar::Scalar::bgr_to_hsv_planes(
&mut ho,
&mut so,
&mut vo,
&src,
w as u32,
h as u32,
(w * 3) as u32,
);
assert!(vo.iter().any(|&v| v > 0));
}
#[test]
fn scalar_mean_abs_diff_nonzero() {
let a = make_luma(64, 1);
let b = make_luma(64, 1);
let d = scalar::Scalar::mean_abs_diff(&a, &b, 64);
assert!(d >= 0.0);
}
#[test]
fn scalar_sobel() {
let (w, h) = (16, 16);
let src = make_luma(w, h);
let mut mag = vec![0i32; w * h];
let mut dir = vec![0u8; w * h];
scalar::Scalar::sobel(&src, &mut mag, &mut dir, w, h);
assert!(mag.iter().any(|&m| m > 0));
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
#[test]
fn ssse3_bgr_to_hsv_planes_direct() {
if !std::is_x86_feature_detected!("ssse3") {
return;
}
let (w, h) = (64, 16);
let src = make_bgr(w, h);
let n = w * h;
let mut ho = vec![0u8; n];
let mut so = vec![0u8; n];
let mut vo = vec![0u8; n];
unsafe {
x86_ssse3::bgr_to_hsv_planes(
&mut ho,
&mut so,
&mut vo,
&src,
w as u32,
h as u32,
(w * 3) as u32,
);
}
assert!(vo.iter().any(|&v| v > 0));
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
#[test]
fn ssse3_mean_abs_diff_direct() {
if !std::is_x86_feature_detected!("ssse3") {
return;
}
let a = make_luma(128, 1);
let b = make_luma(128, 1);
let d = unsafe { x86_ssse3::mean_abs_diff(&a, &b, 128) };
assert!(d >= 0.0);
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
#[test]
fn ssse3_sobel_direct() {
if !std::is_x86_feature_detected!("ssse3") {
return;
}
let (w, h) = (32, 32);
let src = make_luma(w, h);
let mut mag = vec![0i32; w * h];
let mut dir = vec![0u8; w * h];
unsafe { x86_ssse3::sobel(&src, &mut mag, &mut dir, w, h) };
assert!(mag.iter().any(|&m| m > 0));
}
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "std"))]
#[test]
fn avx2_bgr_to_hsv_planes_direct() {
if !std::is_x86_feature_detected!("avx2") {
return;
}
let (w, h) = (64, 16);
let src = make_bgr(w, h);
let n = w * h;
let mut ho = vec![0u8; n];
let mut so = vec![0u8; n];
let mut vo = vec![0u8; n];
unsafe {
x86_avx2::bgr_to_hsv_planes(
&mut ho,
&mut so,
&mut vo,
&src,
w as u32,
h as u32,
(w * 3) as u32,
);
}
assert!(vo.iter().any(|&v| v > 0));
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_bgr_to_hsv_planes_direct() {
let (w, h) = (64, 16);
let src = make_bgr(w, h);
let n = w * h;
let mut ho = vec![0u8; n];
let mut so = vec![0u8; n];
let mut vo = vec![0u8; n];
unsafe {
neon::bgr_to_hsv_planes(
&mut ho,
&mut so,
&mut vo,
&src,
w as u32,
h as u32,
(w * 3) as u32,
);
}
assert!(vo.iter().any(|&v| v > 0));
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_mean_abs_diff_direct() {
let a = make_luma(128, 1);
let b = make_luma(128, 1);
let d = unsafe { neon::mean_abs_diff(&a, &b, 128) };
assert!(d >= 0.0);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn neon_sobel_direct() {
let (w, h) = (32, 32);
let src = make_luma(w, h);
let mut mag = vec![0i32; w * h];
let mut dir = vec![0u8; w * h];
unsafe { neon::sobel(&src, &mut mag, &mut dir, w, h) };
assert!(mag.iter().any(|&m| m > 0));
}
}