zoomvtools 2.0.0

Video motion vector analysis utilities in pure Rust
Documentation
use std::{fmt::Display, mem::MaybeUninit, num::NonZeroUsize};

use num_traits::{AsPrimitive, PrimInt};

use crate::{
    resize::{
        SIMPLE_RESIZE_WEIGHT_HALF, SIMPLE_RESIZE_WEIGHT_MAX, SIMPLE_RESIZE_WEIGHT_SHIFT,
        SimpleResize,
    },
    util::uninit_vec,
};
use semisafe::option::unwrap as semisafe_opt_unwrap;
use semisafe::slice::get as semisafe_get;
use semisafe::slice::get_mut as semisafe_get_mut;

pub trait SimpleResizeable:
    Copy
    + Clone
    + Default
    + Send
    + Sync
    + PrimInt
    + Display
    + AsPrimitive<i16>
    + AsPrimitive<i32>
    + AsPrimitive<i64>
    + 'static
{
}

impl SimpleResizeable for u8 {}
impl SimpleResizeable for i16 {}

pub(crate) unsafe fn simple_resize<T: SimpleResizeable>(
    resizer: &SimpleResize,
    mut dest: *mut u8,
    dest_stride_bytes: NonZeroUsize,
    src: *const u8,
    src_stride_bytes: NonZeroUsize,
    horizontal_vectors: bool,
) {
    // Apparently only 16 bit vectors need limiting.
    let limit_vectors = size_of::<T>() == 2;

    let pel = resizer.pel as i32;
    let mut minimum = 0;
    let mut maximum = resizer.limit_height.get() as i32 * pel - 1;
    let horizontal_step = if horizontal_vectors { pel } else { 0 };
    let vertical_step = if horizontal_vectors { 0 } else { pel };

    let mut work: Vec<MaybeUninit<T>> = uninit_vec(resizer.src_width.get());
    for y in 0..resizer.dest_height.get() {
        let weight_bottom = *semisafe_get(&resizer.vertical_weights, y);
        let weight_top = SIMPLE_RESIZE_WEIGHT_MAX - weight_bottom;

        let offset1 = *semisafe_get(&resizer.vertical_offsets, y) * src_stride_bytes.get();
        let src_p1: *const T = src.add(offset1).cast();
        let src_p2: *const T = src.add(offset1 + src_stride_bytes.get()).cast();

        // vertical
        for x in 0..resizer.src_width.get() {
            let src1: i32 = (*src_p1.add(x)).as_();
            let src2: i32 = (*src_p2.add(x)).as_();
            semisafe_get_mut(&mut work, x).write(semisafe_opt_unwrap(T::from(
                (src1 * weight_top + src2 * weight_bottom + SIMPLE_RESIZE_WEIGHT_HALF)
                    >> SIMPLE_RESIZE_WEIGHT_SHIFT,
            )));
        }

        if horizontal_vectors {
            minimum = 0;
            maximum = resizer.limit_width.get() as i32 * pel - 1;
        }

        // horizontal
        for x in 0..resizer.dest_width.get() {
            let weight_right = *semisafe_get(&resizer.horizontal_weights, x);
            let weight_left = SIMPLE_RESIZE_WEIGHT_MAX - weight_right;

            let offset = *semisafe_get(&resizer.horizontal_offsets, x);
            // SAFETY: vertical pass above writes every work element before horizontal reads.
            let workp1: i32 = (*unsafe { semisafe_get(&work, offset).assume_init_ref() }).as_();
            // SAFETY: `init_tables` guarantees `offset + 1 < src_width`, and vertical pass wrote it.
            let workp2: i32 = (*unsafe { semisafe_get(&work, offset + 1).assume_init_ref() }).as_();
            let mut result =
                (workp1 * weight_left + workp2 * weight_right + SIMPLE_RESIZE_WEIGHT_HALF)
                    >> SIMPLE_RESIZE_WEIGHT_SHIFT;

            if limit_vectors {
                result = result.clamp(minimum, maximum);

                minimum -= horizontal_step;
                maximum -= horizontal_step;
            }

            // SAFETY: `dest` points to current destination row and `x < dest_width`.
            let destp: *mut T = dest.cast::<T>();
            // SAFETY: destination row has at least `dest_width` elements.
            *unsafe { destp.add(x) } = semisafe_opt_unwrap(T::from(result));
        }

        dest = dest.add(dest_stride_bytes.get());

        if limit_vectors {
            minimum -= vertical_step;
            maximum -= vertical_step;
        }
    }
}