mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-28 20:47:15 -06:00
- crates/media: probe (duration, rate/channels, size/fps; WMA/WMV recognised), audio decode to f32 PCM (MP3, AAC M4A/ADTS, ALAC, WAV, AIFF, CAF, FLAC, Vorbis via symphonia; Opus with our decoder in Ogg/WebM/MKV), video decode (MP4/MOV/WebM/MKV) with YUV->RGBA, poster frames, trim/fade/volume, Player mixer + per-voice clock (device-clocked or wall-clocked), VideoFeed. - Codec crates ported from FilmCraft: bitstream, h264, hevc, vp9, av1, isobmff, matroska, ogg, opus. - insert.audio/insert.video probe and store duration/size and a poster frame; unplayable media is embedded with a warning. New media.play/pause/toggle/stop/seek/info/posterFrame. - UI: MediaHost (cpal output injected by the desktop app), control bar under media, video frames on canvas and in the show; autoplay, click to play, loop, rewind, across slides, hide, full screen. - Tests with in-code WAV/AIFF and CC0 self-generated fixtures (make.sh, ATTRIBUTION.md rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
514 lines
20 KiB
Rust
514 lines
20 KiB
Rust
//! Inter prediction sample interpolation (8.4.2.2) and weighted sample prediction (8.4.2.3).
|
|
|
|
/// A reference plane (used by the plane-based helpers in tests).
|
|
#[cfg(test)]
|
|
#[derive(Clone, Copy)]
|
|
pub struct PlaneRef<'a> {
|
|
pub data: &'a [u8],
|
|
pub width: usize,
|
|
pub height: usize,
|
|
pub stride: usize,
|
|
}
|
|
|
|
const WIN: usize = 16 + 5;
|
|
|
|
/// Copy the (bw+5)x(bh+5) window whose top-left is (x-2, y-2) into `win` (stride WIN), clamping
|
|
/// coordinates to the picture.
|
|
#[cfg(test)]
|
|
#[inline]
|
|
fn fetch_window(p: PlaneRef, x: i32, y: i32, bw: usize, bh: usize, win: &mut [u8; WIN * WIN]) {
|
|
let x0 = x - 2;
|
|
let y0 = y - 2;
|
|
let ww = bw + 5;
|
|
let wh = bh + 5;
|
|
let maxx = p.width as i32 - 1;
|
|
let maxy = p.height as i32 - 1;
|
|
if x0 >= 0 && y0 >= 0 && x0 + ww as i32 - 1 <= maxx && y0 + wh as i32 - 1 <= maxy {
|
|
for r in 0..wh {
|
|
let src = (y0 as usize + r) * p.stride + x0 as usize;
|
|
win[r * WIN..r * WIN + ww].copy_from_slice(&p.data[src..src + ww]);
|
|
}
|
|
} else {
|
|
for r in 0..wh {
|
|
let yy = (y0 + r as i32).clamp(0, maxy) as usize;
|
|
let row = &p.data[yy * p.stride..yy * p.stride + p.width];
|
|
for c in 0..ww {
|
|
let xx = (x0 + c as i32).clamp(0, maxx) as usize;
|
|
win[r * WIN + c] = row[xx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn tap6(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {
|
|
a - 5 * b + 20 * c + 20 * d - 5 * e + f
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn clip1(v: i32) -> u8 {
|
|
v.clamp(0, 255) as u8
|
|
}
|
|
|
|
/// Copy `n` bytes with a fixed-size move for the common block widths.
|
|
#[inline(always)]
|
|
fn copy_n(dst: &mut [u8], src: &[u8], n: usize) {
|
|
fn fixed<const N: usize>(dst: &mut [u8], src: &[u8]) {
|
|
if let (Some(d), Some(s)) = (dst.first_chunk_mut::<N>(), src.first_chunk::<N>()) {
|
|
*d = *s;
|
|
}
|
|
}
|
|
match n {
|
|
16 => fixed::<16>(dst, src),
|
|
8 => fixed::<8>(dst, src),
|
|
4 => fixed::<4>(dst, src),
|
|
2 => fixed::<2>(dst, src),
|
|
_ => dst[..n].copy_from_slice(&src[..n]),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn avg(a: u8, b: u8) -> u8 {
|
|
((a as u16 + b as u16 + 1) >> 1) as u8
|
|
}
|
|
|
|
/// Horizontal 6-tap (unclipped) at every position of `row` for `n` outputs: out[c] uses row[c..c + 6].
|
|
#[inline(always)]
|
|
fn htap_row(row: &[u8], out: &mut [i32], n: usize) {
|
|
let row = &row[..n + 5];
|
|
for (o, w) in out[..n].iter_mut().zip(row.windows(6)) {
|
|
*o = tap6(w[0] as i32, w[1] as i32, w[2] as i32, w[3] as i32, w[4] as i32, w[5] as i32);
|
|
}
|
|
}
|
|
|
|
/// Vertical 6-tap (unclipped) over six rows at columns 0..n.
|
|
#[inline(always)]
|
|
fn vtap_row(rows: [&[u8]; 6], out: &mut [i32], n: usize) {
|
|
let [a, b, c, d, e, f] = rows.map(|r| &r[..n]);
|
|
for i in 0..n {
|
|
out[i] = tap6(a[i] as i32, b[i] as i32, c[i] as i32, d[i] as i32, e[i] as i32, f[i] as i32);
|
|
}
|
|
}
|
|
|
|
/// Luma sample interpolation for a bw x bh block (8.4.2.2.1). (x, y) is the integer sample position
|
|
/// (block position + (mv >> 2)); (fx, fy) the quarter-sample fraction. Output stride is `os`.
|
|
#[cfg(test)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn mc_luma(p: PlaneRef, x: i32, y: i32, fx: u32, fy: u32, bw: usize, bh: usize, out: &mut [u8], os: usize) {
|
|
let mut win = [0u8; WIN * WIN];
|
|
let (x0, y0) = (x - 2, y - 2);
|
|
let inside = x0 >= 0 && y0 >= 0 && x0 as usize + bw + 5 <= p.width && y0 as usize + bh + 5 <= p.height;
|
|
let (src, ss): (&[u8], usize) = if inside {
|
|
(&p.data[y0 as usize * p.stride + x0 as usize..], p.stride)
|
|
} else {
|
|
fetch_window(p, x, y, bw, bh, &mut win);
|
|
(&win[..], WIN)
|
|
};
|
|
mc_luma_win(src, ss, fx, fy, bw, bh, out, os);
|
|
}
|
|
|
|
/// Luma interpolation from a prepared window: `src[0]` is the sample at (x - 2, y - 2) and `ss` the
|
|
/// window stride; the window covers (bw + 5) x (bh + 5) samples.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn mc_luma_win(src: &[u8], ss: usize, fx: u32, fy: u32, bw: usize, bh: usize, out: &mut [u8], os: usize) {
|
|
// window row r (0..bh+5) starts at src[r * ss]; sample G of block (r, c) is at window (r + 2, c + 2)
|
|
let wrow = |r: usize| &src[r * ss..r * ss + bw + 5];
|
|
let mut t = [0i32; 16];
|
|
match (fx, fy) {
|
|
(0, 0) => {
|
|
for r in 0..bh {
|
|
copy_n(&mut out[r * os..], &wrow(r + 2)[2..], bw);
|
|
}
|
|
}
|
|
(_, 0) => {
|
|
for r in 0..bh {
|
|
let row = wrow(r + 2);
|
|
htap_row(row, &mut t, bw);
|
|
let o = &mut out[r * os..r * os + bw];
|
|
match fx {
|
|
1 => {
|
|
for c in 0..bw {
|
|
o[c] = avg(row[c + 2], clip1((t[c] + 16) >> 5));
|
|
}
|
|
}
|
|
2 => {
|
|
for c in 0..bw {
|
|
o[c] = clip1((t[c] + 16) >> 5);
|
|
}
|
|
}
|
|
_ => {
|
|
for c in 0..bw {
|
|
o[c] = avg(row[c + 3], clip1((t[c] + 16) >> 5));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
(0, _) => {
|
|
for r in 0..bh {
|
|
let rows = [wrow(r), wrow(r + 1), wrow(r + 2), wrow(r + 3), wrow(r + 4), wrow(r + 5)].map(|x| &x[2..]);
|
|
vtap_row(rows, &mut t, bw);
|
|
let g = if fy == 1 { rows[2] } else { rows[3] };
|
|
let o = &mut out[r * os..r * os + bw];
|
|
if fy == 2 {
|
|
for c in 0..bw {
|
|
o[c] = clip1((t[c] + 16) >> 5);
|
|
}
|
|
} else {
|
|
for c in 0..bw {
|
|
o[c] = avg(g[c], clip1((t[c] + 16) >> 5));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
(2, _) | (_, 2) => {
|
|
// unclipped horizontal half samples b1 for window rows 0..bh+5
|
|
let mut b1 = [[0i32; 16]; WIN];
|
|
for (r, bt) in b1.iter_mut().enumerate().take(bh + 5) {
|
|
htap_row(wrow(r), bt, bw);
|
|
}
|
|
let mut v = [0i32; 16];
|
|
for r in 0..bh {
|
|
let o = &mut out[r * os..r * os + bw];
|
|
let (b0, b1r, b2, b3, b4, b5) = (&b1[r], &b1[r + 1], &b1[r + 2], &b1[r + 3], &b1[r + 4], &b1[r + 5]);
|
|
for c in 0..bw {
|
|
t[c] = tap6(b0[c], b1r[c], b2[c], b3[c], b4[c], b5[c]);
|
|
}
|
|
match (fx, fy) {
|
|
(2, 2) => {
|
|
for c in 0..bw {
|
|
o[c] = clip1((t[c] + 512) >> 10);
|
|
}
|
|
}
|
|
(2, 1) | (2, 3) => {
|
|
let bh_ = if fy == 1 { b2 } else { b3 };
|
|
for c in 0..bw {
|
|
o[c] = avg(clip1((bh_[c] + 16) >> 5), clip1((t[c] + 512) >> 10));
|
|
}
|
|
}
|
|
_ => {
|
|
let off = if fx == 1 { 2 } else { 3 };
|
|
let rows = [wrow(r), wrow(r + 1), wrow(r + 2), wrow(r + 3), wrow(r + 4), wrow(r + 5)].map(|x| &x[off..]);
|
|
vtap_row(rows, &mut v, bw);
|
|
for c in 0..bw {
|
|
o[c] = avg(clip1((v[c] + 16) >> 5), clip1((t[c] + 512) >> 10));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
// e, g, p, r: average of a horizontal half sample (row r or r + 1) and a vertical one (col c or c + 1)
|
|
let hoff = if fy == 1 { 2 } else { 3 };
|
|
let voff = if fx == 1 { 2 } else { 3 };
|
|
let mut v = [0i32; 16];
|
|
for r in 0..bh {
|
|
htap_row(wrow(r + hoff), &mut t, bw);
|
|
let rows = [wrow(r), wrow(r + 1), wrow(r + 2), wrow(r + 3), wrow(r + 4), wrow(r + 5)].map(|x| &x[voff..]);
|
|
vtap_row(rows, &mut v, bw);
|
|
let o = &mut out[r * os..r * os + bw];
|
|
for c in 0..bw {
|
|
o[c] = avg(clip1((t[c] + 16) >> 5), clip1((v[c] + 16) >> 5));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Chroma sample interpolation (8.4.2.2.2) for a bw x bh block at integer position (x, y) with
|
|
/// eighth-sample fraction (fx, fy).
|
|
#[cfg(test)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn mc_chroma(p: PlaneRef, x: i32, y: i32, fx: u32, fy: u32, bw: usize, bh: usize, out: &mut [u8], os: usize) {
|
|
let maxx = p.width as i32 - 1;
|
|
let maxy = p.height as i32 - 1;
|
|
let inside = x >= 0 && y >= 0 && x + bw as i32 <= maxx && y + bh as i32 <= maxy;
|
|
// (bw+1) x (bh+1) source window; copied with edge clamping when it crosses the picture border
|
|
let mut win = [0u8; 9 * 9];
|
|
let (src, ss): (&[u8], usize) = if inside {
|
|
(&p.data[y as usize * p.stride + x as usize..], p.stride)
|
|
} else {
|
|
for r in 0..=bh {
|
|
let yy = (y + r as i32).clamp(0, maxy) as usize;
|
|
for c in 0..=bw {
|
|
win[r * 9 + c] = p.data[yy * p.stride + (x + c as i32).clamp(0, maxx) as usize];
|
|
}
|
|
}
|
|
(&win[..], 9)
|
|
};
|
|
mc_chroma_win(src, ss, fx, fy, bw, bh, out, os);
|
|
}
|
|
|
|
/// Chroma interpolation from a prepared (bw + 1) x (bh + 1) window whose first sample is the integer
|
|
/// position of the block.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn mc_chroma_win(src: &[u8], ss: usize, fx: u32, fy: u32, bw: usize, bh: usize, out: &mut [u8], os: usize) {
|
|
let (fx, fy) = (fx as u16, fy as u16);
|
|
let (w00, w10, w01, w11) = ((8 - fx) * (8 - fy), fx * (8 - fy), (8 - fx) * fy, fx * fy);
|
|
if fx == 0 && fy == 0 {
|
|
for r in 0..bh {
|
|
copy_n(&mut out[r * os..], &src[r * ss..], bw);
|
|
}
|
|
return;
|
|
}
|
|
for r in 0..bh {
|
|
let r0 = &src[r * ss..r * ss + bw + 1];
|
|
let r1 = &src[(r + 1) * ss..(r + 1) * ss + bw + 1];
|
|
let o = &mut out[r * os..r * os + bw];
|
|
for (c, o) in o.iter_mut().enumerate() {
|
|
let v = w00 * r0[c] as u16 + w10 * r0[c + 1] as u16 + w01 * r1[c] as u16 + w11 * r1[c + 1] as u16;
|
|
*o = ((v + 32) >> 6) as u8;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Weights for one prediction direction or pair.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Weight {
|
|
/// Default: copy (single list) or rounded average (bi).
|
|
Default,
|
|
/// Explicit/implicit weighting: log2 denominator, weights and offsets for L0 and L1.
|
|
Weighted { log_wd: i32, w0: i32, w1: i32, o0: i32, o1: i32 },
|
|
}
|
|
|
|
/// Combine prediction(s) into `dst` (stride `ds`). `p0`/`p1` have stride `ps`.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn weighted_store(dst: &mut [u8], ds: usize, p0: Option<&[u8]>, p1: Option<&[u8]>, ps: usize, bw: usize, bh: usize, w: Weight) {
|
|
match (p0, p1, w) {
|
|
(Some(a), Some(b), Weight::Default) => {
|
|
for r in 0..bh {
|
|
let d = &mut dst[r * ds..r * ds + bw];
|
|
let ra = &a[r * ps..r * ps + bw];
|
|
let rb = &b[r * ps..r * ps + bw];
|
|
for ((d, &x), &y) in d.iter_mut().zip(ra).zip(rb) {
|
|
*d = ((x as u32 + y as u32 + 1) >> 1) as u8;
|
|
}
|
|
}
|
|
}
|
|
(Some(a), Some(b), Weight::Weighted { log_wd, w0, w1, o0, o1 }) => {
|
|
let round = 1 << log_wd;
|
|
let off = (o0 + o1 + 1) >> 1;
|
|
let sh = log_wd + 1;
|
|
for r in 0..bh {
|
|
let d = &mut dst[r * ds..r * ds + bw];
|
|
let ra = &a[r * ps..r * ps + bw];
|
|
let rb = &b[r * ps..r * ps + bw];
|
|
for ((d, &x), &y) in d.iter_mut().zip(ra).zip(rb) {
|
|
*d = clip1(((x as i32 * w0 + y as i32 * w1 + round) >> sh) + off);
|
|
}
|
|
}
|
|
}
|
|
(Some(a), None, w) | (None, Some(a), w) => {
|
|
let (wt, o, log_wd) = match (w, p0.is_some()) {
|
|
(Weight::Default, _) => {
|
|
for r in 0..bh {
|
|
copy_n(&mut dst[r * ds..], &a[r * ps..], bw);
|
|
}
|
|
return;
|
|
}
|
|
(Weight::Weighted { w0, o0, log_wd, .. }, true) => (w0, o0, log_wd),
|
|
(Weight::Weighted { w1, o1, log_wd, .. }, false) => (w1, o1, log_wd),
|
|
};
|
|
let round = if log_wd >= 1 { 1 << (log_wd - 1) } else { 0 };
|
|
for r in 0..bh {
|
|
let d = &mut dst[r * ds..r * ds + bw];
|
|
let ra = &a[r * ps..r * ps + bw];
|
|
for (d, &x) in d.iter_mut().zip(ra) {
|
|
*d = clip1(((x as i32 * wt + round) >> log_wd) + o);
|
|
}
|
|
}
|
|
}
|
|
(None, None, _) => {}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod reference {
|
|
use super::*;
|
|
|
|
/// Luma sample interpolation for a bw x bh block. (x, y) is the integer sample position
|
|
/// (block position + (mv >> 2)); (fx, fy) the quarter-sample fraction. Output stride is `os`.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn mc_luma_ref(p: PlaneRef, x: i32, y: i32, fx: u32, fy: u32, bw: usize, bh: usize, out: &mut [u8], os: usize) {
|
|
let mut win = [0u8; WIN * WIN];
|
|
fetch_window(p, x, y, bw, bh, &mut win);
|
|
// sample G at (r, c) in block coordinates is win[(r + 2) * WIN + c + 2]
|
|
let g = |r: usize, c: usize| win[(r + 2) * WIN + c + 2] as i32;
|
|
// unclipped horizontal half-sample b1 at block row r (-2..bh+3 via offset) and column c
|
|
let b1 = |r: isize, c: usize| {
|
|
let base = ((r + 2) as usize) * WIN + c;
|
|
tap6(win[base] as i32, win[base + 1] as i32, win[base + 2] as i32, win[base + 3] as i32, win[base + 4] as i32, win[base + 5] as i32)
|
|
};
|
|
let h1 = |r: usize, c: isize| {
|
|
let col = (c + 2) as usize;
|
|
tap6(
|
|
win[r * WIN + col] as i32,
|
|
win[(r + 1) * WIN + col] as i32,
|
|
win[(r + 2) * WIN + col] as i32,
|
|
win[(r + 3) * WIN + col] as i32,
|
|
win[(r + 4) * WIN + col] as i32,
|
|
win[(r + 5) * WIN + col] as i32,
|
|
)
|
|
};
|
|
match (fx, fy) {
|
|
(0, 0) => {
|
|
for r in 0..bh {
|
|
out[r * os..r * os + bw].copy_from_slice(&win[(r + 2) * WIN + 2..(r + 2) * WIN + 2 + bw]);
|
|
}
|
|
}
|
|
(_, 0) => {
|
|
// a, b, c
|
|
for r in 0..bh {
|
|
for c in 0..bw {
|
|
let b = clip1((b1(r as isize, c) + 16) >> 5) as i32;
|
|
out[r * os + c] = match fx {
|
|
1 => ((g(r, c) + b + 1) >> 1) as u8,
|
|
2 => b as u8,
|
|
_ => ((g(r, c + 1) + b + 1) >> 1) as u8,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
(0, _) => {
|
|
// d, h, n
|
|
for r in 0..bh {
|
|
for c in 0..bw {
|
|
let h = clip1((h1(r, c as isize) + 16) >> 5) as i32;
|
|
out[r * os + c] = match fy {
|
|
1 => ((g(r, c) + h + 1) >> 1) as u8,
|
|
2 => h as u8,
|
|
_ => ((g(r + 1, c) + h + 1) >> 1) as u8,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
(2, _) | (_, 2) => {
|
|
// j-based: j needs b1 for rows -2..bh+3
|
|
let mut bcol = [0i32; WIN * WIN];
|
|
for r in 0..bh + 5 {
|
|
for c in 0..bw {
|
|
bcol[r * WIN + c] = b1(r as isize - 2, c);
|
|
}
|
|
}
|
|
for r in 0..bh {
|
|
for c in 0..bw {
|
|
let j1 = tap6(
|
|
bcol[r * WIN + c],
|
|
bcol[(r + 1) * WIN + c],
|
|
bcol[(r + 2) * WIN + c],
|
|
bcol[(r + 3) * WIN + c],
|
|
bcol[(r + 4) * WIN + c],
|
|
bcol[(r + 5) * WIN + c],
|
|
);
|
|
let j = clip1((j1 + 512) >> 10) as i32;
|
|
let v = match (fx, fy) {
|
|
(2, 2) => j,
|
|
(2, 1) => (clip1((bcol[(r + 2) * WIN + c] + 16) >> 5) as i32 + j + 1) >> 1, // f
|
|
(2, 3) => (clip1((bcol[(r + 3) * WIN + c] + 16) >> 5) as i32 + j + 1) >> 1, // q
|
|
(1, 2) => (clip1((h1(r, c as isize) + 16) >> 5) as i32 + j + 1) >> 1, // i
|
|
_ => (clip1((h1(r, c as isize + 1) + 16) >> 5) as i32 + j + 1) >> 1, // k
|
|
};
|
|
out[r * os + c] = v as u8;
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
// e, g, p, r: average of a horizontal half-sample (b or s) and a vertical one (h or m)
|
|
for r in 0..bh {
|
|
for c in 0..bw {
|
|
let hr = if fy == 1 { r as isize } else { r as isize + 1 };
|
|
let vc = if fx == 1 { c as isize } else { c as isize + 1 };
|
|
let bh_ = clip1((b1(hr, c) + 16) >> 5) as i32;
|
|
let vv = clip1((h1(r, vc) + 16) >> 5) as i32;
|
|
out[r * os + c] = ((bh_ + vv + 1) >> 1) as u8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn plane(w: usize, h: usize, f: impl Fn(usize, usize) -> u8) -> Vec<u8> {
|
|
(0..w * h).map(|i| f(i % w, i / w)).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn full_sample_copy_and_clamp() {
|
|
let d = plane(16, 16, |x, y| (x + 16 * y) as u8);
|
|
let p = PlaneRef { data: &d, width: 16, height: 16, stride: 16 };
|
|
let mut out = [0u8; 16];
|
|
mc_luma(p, 2, 3, 0, 0, 4, 4, &mut out, 4);
|
|
assert_eq!(&out[..4], &[50, 51, 52, 53]);
|
|
// clamped: far outside to the top-left gives sample (0,0)
|
|
mc_luma(p, -40, -40, 0, 0, 4, 4, &mut out, 4);
|
|
assert!(out.iter().all(|&v| v == 0));
|
|
}
|
|
|
|
#[test]
|
|
fn half_sample_constant_is_constant() {
|
|
let d = vec![100u8; 32 * 32];
|
|
let p = PlaneRef { data: &d, width: 32, height: 32, stride: 32 };
|
|
for fx in 0..4 {
|
|
for fy in 0..4 {
|
|
let mut out = [0u8; 64];
|
|
mc_luma(p, 8, 8, fx, fy, 8, 8, &mut out, 8);
|
|
assert!(out.iter().all(|&v| v == 100), "{fx},{fy}");
|
|
mc_chroma(p, 8, 8, fx * 2, fy * 2, 8, 8, &mut out, 8);
|
|
assert!(out.iter().all(|&v| v == 100));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn half_sample_horizontal_ramp() {
|
|
// linear ramp: 6-tap filter of a linear function is exact: b = (G + H) / 2 (rounded)
|
|
let d = plane(32, 32, |x, _| (x * 4) as u8);
|
|
let p = PlaneRef { data: &d, width: 32, height: 32, stride: 32 };
|
|
let mut out = [0u8; 16];
|
|
mc_luma(p, 8, 8, 2, 0, 4, 4, &mut out, 4);
|
|
assert_eq!(&out[..4], &[34, 38, 42, 46]);
|
|
mc_luma(p, 8, 8, 1, 0, 4, 4, &mut out, 4);
|
|
assert_eq!(&out[..4], &[33, 37, 41, 45]);
|
|
}
|
|
|
|
#[test]
|
|
fn fast_luma_matches_reference() {
|
|
let mut seed = 7u32;
|
|
let d: Vec<u8> = (0..40 * 36)
|
|
.map(|_| {
|
|
seed = seed.wrapping_mul(1664525).wrapping_add(1013904223);
|
|
(seed >> 24) as u8
|
|
})
|
|
.collect();
|
|
let p = PlaneRef { data: &d, width: 40, height: 36, stride: 40 };
|
|
for &(bw, bh) in &[(16, 16), (16, 8), (8, 16), (8, 8), (8, 4), (4, 8), (4, 4)] {
|
|
for fx in 0..4 {
|
|
for fy in 0..4 {
|
|
for &(x, y) in &[(10, 9), (-3, -5), (30, 30), (0, 0), (2, 2), (38, 1)] {
|
|
let mut a = [0u8; 256];
|
|
let mut b = [0u8; 256];
|
|
mc_luma(p, x, y, fx, fy, bw, bh, &mut a, 16);
|
|
reference::mc_luma_ref(p, x, y, fx, fy, bw, bh, &mut b, 16);
|
|
assert_eq!(a, b, "{bw}x{bh} frac {fx},{fy} at {x},{y}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn weighting() {
|
|
let a = [100u8; 4];
|
|
let b = [50u8; 4];
|
|
let mut d = [0u8; 4];
|
|
weighted_store(&mut d, 4, Some(&a), Some(&b), 4, 4, 1, Weight::Default);
|
|
assert_eq!(d, [75; 4]);
|
|
weighted_store(&mut d, 4, Some(&a), None, 4, 4, 1, Weight::Weighted { log_wd: 5, w0: 16, w1: 0, o0: 3, o1: 0 });
|
|
assert_eq!(d, [53; 4]);
|
|
}
|
|
}
|