zoomvtools 1.1.1

Video motion vector analysis utilities in pure Rust
Documentation
#![allow(clippy::unwrap_used, reason = "allow in test files")]
#![allow(clippy::undocumented_unsafe_blocks, reason = "allow in test files")]
#![allow(clippy::indexing_slicing, reason = "allow in test files")]

#[cfg(all(target_arch = "x86_64", feature = "avx2"))]
use std::mem::size_of;
use std::num::NonZeroUsize;

fn nz(value: usize) -> NonZeroUsize {
    NonZeroUsize::new(value).expect("test value must be non-zero")
}

#[test]
fn limit_changes_u8_clamps_to_src_range() {
    let mut dest = [0_u8, 50, 120, 250];
    let src = [10_u8, 40, 100, 240];

    unsafe {
        super::rust::limit_changes::<u8>(
            dest.as_mut_ptr().cast(),
            nz(4),
            src.as_ptr().cast(),
            nz(4),
            nz(4),
            nz(1),
            8,
        );
    }

    assert_eq!(dest, [2, 48, 108, 248]);
}

#[test]
fn limit_changes_u8_limit_zero_copies_src() {
    let mut dest = [5_u8, 6, 7, 8, 9, 10];
    let src = [100_u8, 90, 80, 70, 60, 50];

    unsafe {
        super::rust::limit_changes::<u8>(
            dest.as_mut_ptr().cast(),
            nz(6),
            src.as_ptr().cast(),
            nz(6),
            nz(6),
            nz(1),
            0,
        );
    }

    assert_eq!(dest, src);
}

#[test]
fn limit_changes_u16_limit_max_keeps_dest() {
    let mut dest = [0_u16, 1, 1200, 32_000, 65_535];
    let original_dest = dest;
    let src = [65_535_u16, 22, 9, 7, 0];

    unsafe {
        super::rust::limit_changes::<u16>(
            dest.as_mut_ptr().cast(),
            nz(dest.len() * size_of::<u16>()),
            src.as_ptr().cast(),
            nz(src.len() * size_of::<u16>()),
            nz(dest.len()),
            nz(1),
            u16::MAX,
        );
    }

    assert_eq!(dest, original_dest);
}

#[test]
fn limit_changes_u16_respects_multirow_byte_strides() {
    let width = 3;
    let height = 3;
    let stride = 5;
    let mut dest = [
        100_u16, 200, 300, 9999, 9999, 400, 500, 600, 9999, 9999, 700, 800, 900, 9999, 9999,
    ];
    let src = [
        150_u16, 150, 150, 77, 77, 500, 500, 500, 88, 88, 1000, 1000, 1000, 99, 99,
    ];

    unsafe {
        super::rust::limit_changes::<u16>(
            dest.as_mut_ptr().cast(),
            nz(stride * size_of::<u16>()),
            src.as_ptr().cast(),
            nz(stride * size_of::<u16>()),
            nz(width),
            nz(height),
            50,
        );
    }

    assert_eq!(dest[0], 100);
    assert_eq!(dest[1], 200);
    assert_eq!(dest[2], 200);
    assert_eq!(dest[5], 450);
    assert_eq!(dest[6], 500);
    assert_eq!(dest[7], 550);
    assert_eq!(dest[10], 950);
    assert_eq!(dest[11], 950);
    assert_eq!(dest[12], 950);
    assert_eq!(dest[3], 9999);
    assert_eq!(dest[4], 9999);
    assert_eq!(dest[8], 9999);
    assert_eq!(dest[9], 9999);
    assert_eq!(dest[13], 9999);
    assert_eq!(dest[14], 9999);
}

#[cfg(all(target_arch = "x86_64", feature = "avx2"))]
const LIMIT_WIDTHS: &[usize] = &[1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64];

#[cfg(all(target_arch = "x86_64", feature = "avx2"))]
const LIMIT_HEIGHTS: &[usize] = &[1, 2, 3, 5, 8];

