#[cfg(test)]
mod tests;
use std::{
mem::size_of,
num::{NonZeroU8, NonZeroUsize},
};
use anyhow::{Result, anyhow, ensure};
use semisafe::slice::get as semisafe_get;
use semisafe::slice::get_mut as semisafe_get_mut;
use smallvec::SmallVec;
use crate::{
analysis::MVAnalysisData,
degrain::{DegrainFn, select_degrain},
fake::group_of_planes::FakeGroupOfPlanes,
frame::{FramePlanesMut, FrameView, PlaneSizeTuple},
limit::{LimitChangesFn, select_limit_changes},
mv_gof::BorrowedSuperFrame,
overlaps::{OverlapWindows, OverlapsFn, ToPixelsFn, select_overlaps, select_to_pixels},
params::{MVPlaneSet, PlaneSelection},
util::{Pixel, uninit_vec, vs_bitblt},
video::{ColorFamily, SampleType, VideoInfo},
};
use super::analyse::SuperClipInfo;
pub const MAX_RADIUS: usize = 6;
pub const MAX_REFS_SIZE: usize = 2 * MAX_RADIUS;
#[derive(Debug, Clone, Copy)]
pub struct DegrainOptions {
pub thsad: u64,
pub thsad_chroma: u64,
pub plane: PlaneSelection,
pub limit: u16,
pub limit_chroma: u16,
pub thscd1: u64,
pub thscd2: u64,
}
struct DegrainFunctions {
overlaps: OverlapsFn,
limit_changes: LimitChangesFn,
overlaps_uv: OverlapsFn,
to_pixels: ToPixelsFn,
degrain: DegrainFn,
degrain_uv: DegrainFn,
}
pub struct Degrain<const RADIUS: usize> {
info: VideoInfo,
super_info: SuperClipInfo,
vectors_data: SmallVec<[MVAnalysisData; MAX_REFS_SIZE]>,
thsad: u64,
thsad_chroma: u64,
thscd1: u64,
thscd2: u64,
limit: u16,
limit_chroma: u16,
process: [bool; 3],
over_wins: Option<OverlapWindows>,
over_wins_uv: Option<OverlapWindows>,
dest_temp_stride_bytes: NonZeroUsize,
functions: DegrainFunctions,
}
impl<const RADIUS: usize> Degrain<RADIUS> {
#[inline]
pub fn new(
info: VideoInfo,
actual_super_width: usize,
super_info: SuperClipInfo,
vectors_data: SmallVec<[MVAnalysisData; MAX_REFS_SIZE]>,
options: DegrainOptions,
) -> Result<Self> {
ensure!(
vectors_data.len() == RADIUS * 2,
"Degrain: expected {} vector clips, got {}",
RADIUS * 2,
vectors_data.len()
);
validate_standard_input(info, "Degrain")?;
let first = *semisafe_get(&vectors_data, 0);
validate_super_geometry(info, actual_super_width, super_info, first, "Degrain")?;
let mut thscd1 = options.thscd1;
let mut thscd2 = options.thscd2;
let thscd1_before_scale = thscd1;
first.scale_thscd(&mut thscd1, &mut thscd2, "Degrain")?;
for r in 1..vectors_data.len() {
first.check_similarity(semisafe_get(&vectors_data, r), "Degrain", "mvbw", "mvfw")?;
}
for data in &vectors_data {
ensure!(
data.delta_frame > 0,
"Degrain: cannot use motion vectors with absolute frame references."
);
}
ensure!(
first.is_backward,
"Degrain: mvbw must be generated with isb=True."
);
ensure!(
!semisafe_get(&vectors_data, 1).is_backward,
"Degrain: mvfw must be generated with isb=False."
);
for thr in 1..RADIUS {
Self::check_vectors(thr, &vectors_data)?;
}
let thsad = options.thsad * thscd1 / thscd1_before_scale;
let thsad_chroma = options.thsad_chroma * thscd1 / thscd1_before_scale;
let pixel_max = ((1u32 << info.format.bits_per_sample.get()) - 1) as u16;
ensure!(
options.limit <= pixel_max,
"Degrain: limit must be between 0 and {pixel_max} (inclusive)"
);
ensure!(
options.limit_chroma <= pixel_max,
"Degrain: limitc must be between 0 and {pixel_max} (inclusive)"
);
let yuv_planes = MVPlaneSet::from(options.plane);
let process = [
yuv_planes.contains(MVPlaneSet::YPLANE),
yuv_planes
.intersection(super_info.mode_yuv)
.contains(MVPlaneSet::UPLANE),
yuv_planes
.intersection(super_info.mode_yuv)
.contains(MVPlaneSet::VPLANE),
];
let bytes_per_sample = info.format.bytes_per_sample.get() as usize;
let dest_temp_stride_bytes =
NonZeroUsize::new(((first.width.get() + 15) / 16) * 16 * bytes_per_sample * 2)
.expect("cannot be zero");
let blk_size_uv_x = unsafe {
NonZeroUsize::new_unchecked(first.blk_size_x.get() / first.x_ratio_uv.get() as usize)
};
let blk_size_uv_y = unsafe {
NonZeroUsize::new_unchecked(first.blk_size_y.get() / first.y_ratio_uv.get() as usize)
};
let (over_wins, over_wins_uv) = if first.overlap_x > 0 || first.overlap_y > 0 {
let over_wins = Some(OverlapWindows::new(
first.blk_size_x,
first.blk_size_y,
first.overlap_x,
first.overlap_y,
));
let over_wins_uv = yuv_planes
.intersection(super_info.mode_yuv)
.contains(MVPlaneSet::UVPLANES)
.then(|| {
OverlapWindows::new(
blk_size_uv_x,
blk_size_uv_y,
first.overlap_x / first.x_ratio_uv.get() as usize,
first.overlap_y / first.y_ratio_uv.get() as usize,
)
});
(over_wins, over_wins_uv)
} else {
(None, None)
};
let functions = Self::select_functions(
first.blk_size_x,
first.blk_size_y,
blk_size_uv_x,
blk_size_uv_y,
first.bits_per_sample,
);
Ok(Self {
info,
super_info,
vectors_data,
thsad,
thsad_chroma,
thscd1,
thscd2,
limit: options.limit,
limit_chroma: options.limit_chroma,
process,
over_wins,
over_wins_uv,
dest_temp_stride_bytes,
functions,
})
}
#[inline]
pub fn render_frame<T: Pixel>(
&self,
source: &FrameView<'_, T>,
output: &mut FramePlanesMut<'_, T>,
output_pitch: PlaneSizeTuple,
vectors: &[FakeGroupOfPlanes],
reference_supers: &[Option<&FrameView<'_, T>>],
) -> Result<()> {
ensure!(
vectors.len() == RADIUS * 2 && reference_supers.len() == RADIUS * 2,
"Degrain: render inputs must match the filter radius"
);
let first = *semisafe_get(&self.vectors_data, 0);
let x_ratio_uv = first.x_ratio_uv;
let y_ratio_uv = first.y_ratio_uv;
let x_ratio_uv_usize = x_ratio_uv.get() as usize;
let y_ratio_uv_usize = y_ratio_uv.get() as usize;
let blk_x = first.blk_x;
let blk_y = first.blk_y;
let width = first.width;
let width_uv = unsafe { NonZeroUsize::new_unchecked(width.get() / x_ratio_uv_usize) };
let height = first.height;
let height_uv = unsafe { NonZeroUsize::new_unchecked(height.get() / y_ratio_uv_usize) };
let overlap_x = first.overlap_x;
let overlap_x_uv = overlap_x / x_ratio_uv_usize;
let overlap_y = first.overlap_y;
let overlap_y_uv = overlap_y / y_ratio_uv_usize;
let blk_size_x = first.blk_size_x;
let blk_size_x_uv =
unsafe { NonZeroUsize::new_unchecked(blk_size_x.get() / x_ratio_uv_usize) };
let blk_size_y = first.blk_size_y;
let blk_size_y_uv =
unsafe { NonZeroUsize::new_unchecked(blk_size_y.get() / y_ratio_uv_usize) };
let width_b = unsafe {
NonZeroUsize::new_unchecked(blk_x.get() * (blk_size_x.get() - overlap_x) + overlap_x)
};
let width_b_uv = unsafe { NonZeroUsize::new_unchecked(width_b.get() / x_ratio_uv_usize) };
let height_b = unsafe {
NonZeroUsize::new_unchecked(blk_y.get() * (blk_size_y.get() - overlap_y) + overlap_y)
};
let height_b_uv = unsafe { NonZeroUsize::new_unchecked(height_b.get() / y_ratio_uv_usize) };
let log_pel = first.pel.log();
let bits_per_sample = first.bits_per_sample;
let pixel_max = ((1u32 << bits_per_sample.get()) - 1) as u16;
let source_strides_pixels = source.pitch();
let source_strides_bytes = (
pitch_to_bytes::<T>(source_strides_pixels.0),
source_strides_pixels.1.map(pitch_to_bytes::<T>),
source_strides_pixels.2.map(pitch_to_bytes::<T>),
);
let output_strides_pixels = output_pitch;
let output_strides_bytes = (
pitch_to_bytes::<T>(output_strides_pixels.0),
output_strides_pixels.1.map(pitch_to_bytes::<T>),
output_strides_pixels.2.map(pitch_to_bytes::<T>),
);
let mut borrowed_refs: SmallVec<[Option<BorrowedSuperFrame<'_, T>>; MAX_REFS_SIZE]> =
SmallVec::with_capacity(RADIUS * 2);
let mut is_usable: SmallVec<[bool; MAX_REFS_SIZE]> = SmallVec::with_capacity(RADIUS * 2);
for r in 0..(RADIUS * 2) {
let usable = semisafe_get(vectors, r).is_usable(self.thscd1, self.thscd2);
if usable {
let reference = semisafe_get(reference_supers, r)
.as_ref()
.copied()
.ok_or_else(|| {
anyhow!("Degrain: usable vector is missing its reference super frame")
})?;
borrowed_refs.push(Some(BorrowedSuperFrame::new(
reference,
self.super_info.levels,
first.width,
first.height,
self.super_info.pel,
self.super_info.hpad,
self.super_info.vpad,
self.super_info.mode_yuv,
first.x_ratio_uv,
first.y_ratio_uv,
bits_per_sample,
)?));
} else {
borrowed_refs.push(None);
}
is_usable.push(usable);
}
for plane in 0..self.info.format.plane_count() {
let src_plane = source.plane(plane)?;
let dest_plane = output.plane_mut(plane)?;
if !semisafe_get(&self.process, plane) {
dest_plane.copy_from_slice(src_plane);
continue;
}
if overlap_x == 0 && overlap_y == 0 {
match plane {
0 => self.plane_loop_no_overlap::<T, 0>(
blk_size_x,
blk_size_y,
blk_x,
blk_y,
&is_usable,
vectors,
&borrowed_refs,
source_strides_pixels.0,
log_pel,
x_ratio_uv,
y_ratio_uv,
self.thsad,
width,
width_b,
height,
height_b,
dest_plane,
output_strides_pixels.0,
src_plane,
),
1 => self.plane_loop_no_overlap::<T, 1>(
blk_size_x_uv,
blk_size_y_uv,
blk_x,
blk_y,
&is_usable,
vectors,
&borrowed_refs,
source_strides_pixels.1.expect("has chroma"),
log_pel,
x_ratio_uv,
y_ratio_uv,
self.thsad_chroma,
width_uv,
width_b_uv,
height_uv,
height_b_uv,
dest_plane,
output_strides_pixels.1.expect("has chroma"),
src_plane,
),
2 => self.plane_loop_no_overlap::<T, 2>(
blk_size_x_uv,
blk_size_y_uv,
blk_x,
blk_y,
&is_usable,
vectors,
&borrowed_refs,
source_strides_pixels.2.expect("has chroma"),
log_pel,
x_ratio_uv,
y_ratio_uv,
self.thsad_chroma,
width_uv,
width_b_uv,
height_uv,
height_b_uv,
dest_plane,
output_strides_pixels.2.expect("has chroma"),
src_plane,
),
_ => unreachable!(),
}
} else {
match plane {
0 => self.plane_loop_overlap::<T, 0>(
blk_size_x,
blk_size_y,
blk_x,
blk_y,
overlap_x,
overlap_y,
&is_usable,
vectors,
&borrowed_refs,
source_strides_pixels.0,
log_pel,
x_ratio_uv,
y_ratio_uv,
self.thsad,
width,
width_b,
height,
height_b,
dest_plane,
output_strides_pixels.0,
src_plane,
self.over_wins.as_ref().expect("has luma overlap windows"),
bits_per_sample,
),
1 => self.plane_loop_overlap::<T, 1>(
blk_size_x_uv,
blk_size_y_uv,
blk_x,
blk_y,
overlap_x_uv,
overlap_y_uv,
&is_usable,
vectors,
&borrowed_refs,
source_strides_pixels.1.expect("has chroma"),
log_pel,
x_ratio_uv,
y_ratio_uv,
self.thsad_chroma,
width_uv,
width_b_uv,
height_uv,
height_b_uv,
dest_plane,
output_strides_pixels.1.expect("has chroma"),
src_plane,
self.over_wins_uv
.as_ref()
.expect("has chroma overlap windows"),
bits_per_sample,
),
2 => self.plane_loop_overlap::<T, 2>(
blk_size_x_uv,
blk_size_y_uv,
blk_x,
blk_y,
overlap_x_uv,
overlap_y_uv,
&is_usable,
vectors,
&borrowed_refs,
source_strides_pixels.2.expect("has chroma"),
log_pel,
x_ratio_uv,
y_ratio_uv,
self.thsad_chroma,
width_uv,
width_b_uv,
height_uv,
height_b_uv,
dest_plane,
output_strides_pixels.2.expect("has chroma"),
src_plane,
self.over_wins_uv
.as_ref()
.expect("has chroma overlap windows"),
bits_per_sample,
),
_ => unreachable!(),
}
}
match plane {
0 if self.limit < pixel_max => {
unsafe {
(self.functions.limit_changes)(
dest_plane.as_mut_ptr().cast(),
output_strides_bytes.0,
src_plane.as_ptr().cast(),
source_strides_bytes.0,
width,
height,
self.limit,
);
}
}
1 if self.limit_chroma < pixel_max => {
unsafe {
(self.functions.limit_changes)(
dest_plane.as_mut_ptr().cast(),
output_strides_bytes.1.expect("has chroma"),
src_plane.as_ptr().cast(),
source_strides_bytes.1.expect("has chroma"),
width_uv,
height_uv,
self.limit_chroma,
);
}
}
2 if self.limit_chroma < pixel_max => {
unsafe {
(self.functions.limit_changes)(
dest_plane.as_mut_ptr().cast(),
output_strides_bytes.2.expect("has chroma"),
src_plane.as_ptr().cast(),
source_strides_bytes.2.expect("has chroma"),
width_uv,
height_uv,
self.limit_chroma,
);
}
}
_ => {}
}
}
Ok(())
}
fn check_vectors(thr: usize, vectors_data: &[MVAnalysisData]) -> Result<()> {
let backward_n = semisafe_get(vectors_data, thr * 2);
let forward_n = semisafe_get(vectors_data, thr * 2 + 1);
let backward_p = semisafe_get(vectors_data, (thr - 1) * 2);
let forward_p = semisafe_get(vectors_data, (thr - 1) * 2 + 1);
ensure!(
backward_n.is_backward,
"Degrain: mvbw must be generated with isb=True."
);
ensure!(
!forward_n.is_backward,
"Degrain: mvfw must be generated with isb=False."
);
ensure!(
backward_n.delta_frame > backward_p.delta_frame,
"Degrain: mvbwN must have greater delta than mvbwP."
);
ensure!(
forward_n.delta_frame > forward_p.delta_frame,
"Degrain: mvfwN must have greater delta than mvfwP."
);
Ok(())
}
fn select_functions(
blk_size_x: NonZeroUsize,
blk_size_y: NonZeroUsize,
chroma_blk_x: NonZeroUsize,
chroma_blk_y: NonZeroUsize,
bit_depth: NonZeroU8,
) -> DegrainFunctions {
match bit_depth.get() {
1..=8 => DegrainFunctions {
overlaps: select_overlaps::<u8>(blk_size_x, blk_size_y),
overlaps_uv: select_overlaps::<u8>(chroma_blk_x, chroma_blk_y),
to_pixels: select_to_pixels::<u8>(),
limit_changes: select_limit_changes::<u8>(),
degrain: select_degrain::<u8, RADIUS>(blk_size_x, blk_size_y),
degrain_uv: select_degrain::<u8, RADIUS>(chroma_blk_x, chroma_blk_y),
},
9..=16 => DegrainFunctions {
overlaps: select_overlaps::<u16>(blk_size_x, blk_size_y),
overlaps_uv: select_overlaps::<u16>(chroma_blk_x, chroma_blk_y),
to_pixels: select_to_pixels::<u16>(),
limit_changes: select_limit_changes::<u16>(),
degrain: select_degrain::<u16, RADIUS>(blk_size_x, blk_size_y),
degrain_uv: select_degrain::<u16, RADIUS>(chroma_blk_x, chroma_blk_y),
},
_ => unreachable!(),
}
}
fn plane_loop_no_overlap<T: Pixel, const PLANE: usize>(
&self,
blk_size_x: NonZeroUsize,
blk_size_y: NonZeroUsize,
blk_x: NonZeroUsize,
blk_y: NonZeroUsize,
is_usable: &[bool],
vectors: &[FakeGroupOfPlanes],
borrowed_refs: &[Option<BorrowedSuperFrame<'_, T>>],
src_stride_pixels: NonZeroUsize,
log_pel: usize,
x_sub_uv: NonZeroU8,
y_sub_uv: NonZeroU8,
th_sad: u64,
width: NonZeroUsize,
width_b: NonZeroUsize,
height: NonZeroUsize,
height_b: NonZeroUsize,
dest_plane: &mut [T],
dest_stride_pixels: NonZeroUsize,
src_plane: &[T],
) {
let src_stride_bytes = pitch_to_bytes::<T>(src_stride_pixels);
let dest_stride_bytes = pitch_to_bytes::<T>(dest_stride_pixels);
let blk_y = blk_y.get();
let blk_x = blk_x.get();
let mut src_current_offset = 0;
let mut dest_current_offset = 0;
for by in 0..blk_y {
let mut xx = 0;
for bx in 0..blk_x {
let i = by * blk_x + bx;
let mut strides: SmallVec<[NonZeroUsize; MAX_REFS_SIZE]> =
SmallVec::with_capacity(RADIUS * 2);
let mut w_src = 0;
let mut w_refs: SmallVec<[i32; MAX_REFS_SIZE]> =
SmallVec::with_capacity(RADIUS * 2);
let mut refs: SmallVec<[&[T]; MAX_REFS_SIZE]> = SmallVec::with_capacity(RADIUS * 2);
for r in 0..(RADIUS * 2) {
self.use_block::<T, PLANE>(
&mut refs,
&mut strides,
&mut w_refs,
*semisafe_get(is_usable, r),
semisafe_get(vectors, r),
i,
semisafe_get(borrowed_refs, r).as_ref(),
src_current_offset,
xx,
src_stride_bytes,
log_pel,
x_sub_uv,
y_sub_uv,
th_sad,
src_plane,
);
}
normalize_weights::<RADIUS>(&mut w_src, &mut w_refs);
let refs = refs
.into_iter()
.map(|r| r.as_ptr().cast())
.collect::<Box<[_]>>();
unsafe {
if PLANE == 0 {
(self.functions.degrain)(
dest_plane.as_mut_ptr().add(dest_current_offset + xx).cast(),
dest_stride_bytes,
src_plane.as_ptr().add(src_current_offset + xx).cast(),
src_stride_bytes,
&refs,
&strides,
w_src,
&w_refs,
);
} else {
(self.functions.degrain_uv)(
dest_plane.as_mut_ptr().add(dest_current_offset + xx).cast(),
dest_stride_bytes,
src_plane.as_ptr().add(src_current_offset + xx).cast(),
src_stride_bytes,
&refs,
&strides,
w_src,
&w_refs,
);
}
}
xx += blk_size_x.get();
if bx == blk_x - 1 && width > width_b {
let uncovered_width =
unsafe { NonZeroUsize::new_unchecked(width.get() - width_b.get()) };
vs_bitblt(
semisafe_get_mut(dest_plane, (dest_current_offset + width_b.get())..),
dest_stride_pixels,
semisafe_get(src_plane, (src_current_offset + width_b.get())..),
src_stride_pixels,
uncovered_width,
blk_size_y,
);
}
}
dest_current_offset += blk_size_y.get() * dest_stride_pixels.get();
src_current_offset += blk_size_y.get() * src_stride_pixels.get();
if by == blk_y - 1 && height > height_b {
let uncovered_height =
unsafe { NonZeroUsize::new_unchecked(height.get() - height_b.get()) };
vs_bitblt(
semisafe_get_mut(dest_plane, dest_current_offset..),
dest_stride_pixels,
semisafe_get(src_plane, src_current_offset..),
src_stride_pixels,
width,
uncovered_height,
);
}
}
}
fn plane_loop_overlap<T: Pixel, const PLANE: usize>(
&self,
blk_size_x: NonZeroUsize,
blk_size_y: NonZeroUsize,
blk_x: NonZeroUsize,
blk_y: NonZeroUsize,
overlap_x: usize,
overlap_y: usize,
is_usable: &[bool],
vectors: &[FakeGroupOfPlanes],
borrowed_refs: &[Option<BorrowedSuperFrame<'_, T>>],
src_stride_pixels: NonZeroUsize,
log_pel: usize,
x_sub_uv: NonZeroU8,
y_sub_uv: NonZeroU8,
th_sad: u64,
width: NonZeroUsize,
width_b: NonZeroUsize,
height: NonZeroUsize,
height_b: NonZeroUsize,
dest_plane: &mut [T],
dest_stride_pixels: NonZeroUsize,
src_plane: &[T],
over_wins: &OverlapWindows,
bits_per_sample: NonZeroU8,
) {
let src_stride_bytes = pitch_to_bytes::<T>(src_stride_pixels);
let dest_stride_bytes = pitch_to_bytes::<T>(dest_stride_pixels);
let temp_block_stride_bytes = pitch_to_bytes::<T>(blk_size_x);
let blk_y = blk_y.get();
let blk_x = blk_x.get();
let mut src_current_offset = 0;
let mut dest_temp_current_offset = 0;
let mut temp_block = uninit_vec::<u8>(temp_block_stride_bytes.get() * height.get());
let mut dest_temp = vec![0u8; self.dest_temp_stride_bytes.get() * height_b.get()];
for by in 0..blk_y {
let wby = ((by + blk_y - 3) / (blk_y - 2)) * 3;
let mut wbx = 0;
let mut xx = 0;
for bx in 0..blk_x {
wbx = if bx == blk_x - 1 { 2 } else { wbx };
let win_over = over_wins.get_window(wby + wbx);
let i = by * blk_x + bx;
let mut strides: SmallVec<[NonZeroUsize; MAX_REFS_SIZE]> =
SmallVec::with_capacity(RADIUS * 2);
let mut w_src = 0;
let mut w_refs: SmallVec<[i32; MAX_REFS_SIZE]> =
SmallVec::with_capacity(RADIUS * 2);
let mut refs: SmallVec<[&[T]; MAX_REFS_SIZE]> = SmallVec::with_capacity(RADIUS * 2);
for r in 0..(RADIUS * 2) {
self.use_block::<T, PLANE>(
&mut refs,
&mut strides,
&mut w_refs,
*semisafe_get(is_usable, r),
semisafe_get(vectors, r),
i,
semisafe_get(borrowed_refs, r).as_ref(),
src_current_offset,
xx,
src_stride_bytes,
log_pel,
x_sub_uv,
y_sub_uv,
th_sad,
src_plane,
);
}
normalize_weights::<RADIUS>(&mut w_src, &mut w_refs);
let refs = refs
.into_iter()
.map(|r| r.as_ptr().cast())
.collect::<Box<[_]>>();
unsafe {
if PLANE == 0 {
(self.functions.degrain)(
temp_block.as_mut_ptr().cast(),
temp_block_stride_bytes,
src_plane.as_ptr().add(src_current_offset + xx).cast(),
src_stride_bytes,
&refs,
&strides,
w_src,
&w_refs,
);
let temp_x_offset_bytes = xx * size_of::<T>() * 2;
(self.functions.overlaps)(
dest_temp
.as_mut_ptr()
.add(dest_temp_current_offset + temp_x_offset_bytes)
.cast(),
self.dest_temp_stride_bytes,
temp_block.as_ptr().cast(),
temp_block_stride_bytes,
win_over.as_ptr(),
blk_size_x,
);
} else {
(self.functions.degrain_uv)(
temp_block.as_mut_ptr().cast(),
temp_block_stride_bytes,
src_plane.as_ptr().add(src_current_offset + xx).cast(),
src_stride_bytes,
&refs,
&strides,
w_src,
&w_refs,
);
let temp_x_offset_bytes = xx * size_of::<T>() * 2;
(self.functions.overlaps_uv)(
dest_temp
.as_mut_ptr()
.add(dest_temp_current_offset + temp_x_offset_bytes)
.cast(),
self.dest_temp_stride_bytes,
temp_block.as_ptr().cast(),
temp_block_stride_bytes,
win_over.as_ptr(),
blk_size_x,
);
}
}
xx += blk_size_x.get() - overlap_x;
wbx = 1;
}
src_current_offset += (blk_size_y.get() - overlap_y) * src_stride_pixels.get();
dest_temp_current_offset +=
(blk_size_y.get() - overlap_y) * self.dest_temp_stride_bytes.get();
}
unsafe {
(self.functions.to_pixels)(
dest_plane.as_mut_ptr().cast(),
dest_stride_bytes,
dest_temp.as_ptr(),
self.dest_temp_stride_bytes,
width_b,
height_b,
bits_per_sample,
);
}
if width > width_b {
let uncovered_width =
unsafe { NonZeroUsize::new_unchecked(width.get() - width_b.get()) };
vs_bitblt(
semisafe_get_mut(dest_plane, width_b.get()..),
dest_stride_pixels,
semisafe_get(src_plane, width_b.get()..),
src_stride_pixels,
uncovered_width,
height_b,
);
}
if height > height_b {
let uncovered_height =
unsafe { NonZeroUsize::new_unchecked(height.get() - height_b.get()) };
vs_bitblt(
semisafe_get_mut(dest_plane, (dest_stride_pixels.get() * height_b.get())..),
dest_stride_pixels,
semisafe_get(src_plane, (src_stride_pixels.get() * height_b.get())..),
src_stride_pixels,
width,
uncovered_height,
);
}
}
fn use_block<'a, T: Pixel, const PLANE: usize>(
&self,
refs: &mut SmallVec<[&'a [T]; MAX_REFS_SIZE]>,
strides: &mut SmallVec<[NonZeroUsize; MAX_REFS_SIZE]>,
w_refs: &mut SmallVec<[i32; MAX_REFS_SIZE]>,
is_usable: bool,
vectors: &FakeGroupOfPlanes,
i: usize,
borrowed_ref: Option<&'a BorrowedSuperFrame<'a, T>>,
src_offset: usize,
xx: usize,
src_stride_bytes: NonZeroUsize,
log_pel: usize,
x_sub_uv: NonZeroU8,
y_sub_uv: NonZeroU8,
th_sad: u64,
src_plane: &'a [T],
) {
if is_usable {
let borrowed_ref = borrowed_ref.expect("usable ref present");
let mv_plane = borrowed_ref.mv_plane(PLANE);
let block = vectors.get_block(0, i);
let blx = (block.x << log_pel) + block.vector.x;
let bly = (block.y << log_pel) + block.vector.y;
let offset = mv_plane.get_pix_offset(
if PLANE == 0 {
blx
} else {
blx / x_sub_uv.get() as i32
},
if PLANE == 0 {
bly
} else {
bly / y_sub_uv.get() as i32
},
);
let ref_stride_bytes = pitch_to_bytes::<T>(mv_plane.stride);
strides.push(ref_stride_bytes);
refs.push(semisafe_get(
borrowed_ref.plane(PLANE).expect("plane available"),
offset..,
));
w_refs.push(degrain_weight(th_sad as i64, block.vector.sad));
} else {
refs.push(semisafe_get(src_plane, (src_offset + xx)..));
strides.push(src_stride_bytes);
w_refs.push(0);
}
}
}
fn degrain_weight(th_sad: i64, block_sad: i64) -> i32 {
if block_sad > th_sad {
return 0;
}
(((th_sad - block_sad) * (th_sad + block_sad) * 256) as f64
/ (th_sad * th_sad + block_sad * block_sad) as f64) as i32
}
fn normalize_weights<const RADIUS: usize>(w_src: &mut i32, w_refs: &mut [i32]) {
*w_src = 256;
let mut w_sum = *w_src + 1;
for r in 0..(RADIUS * 2) {
w_sum += *semisafe_get(w_refs, r);
}
let scale = 256.0 / w_sum as f64;
for r in 0..(RADIUS * 2) {
let w_ref = semisafe_get_mut(w_refs, r);
*w_ref = (*w_ref as f64 * scale) as i32;
*w_src -= *w_ref;
}
}
fn validate_standard_input(info: VideoInfo, filter_name: &str) -> Result<()> {
ensure!(
info.format.bits_per_sample.get() <= 16
&& info.format.sample_type == SampleType::Integer
&& [ColorFamily::Yuv, ColorFamily::Gray].contains(&info.format.color_family)
&& info.format.sub_sampling_w <= 1
&& info.format.sub_sampling_h <= 1,
"{filter_name}: input clip must be GRAY, 420, 422, 440, or 444, up to 16 bits, with constant dimensions."
);
Ok(())
}
fn validate_super_geometry(
info: VideoInfo,
actual_super_width: usize,
super_info: SuperClipInfo,
vectors_data: MVAnalysisData,
filter_name: &str,
) -> Result<()> {
let vectors_super_width = super_info
.hpad
.checked_mul(2)
.and_then(|padding| actual_super_width.checked_sub(padding))
.ok_or_else(|| anyhow!("{filter_name}: wrong source or super clip frame size"))?;
ensure!(
vectors_data.height == super_info.height
&& vectors_data.width.get() == vectors_super_width
&& vectors_data.width.get() == info.resolution.width
&& vectors_data.height.get() == info.resolution.height
&& vectors_data.pel == super_info.pel
&& vectors_data.h_padding == super_info.hpad
&& vectors_data.v_padding == super_info.vpad,
"{filter_name}: wrong source or super clip frame size"
);
Ok(())
}
const fn pitch_to_bytes<T: Pixel>(pitch_pixels: NonZeroUsize) -> NonZeroUsize {
unsafe { NonZeroUsize::new_unchecked(pitch_pixels.get() * size_of::<T>()) }
}