zoomvtools 2.0.0

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

use std::num::{NonZeroU8, NonZeroUsize};

use crate::{
    analysis::MVAnalysisData,
    fake::group_of_planes::FakeGroupOfPlanes,
    frame::{FramePlanes, FramePlanesMut, FrameView, PlaneRef, PlaneSizeTuple},
    mv::MotionVector,
    params::{MotionFlags, Subpel},
    video::{ColorFamily, Resolution, SampleType, VideoFormat, VideoInfo},
};

use super::{Flow, FlowMode, FlowOptions};

static Y_REFERENCE: [u8; 4] = [10, 20, 40, 80];
static U_REFERENCE: [u8; 4] = [1, 5, 13, 29];
static V_REFERENCE: [u8; 4] = [4, 8, 20, 32];

fn yuv444_info() -> VideoInfo {
    VideoInfo {
        format: VideoFormat {
            color_family: ColorFamily::Yuv,
            sample_type: SampleType::Integer,
            bits_per_sample: NonZeroU8::new(8).unwrap(),
            bytes_per_sample: NonZeroU8::new(1).unwrap(),
            sub_sampling_w: 0,
            sub_sampling_h: 0,
        },
        resolution: Resolution {
            width: 2,
            height: 1,
        },
        num_frames: 10,
    }
}

fn analysis_data() -> MVAnalysisData {
    MVAnalysisData {
        blk_size_x: NonZeroUsize::new(1).unwrap(),
        blk_size_y: NonZeroUsize::new(1).unwrap(),
        pel: Subpel::Full,
        level_count: 1,
        delta_frame: 1,
        is_backward: false,
        motion_flags: MotionFlags::empty(),
        width: NonZeroUsize::new(2).unwrap(),
        height: NonZeroUsize::new(1).unwrap(),
        overlap_x: 0,
        overlap_y: 0,
        blk_x: NonZeroUsize::new(2).unwrap(),
        blk_y: NonZeroUsize::new(1).unwrap(),
        bits_per_sample: NonZeroU8::new(8).unwrap(),
        y_ratio_uv: NonZeroU8::new(1).unwrap(),
        x_ratio_uv: NonZeroU8::new(1).unwrap(),
        h_padding: 1,
        v_padding: 0,
    }
}

fn fake_vectors(vector: MotionVector, validity: bool) -> FakeGroupOfPlanes {
    let mut fake_gop = FakeGroupOfPlanes::new(&analysis_data());
    for block in &mut fake_gop.plane_mut(0).blocks {
        block.vector = vector;
    }
    fake_gop.set_validity(validity);
    fake_gop
}

fn padded_reference_view() -> FrameView<'static, u8> {
    FrameView::new(
        FramePlanes::new([
            Some(PlaneRef::new(&Y_REFERENCE)),
            Some(PlaneRef::new(&U_REFERENCE)),
            Some(PlaneRef::new(&V_REFERENCE)),
        ]),
        plane_sizes(3),
    )
}

fn plane_sizes(stride: usize) -> PlaneSizeTuple {
    let stride = NonZeroUsize::new(stride).unwrap();
    (stride, Some(stride), Some(stride))
}

#[test]
fn flow_render_frame_fetch_uses_motion_vectors_on_all_planes() {
    let flow = Flow::new(
        yuv444_info(),
        analysis_data(),
        FlowOptions {
            time: 100.0,
            mode: FlowMode::Fetch,
        },
    )
    .unwrap();
    let reference = padded_reference_view();
    let vectors = fake_vectors(MotionVector { x: 1, y: 0, sad: 0 }, true);
    let mut y = [0u8; 2];
    let mut u = [0u8; 2];
    let mut v = [0u8; 2];
    let mut output = FramePlanesMut::new([Some(&mut y), Some(&mut u), Some(&mut v)]);

    flow.render_frame(&reference, &mut output, plane_sizes(2), &vectors, 0)
        .unwrap();

    assert_eq!(y, [40, 40]);
    assert_eq!(u, [13, 13]);
    assert_eq!(v, [20, 20]);
}

#[test]
fn flow_render_frame_shift_moves_pixels_and_fills_holes() {
    let flow = Flow::new(
        yuv444_info(),
        analysis_data(),
        FlowOptions {
            time: 100.0,
            mode: FlowMode::Shift,
        },
    )
    .unwrap();
    let reference = padded_reference_view();
    let vectors = fake_vectors(MotionVector { x: 1, y: 0, sad: 0 }, true);
    let mut y = [0u8; 2];
    let mut u = [0u8; 2];
    let mut v = [0u8; 2];
    let mut output = FramePlanesMut::new([Some(&mut y), Some(&mut u), Some(&mut v)]);

    flow.render_frame(&reference, &mut output, plane_sizes(2), &vectors, 0)
        .unwrap();

    assert_eq!(y, [255, 40]);
    assert_eq!(u, [255, 13]);
    assert_eq!(v, [255, 20]);
}

#[test]
fn flow_vectors_are_not_usable_when_scene_change_marks_vectors_invalid() {
    let flow = Flow::new(
        yuv444_info(),
        analysis_data(),
        FlowOptions {
            time: 100.0,
            mode: FlowMode::Fetch,
        },
    )
    .unwrap();
    let vectors = fake_vectors(MotionVector { x: 1, y: 0, sad: 0 }, false);

    assert!(!flow.vectors_are_usable(&vectors, 400, 130));
}