#[cfg(all(target_arch = "x86_64", feature = "avx2"))]
#[test]
fn limit_changes_u8_avx2_matches_rust_varied_shapes_and_limits() {
    for &width in LIMIT_WIDTHS {
        for &height in LIMIT_HEIGHTS {
            for padding in [0, 3, 17] {
                let stride = width + padding;
                let mut src = vec![0_u8; stride * height];
                let mut rust_dest = vec![0_u8; stride * height];
                for y in 0..height {
                    for x in 0..width {
                        src[y * stride + x] = ((y * 37 + x * 11 + 13) & 0xff) as u8;
                        rust_dest[y * stride + x] = ((y * 19 + x * 23 + 251) & 0xff) as u8;
                    }
                    src[y * stride] = 0;
                    src[y * stride + width - 1] = u8::MAX;
                    rust_dest[y * stride] = u8::MAX;
                    rust_dest[y * stride + width - 1] = 0;
                }
                let avx_dest = rust_dest.clone();

                for limit in [0_u16, 1, 2, 7, 15, 31, 127, 255] {
                    let mut rust_run = rust_dest.clone();
                    let mut avx_run = avx_dest.clone();

                    unsafe {
                        super::rust::limit_changes::<u8>(
                            rust_run.as_mut_ptr().cast(),
                            nz(stride),
                            src.as_ptr().cast(),
                            nz(stride),
                            nz(width),
                            nz(height),
                            limit,
                        );
                        super::avx2::limit_changes_u8(
                            avx_run.as_mut_ptr().cast(),
                            nz(stride),
                            src.as_ptr().cast(),
                            nz(stride),
                            nz(width),
                            nz(height),
                            limit,
                        );
                    }

                    assert_eq!(
                        avx_run, rust_run,
                        "u8 mismatch at {width}x{height}, stride={stride}, limit={limit}"
                    );
                }
            }
        }
    }
}

#[cfg(all(target_arch = "x86_64", feature = "avx2"))]
#[test]
fn limit_changes_u16_avx2_matches_rust_varied_shapes_and_limits() {
    for &width in LIMIT_WIDTHS {
        for &height in LIMIT_HEIGHTS {
            for padding in [0, 2, 9] {
                let stride = width + padding;
                let mut src = vec![0_u16; stride * height];
                let mut rust_dest = vec![0_u16; stride * height];
                for y in 0..height {
                    for x in 0..width {
                        src[y * stride + x] = ((y * 541 + x * 173 + 19) % 65_536) as u16;
                        rust_dest[y * stride + x] = ((y * 997 + x * 431 + 7) % 65_536) as u16;
                    }
                    src[y * stride] = 0;
                    src[y * stride + width - 1] = u16::MAX;
                    rust_dest[y * stride] = u16::MAX;
                    rust_dest[y * stride + width - 1] = 0;
                }
                let avx_dest = rust_dest.clone();

                for limit in [0_u16, 1, 2, 15, 255, 1023, 32_767, 65_535] {
                    let mut rust_run = rust_dest.clone();
                    let mut avx_run = avx_dest.clone();

                    unsafe {
                        super::rust::limit_changes::<u16>(
                            rust_run.as_mut_ptr().cast(),
                            nz(stride * size_of::<u16>()),
                            src.as_ptr().cast(),
                            nz(stride * size_of::<u16>()),
                            nz(width),
                            nz(height),
                            limit,
                        );
                        super::avx2::limit_changes_u16(
                            avx_run.as_mut_ptr().cast(),
                            nz(stride * size_of::<u16>()),
                            src.as_ptr().cast(),
                            nz(stride * size_of::<u16>()),
                            nz(width),
                            nz(height),
                            limit,
                        );
                    }

                    assert_eq!(
                        avx_run, rust_run,
                        "u16 mismatch at {width}x{height}, stride={stride}, limit={limit}"
                    );
                }
            }
        }
    }
}