#![allow(clippy::too_many_arguments)]
use crate::encoder::bitstream::BitWriter;
use crate::inter_pred::interpolate_luma;
#[derive(Debug, Clone, Copy)]
pub struct MeResult {
pub mv_x: i32,
pub mv_y: i32,
pub sad: u32,
}
fn sad_16x16_at(
src_y: &[u8],
src_stride: usize,
src_mb_x: usize,
src_mb_y: usize,
ref_y: &[u8],
ref_stride: usize,
ref_w: i32,
ref_h: i32,
int_x: i32,
int_y: i32,
) -> u32 {
let mut sad = 0u32;
for j in 0..16i32 {
let ry = (int_y + j).clamp(0, ref_h - 1) as usize;
let row_ref = &ref_y[ry * ref_stride..ry * ref_stride + ref_stride];
let row_src_off = (src_mb_y * 16 + j as usize) * src_stride + src_mb_x * 16;
let row_src = &src_y[row_src_off..row_src_off + 16];
for i in 0..16i32 {
let rx = (int_x + i).clamp(0, ref_w - 1) as usize;
let s = row_src[i as usize] as i32;
let r = row_ref[rx] as i32;
sad = sad.saturating_add((s - r).unsigned_abs());
}
}
sad
}
pub fn search_integer_16x16(
src_y: &[u8],
src_stride: usize,
src_w: u32,
src_h: u32,
ref_y: &[u8],
ref_stride: usize,
ref_w: u32,
ref_h: u32,
mb_x: usize,
mb_y: usize,
range_x: i32,
range_y: i32,
) -> MeResult {
let _ = src_w;
let _ = src_h;
let ref_w_i = ref_w as i32;
let ref_h_i = ref_h as i32;
let centre_x = (mb_x * 16) as i32;
let centre_y = (mb_y * 16) as i32;
let mut best_sad = u32::MAX;
let mut best_dx = 0i32;
let mut best_dy = 0i32;
let mut best_mv_cost = u32::MAX;
for dy in -range_y..=range_y {
for dx in -range_x..=range_x {
let int_x = centre_x + dx;
let int_y = centre_y + dy;
let sad = sad_16x16_at(
src_y, src_stride, mb_x, mb_y, ref_y, ref_stride, ref_w_i, ref_h_i, int_x, int_y,
);
let mv_cost = (dx.unsigned_abs()) + (dy.unsigned_abs());
if sad < best_sad || (sad == best_sad && mv_cost < best_mv_cost) {
best_sad = sad;
best_dx = dx;
best_dy = dy;
best_mv_cost = mv_cost;
}
}
}
MeResult {
mv_x: best_dx * 4, mv_y: best_dy * 4,
sad: best_sad,
}
}
fn sad_16x16_at_qpel(
src_y: &[u8],
src_stride: usize,
src_mb_x: usize,
src_mb_y: usize,
ref_i32: &[i32],
ref_stride: usize,
ref_w: usize,
ref_h: usize,
mb_x: usize,
mb_y: usize,
mv_qpel_x: i32,
mv_qpel_y: i32,
) -> u32 {
let int_x = (mb_x * 16) as i32 + (mv_qpel_x >> 2);
let int_y = (mb_y * 16) as i32 + (mv_qpel_y >> 2);
let x_frac = (mv_qpel_x & 3) as u8;
let y_frac = (mv_qpel_y & 3) as u8;
let mut pred = [0i32; 256];
interpolate_luma(
ref_i32, ref_stride, ref_w, ref_h, int_x, int_y, x_frac, y_frac, 16, 16, 8, &mut pred, 16,
)
.expect("luma interpolation");
let mut sad = 0u32;
for j in 0..16usize {
let row_src_off = (src_mb_y * 16 + j) * src_stride + src_mb_x * 16;
for i in 0..16usize {
let s = src_y[row_src_off + i] as i32;
let p = pred[j * 16 + i].clamp(0, 255);
sad = sad.saturating_add((s - p).unsigned_abs());
}
}
sad
}
pub fn refine_half_pel_16x16(
src_y: &[u8],
src_stride: usize,
src_w: u32,
src_h: u32,
ref_y: &[u8],
ref_stride: usize,
ref_w: u32,
ref_h: u32,
mb_x: usize,
mb_y: usize,
start_mv_qpel_x: i32,
start_mv_qpel_y: i32,
start_sad: u32,
) -> MeResult {
let _ = src_w;
let _ = src_h;
let ref_w_us = ref_w as usize;
let ref_h_us = ref_h as usize;
let ref_i32: Vec<i32> = ref_y
.iter()
.take(ref_stride * ref_h_us)
.map(|&v| v as i32)
.collect();
let mut best_mv_x = start_mv_qpel_x;
let mut best_mv_y = start_mv_qpel_y;
let mut best_sad = start_sad;
let mut best_mv_cost = (start_mv_qpel_x.unsigned_abs()) + (start_mv_qpel_y.unsigned_abs());
const HALF_OFFS: [(i32, i32); 8] = [
(-2, -2),
(0, -2),
(2, -2),
(-2, 0),
(2, 0),
(-2, 2),
(0, 2),
(2, 2),
];
for &(dx, dy) in &HALF_OFFS {
let mv_x = start_mv_qpel_x + dx;
let mv_y = start_mv_qpel_y + dy;
let sad = sad_16x16_at_qpel(
src_y, src_stride, mb_x, mb_y, &ref_i32, ref_stride, ref_w_us, ref_h_us, mb_x, mb_y,
mv_x, mv_y,
);
let mv_cost = (mv_x.unsigned_abs()) + (mv_y.unsigned_abs());
if sad < best_sad || (sad == best_sad && mv_cost < best_mv_cost) {
best_sad = sad;
best_mv_x = mv_x;
best_mv_y = mv_y;
best_mv_cost = mv_cost;
}
}
MeResult {
mv_x: best_mv_x,
mv_y: best_mv_y,
sad: best_sad,
}
}
pub fn search_half_pel_16x16(
src_y: &[u8],
src_stride: usize,
src_w: u32,
src_h: u32,
ref_y: &[u8],
ref_stride: usize,
ref_w: u32,
ref_h: u32,
mb_x: usize,
mb_y: usize,
range_x: i32,
range_y: i32,
) -> MeResult {
let int_best = search_integer_16x16(
src_y, src_stride, src_w, src_h, ref_y, ref_stride, ref_w, ref_h, mb_x, mb_y, range_x,
range_y,
);
refine_half_pel_16x16(
src_y,
src_stride,
src_w,
src_h,
ref_y,
ref_stride,
ref_w,
ref_h,
mb_x,
mb_y,
int_best.mv_x,
int_best.mv_y,
int_best.sad,
)
}
pub fn refine_quarter_pel_16x16(
src_y: &[u8],
src_stride: usize,
src_w: u32,
src_h: u32,
ref_y: &[u8],
ref_stride: usize,
ref_w: u32,
ref_h: u32,
mb_x: usize,
mb_y: usize,
start_mv_qpel_x: i32,
start_mv_qpel_y: i32,
start_sad: u32,
) -> MeResult {
let _ = src_w;
let _ = src_h;
let ref_w_us = ref_w as usize;
let ref_h_us = ref_h as usize;
let ref_i32: Vec<i32> = ref_y
.iter()
.take(ref_stride * ref_h_us)
.map(|&v| v as i32)
.collect();
let mut best_mv_x = start_mv_qpel_x;
let mut best_mv_y = start_mv_qpel_y;
let mut best_sad = start_sad;
let mut best_mv_cost = (start_mv_qpel_x.unsigned_abs()) + (start_mv_qpel_y.unsigned_abs());
const QUARTER_OFFS: [(i32, i32); 8] = [
(-1, -1),
(0, -1),
(1, -1),
(-1, 0),
(1, 0),
(-1, 1),
(0, 1),
(1, 1),
];
for &(dx, dy) in &QUARTER_OFFS {
let mv_x = start_mv_qpel_x + dx;
let mv_y = start_mv_qpel_y + dy;
let sad = sad_16x16_at_qpel(
src_y, src_stride, mb_x, mb_y, &ref_i32, ref_stride, ref_w_us, ref_h_us, mb_x, mb_y,
mv_x, mv_y,
);
let mv_cost = (mv_x.unsigned_abs()) + (mv_y.unsigned_abs());
if sad < best_sad || (sad == best_sad && mv_cost < best_mv_cost) {
best_sad = sad;
best_mv_x = mv_x;
best_mv_y = mv_y;
best_mv_cost = mv_cost;
}
}
MeResult {
mv_x: best_mv_x,
mv_y: best_mv_y,
sad: best_sad,
}
}
pub fn search_quarter_pel_16x16(
src_y: &[u8],
src_stride: usize,
src_w: u32,
src_h: u32,
ref_y: &[u8],
ref_stride: usize,
ref_w: u32,
ref_h: u32,
mb_x: usize,
mb_y: usize,
range_x: i32,
range_y: i32,
) -> MeResult {
let half_best = search_half_pel_16x16(
src_y, src_stride, src_w, src_h, ref_y, ref_stride, ref_w, ref_h, mb_x, mb_y, range_x,
range_y,
);
refine_quarter_pel_16x16(
src_y,
src_stride,
src_w,
src_h,
ref_y,
ref_stride,
ref_w,
ref_h,
mb_x,
mb_y,
half_best.mv_x,
half_best.mv_y,
half_best.sad,
)
}
pub fn p_l0_16x16_pred_bits(mvd_x: i32, mvd_y: i32) -> u64 {
let mut w = BitWriter::new();
w.ue(0); w.se(mvd_x);
w.se(mvd_y);
w.bits_emitted() as u64
}
pub fn p_8x8_pred_bits(mvds: &[(i32, i32); 4]) -> u64 {
let mut w = BitWriter::new();
w.ue(3); for _ in 0..4 {
w.ue(0); }
for &(mvd_x, mvd_y) in mvds.iter() {
w.se(mvd_x);
w.se(mvd_y);
}
w.bits_emitted() as u64
}
fn sad_8x8_at_qpel(
src_y: &[u8],
src_stride: usize,
src_mb_x: usize,
src_mb_y: usize,
sub_x: usize,
sub_y: usize,
ref_i32: &[i32],
ref_stride: usize,
ref_w: usize,
ref_h: usize,
mv_qpel_x: i32,
mv_qpel_y: i32,
) -> u32 {
let origin_x = (src_mb_x * 16 + sub_x * 8) as i32;
let origin_y = (src_mb_y * 16 + sub_y * 8) as i32;
let int_x = origin_x + (mv_qpel_x >> 2);
let int_y = origin_y + (mv_qpel_y >> 2);
let x_frac = (mv_qpel_x & 3) as u8;
let y_frac = (mv_qpel_y & 3) as u8;
let mut pred = [0i32; 64];
interpolate_luma(
ref_i32, ref_stride, ref_w, ref_h, int_x, int_y, x_frac, y_frac, 8, 8, 8, &mut pred, 8,
)
.expect("luma interpolation 8x8");
let mut sad = 0u32;
let src_origin_y = src_mb_y * 16 + sub_y * 8;
let src_origin_x = src_mb_x * 16 + sub_x * 8;
for j in 0..8usize {
let row_src_off = (src_origin_y + j) * src_stride + src_origin_x;
for i in 0..8usize {
let s = src_y[row_src_off + i] as i32;
let p = pred[j * 8 + i].clamp(0, 255);
sad = sad.saturating_add((s - p).unsigned_abs());
}
}
sad
}
fn sad_8x8_at_int(
src_y: &[u8],
src_stride: usize,
src_mb_x: usize,
src_mb_y: usize,
sub_x: usize,
sub_y: usize,
ref_y: &[u8],
ref_stride: usize,
ref_w: i32,
ref_h: i32,
int_x: i32,
int_y: i32,
) -> u32 {
let mut sad = 0u32;
let src_origin_y = src_mb_y * 16 + sub_y * 8;
let src_origin_x = src_mb_x * 16 + sub_x * 8;
for j in 0..8i32 {
let ry = (int_y + j).clamp(0, ref_h - 1) as usize;
let row_ref_off = ry * ref_stride;
let row_src_off = (src_origin_y + j as usize) * src_stride + src_origin_x;
for i in 0..8i32 {
let rx = (int_x + i).clamp(0, ref_w - 1) as usize;
let s = src_y[row_src_off + i as usize] as i32;
let r = ref_y[row_ref_off + rx] as i32;
sad = sad.saturating_add((s - r).unsigned_abs());
}
}
sad
}
#[allow(clippy::too_many_arguments)]
pub fn search_quarter_pel_8x8(
src_y: &[u8],
src_stride: usize,
_src_w: u32,
_src_h: u32,
ref_y: &[u8],
ref_stride: usize,
ref_w: u32,
ref_h: u32,
mb_x: usize,
mb_y: usize,
sub_x: usize,
sub_y: usize,
range_x: i32,
range_y: i32,
) -> MeResult {
let ref_w_i = ref_w as i32;
let ref_h_i = ref_h as i32;
let ref_w_us = ref_w as usize;
let ref_h_us = ref_h as usize;
let centre_x = (mb_x * 16 + sub_x * 8) as i32;
let centre_y = (mb_y * 16 + sub_y * 8) as i32;
let mut best_sad = u32::MAX;
let mut best_dx = 0i32;
let mut best_dy = 0i32;
let mut best_mv_cost = u32::MAX;
for dy in -range_y..=range_y {
for dx in -range_x..=range_x {
let int_x = centre_x + dx;
let int_y = centre_y + dy;
let sad = sad_8x8_at_int(
src_y, src_stride, mb_x, mb_y, sub_x, sub_y, ref_y, ref_stride, ref_w_i, ref_h_i,
int_x, int_y,
);
let mv_cost = (dx.unsigned_abs()) + (dy.unsigned_abs());
if sad < best_sad || (sad == best_sad && mv_cost < best_mv_cost) {
best_sad = sad;
best_dx = dx;
best_dy = dy;
best_mv_cost = mv_cost;
}
}
}
let mut best_mv_x = best_dx * 4;
let mut best_mv_y = best_dy * 4;
let ref_i32: Vec<i32> = ref_y
.iter()
.take(ref_stride * ref_h_us)
.map(|&v| v as i32)
.collect();
let mut best_mv_cost = (best_mv_x.unsigned_abs()) + (best_mv_y.unsigned_abs());
const HALF_OFFS: [(i32, i32); 8] = [
(-2, -2),
(0, -2),
(2, -2),
(-2, 0),
(2, 0),
(-2, 2),
(0, 2),
(2, 2),
];
for &(dx, dy) in &HALF_OFFS {
let mv_x = best_mv_x + dx;
let mv_y = best_mv_y + dy;
let sad = sad_8x8_at_qpel(
src_y, src_stride, mb_x, mb_y, sub_x, sub_y, &ref_i32, ref_stride, ref_w_us, ref_h_us,
mv_x, mv_y,
);
let mv_cost = (mv_x.unsigned_abs()) + (mv_y.unsigned_abs());
if sad < best_sad || (sad == best_sad && mv_cost < best_mv_cost) {
best_sad = sad;
best_mv_x = mv_x;
best_mv_y = mv_y;
best_mv_cost = mv_cost;
}
}
const QUARTER_OFFS: [(i32, i32); 8] = [
(-1, -1),
(0, -1),
(1, -1),
(-1, 0),
(1, 0),
(-1, 1),
(0, 1),
(1, 1),
];
for &(dx, dy) in &QUARTER_OFFS {
let mv_x = best_mv_x + dx;
let mv_y = best_mv_y + dy;
let sad = sad_8x8_at_qpel(
src_y, src_stride, mb_x, mb_y, sub_x, sub_y, &ref_i32, ref_stride, ref_w_us, ref_h_us,
mv_x, mv_y,
);
let mv_cost = (mv_x.unsigned_abs()) + (mv_y.unsigned_abs());
if sad < best_sad || (sad == best_sad && mv_cost < best_mv_cost) {
best_sad = sad;
best_mv_x = mv_x;
best_mv_y = mv_y;
best_mv_cost = mv_cost;
}
}
MeResult {
mv_x: best_mv_x,
mv_y: best_mv_y,
sad: best_sad,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_solid_frame(w: usize, h: usize, val: u8) -> Vec<u8> {
vec![val; w * h]
}
#[test]
fn search_zero_motion_on_identical_frames() {
let src = make_solid_frame(64, 64, 128);
let r = make_solid_frame(64, 64, 128);
let m = search_integer_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
assert_eq!(m.mv_x, 0);
assert_eq!(m.mv_y, 0);
assert_eq!(m.sad, 0);
}
#[test]
fn search_finds_offset_motion() {
let mut src = vec![0u8; 64 * 64];
let mut r = vec![0u8; 64 * 64];
for j in 16..32 {
for i in 16..32 {
let v = if ((i + j) % 2) == 0 { 200u8 } else { 50 };
src[j * 64 + i] = v;
r[j * 64 + i + 4] = v;
}
}
let m = search_integer_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 8, 8);
assert_eq!(m.mv_x, 4 * 4); assert_eq!(m.mv_y, 0);
assert_eq!(m.sad, 0);
}
#[test]
fn search_handles_picture_boundary_via_replication() {
let src = make_solid_frame(32, 32, 100);
let r = make_solid_frame(32, 32, 100);
let m = search_integer_16x16(&src, 32, 32, 32, &r, 32, 32, 32, 1, 1, 16, 16);
assert_eq!(m.sad, 0);
}
fn b_at(plane: &[u8], stride: usize, w: i32, h: i32, x: i32, y: i32) -> i32 {
let s = |xx: i32| -> i32 {
let cx = xx.clamp(0, w - 1) as usize;
let cy = y.clamp(0, h - 1) as usize;
plane[cy * stride + cx] as i32
};
let b1 = s(x - 2) - 5 * s(x - 1) + 20 * s(x) + 20 * s(x + 1) - 5 * s(x + 2) + s(x + 3);
((b1 + 16) >> 5).clamp(0, 255)
}
#[test]
fn half_pel_refinement_finds_horizontal_half_pel_motion() {
let mut r = vec![0u8; 64 * 64];
for j in 0..64 {
for i in 0..64 {
r[j * 64 + i] = (30 + i * 3).clamp(0, 255) as u8;
}
}
let mut src = vec![0u8; 64 * 64];
for j in 0..16i32 {
for i in 0..16i32 {
let x = 16 + i;
let y = 16 + j;
src[(y as usize) * 64 + x as usize] = b_at(&r, 64, 64, 64, x, y) as u8;
}
}
let res = search_half_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
assert_eq!(res.mv_x, 2, "expected mv_x = +½ pel (qpel +2)");
assert_eq!(res.mv_y, 0, "expected mv_y = 0");
assert_eq!(res.sad, 0, "predictor should match the synthetic source");
}
#[test]
fn half_pel_refinement_keeps_integer_when_better() {
let src = vec![123u8; 64 * 64];
let r = vec![123u8; 64 * 64];
let res = search_half_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
assert_eq!(res.mv_x, 0);
assert_eq!(res.mv_y, 0);
assert_eq!(res.sad, 0);
}
#[test]
fn half_pel_refinement_preserves_integer_winner_on_clean_integer_motion() {
let mut src = vec![100u8; 64 * 64];
let mut r = vec![100u8; 64 * 64];
for j in 16..32 {
for i in 16..32 {
let v = if ((i + j) % 2) == 0 { 200u8 } else { 50 };
src[j * 64 + i] = v;
r[j * 64 + i + 4] = v;
}
}
let int_only = search_integer_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 8, 8);
let refined = search_half_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 8, 8);
assert_eq!(int_only.mv_x, 16);
assert!(
refined.sad <= int_only.sad,
"half-pel refinement must not degrade SAD: int={} refined={}",
int_only.sad,
refined.sad,
);
assert_eq!(refined.mv_x, 16);
assert_eq!(refined.mv_y, 0);
assert_eq!(refined.sad, 0);
}
#[test]
fn pred_bits_simple_mvd() {
assert_eq!(p_l0_16x16_pred_bits(0, 0), 3);
assert_eq!(p_l0_16x16_pred_bits(1, 1), 7);
}
fn a_at(plane: &[u8], stride: usize, w: i32, h: i32, x: i32, y: i32) -> i32 {
let cx = x.clamp(0, w - 1) as usize;
let cy = y.clamp(0, h - 1) as usize;
let g = plane[cy * stride + cx] as i32;
let b = b_at(plane, stride, w, h, x, y);
(g + b + 1) >> 1
}
#[test]
fn quarter_pel_refinement_finds_quarter_pel_motion() {
let mut r = vec![0u8; 64 * 64];
for j in 0..64 {
for i in 0..64 {
r[j * 64 + i] = (30 + i * 3).clamp(0, 255) as u8;
}
}
let mut src = vec![0u8; 64 * 64];
for j in 0..16i32 {
for i in 0..16i32 {
let x = 16 + i;
let y = 16 + j;
src[(y as usize) * 64 + x as usize] = a_at(&r, 64, 64, 64, x, y) as u8;
}
}
let res = search_quarter_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
assert_eq!(res.mv_x, 1, "expected mv_x = +¼ pel (qpel +1)");
assert_eq!(res.mv_y, 0, "expected mv_y = 0");
assert_eq!(res.sad, 0, "predictor should match the synthetic source");
}
#[test]
fn quarter_pel_refinement_keeps_half_pel_when_better() {
let mut r = vec![0u8; 64 * 64];
for j in 0..64 {
for i in 0..64 {
r[j * 64 + i] = (30 + i * 3).clamp(0, 255) as u8;
}
}
let mut src = vec![0u8; 64 * 64];
for j in 0..16i32 {
for i in 0..16i32 {
let x = 16 + i;
let y = 16 + j;
src[(y as usize) * 64 + x as usize] = b_at(&r, 64, 64, 64, x, y) as u8;
}
}
let half = search_half_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
let qpel = search_quarter_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
assert_eq!(half.mv_x, 2);
assert_eq!(half.sad, 0);
assert!(qpel.sad <= half.sad);
assert_eq!(qpel.mv_x, 2);
assert_eq!(qpel.mv_y, 0);
assert_eq!(qpel.sad, 0);
}
#[test]
fn quarter_pel_refinement_keeps_integer_when_better() {
let src = vec![55u8; 64 * 64];
let r = vec![55u8; 64 * 64];
let res = search_quarter_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 4, 4);
assert_eq!(res.mv_x, 0);
assert_eq!(res.mv_y, 0);
assert_eq!(res.sad, 0);
}
#[test]
fn quarter_pel_refinement_does_not_degrade_clean_integer_motion() {
let mut src = vec![100u8; 64 * 64];
let mut r = vec![100u8; 64 * 64];
for j in 16..32 {
for i in 16..32 {
let v = if ((i + j) % 2) == 0 { 200u8 } else { 50 };
src[j * 64 + i] = v;
r[j * 64 + i + 4] = v;
}
}
let qpel = search_quarter_pel_16x16(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, 8, 8);
assert_eq!(qpel.mv_x, 16);
assert_eq!(qpel.mv_y, 0);
assert_eq!(qpel.sad, 0);
}
#[test]
fn search_8x8_zero_motion_on_identical_frames() {
let src = make_solid_frame(64, 64, 100);
let r = make_solid_frame(64, 64, 100);
for sub_idx in 0..4usize {
let sub_x = sub_idx % 2;
let sub_y = sub_idx / 2;
let m =
search_quarter_pel_8x8(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, sub_x, sub_y, 4, 4);
assert_eq!(m.mv_x, 0, "sub_idx={sub_idx}");
assert_eq!(m.mv_y, 0, "sub_idx={sub_idx}");
assert_eq!(m.sad, 0, "sub_idx={sub_idx}");
}
}
#[test]
fn search_8x8_finds_per_subblock_motion() {
let mvs: [(i32, i32); 4] = [(2, 0), (-2, 0), (0, 2), (0, -2)];
let mut src = vec![100u8; 64 * 64];
let mut r = vec![100u8; 64 * 64];
for j in 0..64 {
for i in 0..64 {
r[j * 64 + i] = ((i * 7 + j * 11) & 0xff) as u8;
}
}
for (sub_idx, &(mvx, mvy)) in mvs.iter().enumerate() {
let sub_x = sub_idx % 2;
let sub_y = sub_idx / 2;
for j in 0..8i32 {
for i in 0..8i32 {
let dst_x = (16 + sub_x as i32 * 8) + i;
let dst_y = (16 + sub_y as i32 * 8) + j;
let src_x = (dst_x + mvx).clamp(0, 63) as usize;
let src_y = (dst_y + mvy).clamp(0, 63) as usize;
src[(dst_y as usize) * 64 + dst_x as usize] = r[src_y * 64 + src_x];
}
}
}
for (sub_idx, &(mvx, mvy)) in mvs.iter().enumerate() {
let sub_x = sub_idx % 2;
let sub_y = sub_idx / 2;
let m =
search_quarter_pel_8x8(&src, 64, 64, 64, &r, 64, 64, 64, 1, 1, sub_x, sub_y, 4, 4);
assert_eq!(m.mv_x, mvx * 4, "sub_idx={sub_idx} mv=({mvx}, {mvy})");
assert_eq!(m.mv_y, mvy * 4, "sub_idx={sub_idx}");
assert_eq!(m.sad, 0, "sub_idx={sub_idx}");
}
}
#[test]
fn p_8x8_pred_bits_simple() {
assert_eq!(p_8x8_pred_bits(&[(0, 0); 4]), 17);
}
}