use crate::cabac::{CabacEncoder, Contexts};
use crate::fmt::ChromaFormat;
use crate::headers::Headers;
use crate::intra::{
DC_IDX, HOR_IDX, NUM_LUMA_MODE, PLANAR_IDX, VER_IDX, build_mpm, chroma_422_mode,
chroma_cand_modes, encode_bdpcm_mode, encode_chroma_mode, encode_luma_mode,
};
use crate::partition::{CTU_SIZE, code_partitions};
use crate::predict::{RefSamples, predict_into};
use crate::transform::{
dequantize_ts_wh, dequantize_wh, fwd_transform_wh_into, inv_transform_wh,
inv_transform_wh_into, quantize_ts_wh, quantize_wh, rdoq_wh, satd,
};
use crate::tu::{TreeType, TuCoeffs, encode_transform_tree};
use std::sync::atomic::{AtomicI32, AtomicU8, Ordering::Relaxed};
const RDO_CANDIDATES: usize = 4;
#[allow(dead_code)] fn gather_refs(
recon: &[i32],
avail: &[bool],
cw: usize,
ch: usize,
x: usize,
y: usize,
n: usize,
bd: u8,
) -> RefSamples {
gather_refs_wh(recon, avail, cw, ch, x, y, n, n, bd)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn gather_refs_wh(
recon: &[i32],
avail: &[bool],
cw: usize,
ch: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
bd: u8,
) -> RefSamples {
let corner = if x > 0 && y > 0 && avail[(y - 1) * cw + (x - 1)] {
Some(recon[(y - 1) * cw + (x - 1)])
} else {
None
};
let above: Vec<Option<i32>> = (0..2 * bw)
.map(|i| {
let xx = x + i;
if y > 0 && xx < cw && avail[(y - 1) * cw + xx] {
Some(recon[(y - 1) * cw + xx])
} else {
None
}
})
.collect();
let left: Vec<Option<i32>> = (0..2 * bh)
.map(|i| {
let yy = y + i;
if x > 0 && yy < ch && avail[yy * cw + (x - 1)] {
Some(recon[yy * cw + (x - 1)])
} else {
None
}
})
.collect();
RefSamples::build(bw, bh, corner, &above, &left, bd)
}
fn analyze_wh(
src_blk: &[i32],
pred: &[i32],
bw: usize,
bh: usize,
qp: u8,
bd: u8,
lambda: f64,
lfnst_idx: u8,
lfnst_mode: usize,
dep_quant: bool,
) -> Vec<i32> {
thread_local! {
static FWD: std::cell::RefCell<(Vec<i32>, Vec<i32>)> =
std::cell::RefCell::new((vec![0; 64 * 64], vec![0; 64 * 64]));
}
let nn = bw * bh;
FWD.with(|c| {
let (res, coeff) = &mut *c.borrow_mut();
for (r, (&s, &p)) in res[..nn].iter_mut().zip(src_blk.iter().zip(pred)) {
*r = s - p;
}
fwd_transform_wh_into(&mut coeff[..nn], &res[..nn], bw, bh, bd);
if lfnst_idx > 0 {
crate::lfnst::apply_fwd_lfnst(&mut coeff[..nn], bw, bh, lfnst_mode, lfnst_idx as usize);
}
let levels = if dep_quant {
return crate::transform::dq_trellis_wh(&coeff[..nn], bw, bh, qp, bd, lambda);
} else if lambda > 0.0 {
rdoq_wh(&coeff[..nn], bw, bh, qp, bd, lambda)
} else {
quantize_wh(&coeff[..nn], bw, bh, qp, bd)
};
levels[..nn].iter().map(|&l| l as i32).collect()
})
}
fn residual_levels_wh(
src_blk: &[i32],
pred: &[i32],
bw: usize,
bh: usize,
qp: u8,
bd: u8,
lossless: bool,
lambda: f64,
dep_quant: bool,
) -> Vec<i32> {
if lossless {
(0..bw * bh).map(|i| src_blk[i] - pred[i]).collect()
} else {
analyze_wh(src_blk, pred, bw, bh, qp, bd, lambda, 0, 0, dep_quant)
}
}
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)] fn reconstruct(
recon: &mut [i32],
avail: &mut [bool],
cw: usize,
x: usize,
y: usize,
n: usize,
pred: &[i32],
levels: &[i32],
qp: u8,
bd: u8,
max_val: i32,
lossless: bool,
ts: bool,
lfnst_idx: u8,
lfnst_mode: usize,
dep_quant: bool,
mts_idx: u8,
) {
reconstruct_wh(
recon, avail, cw, x, y, n, n, pred, levels, qp, bd, max_val, lossless, ts, lfnst_idx,
lfnst_mode, dep_quant, mts_idx,
);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn reconstruct_wh(
recon: &mut [i32],
avail: &mut [bool],
cw: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
pred: &[i32],
levels: &[i32],
qp: u8,
bd: u8,
max_val: i32,
lossless: bool,
ts: bool,
lfnst_idx: u8,
lfnst_mode: usize,
dep_quant: bool,
mts_idx: u8,
) {
let nn = bw * bh;
let add_into = |recon: &mut [i32], avail: &mut [bool], res: &[i32]| {
for (yy, (pred_row, res_row)) in pred
.chunks_exact(bw)
.zip(res.chunks_exact(bw))
.take(bh)
.enumerate()
{
let row = (y + yy) * cw + x;
for (((r, a), &p), &rs) in recon[row..row + bw]
.iter_mut()
.zip(avail[row..row + bw].iter_mut())
.zip(pred_row)
.zip(res_row)
{
*r = (p + rs).clamp(0, max_val);
*a = true;
}
}
};
if lossless {
add_into(recon, avail, &levels[..nn]);
} else {
let lv: Vec<i16> = levels[..nn].iter().map(|&l| l as i16).collect();
if ts {
let deq = dequantize_ts_wh(&lv, bw, bh, qp);
add_into(recon, avail, &deq[..nn]);
} else {
let mut deq = if dep_quant {
crate::transform::dequantize_dq_wh(
&levels[..nn],
crate::residual::scan_coords(bw, bh),
bw,
bh,
qp,
bd,
)
} else {
dequantize_wh(&lv, bw, bh, qp, bd)
};
if lfnst_idx > 0 {
crate::lfnst::apply_inv_lfnst(
&mut deq[..nn],
bw,
bh,
lfnst_mode,
lfnst_idx as usize,
15,
);
}
let inv = if mts_idx > 0 {
crate::transform::inv_transform_mts_wh(&deq[..nn], bw, bh, bd, mts_idx)
} else {
inv_transform_wh(&deq[..nn], bw, bh, bd)
};
add_into(recon, avail, &inv[..nn]);
}
}
}
fn dpcm_levels_wh(src: &[i32], refs: &RefSamples, bw: usize, bh: usize, dir: u8) -> Vec<i32> {
let mut lv = vec![0i32; bw * bh];
if dir == 1 {
for (y, (lv_row, src_row)) in lv
.chunks_exact_mut(bw)
.zip(src.chunks_exact(bw))
.enumerate()
{
let mut prev = refs.left[y + 1];
for (d, &sv) in lv_row.iter_mut().zip(src_row) {
*d = sv - prev;
prev = sv;
}
}
} else {
let mut prev_row: &[i32] = &refs.top[1..1 + bw];
for (lv_row, src_row) in lv.chunks_exact_mut(bw).zip(src.chunks_exact(bw)) {
for ((d, &sv), &p) in lv_row.iter_mut().zip(src_row).zip(prev_row) {
*d = sv - p;
}
prev_row = src_row;
}
}
lv
}
fn satd_wh(block: &[i32], bw: usize, bh: usize) -> i64 {
if bw == bh {
return satd(block, bw);
}
let s = bw.min(bh);
let mut total = 0i64;
let mut tile = vec![0i32; s * s];
let mut ty = 0;
while ty < bh {
let mut tx = 0;
while tx < bw {
for yy in 0..s {
for xx in 0..s {
tile[yy * s + xx] = block[(ty + yy) * bw + (tx + xx)];
}
}
total += satd(&tile, s);
tx += s;
}
ty += s;
}
total
}
#[allow(dead_code)] fn place_block(
recon: &mut [i32],
avail: &mut [bool],
cw: usize,
x: usize,
y: usize,
n: usize,
src_blk: &[i32],
) {
place_block_wh(recon, avail, cw, x, y, n, n, src_blk);
}
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)] fn place_block_wh(
recon: &mut [i32],
avail: &mut [bool],
cw: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
src_blk: &[i32],
) {
for (yy, blk_row) in src_blk.chunks_exact(bw).take(bh).enumerate() {
let off = (y + yy) * cw + x;
recon[off..off + bw].copy_from_slice(blk_row);
avail[off..off + bw].fill(true);
}
}
fn reconstruct_block_wh(
pred: &[i32],
levels: &[i32],
bw: usize,
bh: usize,
qp: u8,
bd: u8,
max_val: i32,
lossless: bool,
ts: bool,
lfnst_idx: u8,
lfnst_mode: usize,
dep_quant: bool,
mts_idx: u8,
) -> Vec<i32> {
let nn = bw * bh;
let res: Vec<i32> = if lossless {
levels[..nn].to_vec()
} else {
let lv: Vec<i16> = levels[..nn].iter().map(|&l| l as i16).collect();
if ts {
dequantize_ts_wh(&lv, bw, bh, qp)[..nn].to_vec()
} else {
let mut deq = if dep_quant {
crate::transform::dequantize_dq_wh(
&levels[..nn],
crate::residual::scan_coords(bw, bh),
bw,
bh,
qp,
bd,
)
} else {
dequantize_wh(&lv, bw, bh, qp, bd)
};
if lfnst_idx > 0 {
crate::lfnst::apply_inv_lfnst(
&mut deq[..nn],
bw,
bh,
lfnst_mode,
lfnst_idx as usize,
15,
);
}
if mts_idx > 0 {
crate::transform::inv_transform_mts_wh(&deq[..nn], bw, bh, bd, mts_idx)[..nn]
.to_vec()
} else {
inv_transform_wh(&deq[..nn], bw, bh, bd)[..nn].to_vec()
}
}
};
(0..nn)
.map(|i| (pred[i] + res[i]).clamp(0, max_val))
.collect()
}
#[allow(clippy::too_many_arguments)]
fn gather_refs_atomic_wh(
recon: &[AtomicI32],
avail: &[AtomicU8],
cw: usize,
ch: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
bd: u8,
) -> RefSamples {
let av = |i: usize| avail[i].load(Relaxed) != 0;
let rc = |i: usize| recon[i].load(Relaxed);
let corner = if x > 0 && y > 0 && av((y - 1) * cw + (x - 1)) {
Some(rc((y - 1) * cw + (x - 1)))
} else {
None
};
let above: Vec<Option<i32>> = (0..2 * bw)
.map(|i| {
let xx = x + i;
if y > 0 && xx < cw && av((y - 1) * cw + xx) {
Some(rc((y - 1) * cw + xx))
} else {
None
}
})
.collect();
let left: Vec<Option<i32>> = (0..2 * bh)
.map(|i| {
let yy = y + i;
if x > 0 && yy < ch && av(yy * cw + (x - 1)) {
Some(rc(yy * cw + (x - 1)))
} else {
None
}
})
.collect();
RefSamples::build(bw, bh, corner, &above, &left, bd)
}
#[allow(clippy::too_many_arguments)]
fn place_atomic_wh(
recon: &[AtomicI32],
avail: &[AtomicU8],
cw: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
block: &[i32],
) {
for (yy, blk_row) in block.chunks_exact(bw).take(bh).enumerate() {
let row = (y + yy) * cw + x;
for ((r, a), &v) in recon[row..row + bw]
.iter()
.zip(&avail[row..row + bw])
.zip(blk_row)
{
r.store(v, Relaxed);
a.store(1, Relaxed);
}
}
}
#[allow(clippy::too_many_arguments)]
fn reconstruct_atomic_wh(
recon: &[AtomicI32],
avail: &[AtomicU8],
cw: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
pred: &[i32],
levels: &[i32],
qp: u8,
bd: u8,
max_val: i32,
lossless: bool,
ts: bool,
lfnst_idx: u8,
lfnst_mode: usize,
dep_quant: bool,
mts_idx: u8,
) {
let blk = reconstruct_block_wh(
pred, levels, bw, bh, qp, bd, max_val, lossless, ts, lfnst_idx, lfnst_mode, dep_quant,
mts_idx,
);
place_atomic_wh(recon, avail, cw, x, y, bw, bh, &blk);
}
fn extract_block_wh_into(
dst: &mut [i32],
src: &[i32],
cw: usize,
x: usize,
y: usize,
bw: usize,
bh: usize,
) {
for (yy, dst_row) in dst.chunks_exact_mut(bw).take(bh).enumerate() {
let row = (y + yy) * cw + x;
dst_row.copy_from_slice(&src[row..row + bw]);
}
}
fn block_variance(src: &[i32], cw: usize, x: usize, y: usize, n: usize) -> i64 {
let (mut sum, mut sumsq) = (0i64, 0i64);
for yy in 0..n {
let row = (y + yy) * cw + x;
for &sv in &src[row..row + n] {
let v = sv as i64;
sum += v;
sumsq += v * v;
}
}
let count = (n * n) as i64;
let mean = sum / count;
sumsq / count - mean * mean
}
const AQ_STRENGTH: f64 = 1.0;
const AQ_RANGE: i32 = 6;
const AQ_QP_MIN: i32 = 7;
const AQ_QP_MAX: i32 = 51;
pub(crate) const AQ_QG: usize = 32;
pub(crate) const AQ_CU_QP_DELTA_SUBDIV: u32 = 4;
fn region_variance(src: &[i32], cw: usize, ch: usize, x: usize, y: usize, ctu: usize) -> f64 {
let bw = ctu.min(cw - x);
let bh = ctu.min(ch - y);
let (mut sum, mut sumsq) = (0i64, 0i64);
for yy in 0..bh {
let row = (y + yy) * cw + x;
for &sv in &src[row..row + bw] {
let v = sv as i64;
sum += v;
sumsq += v * v;
}
}
let count = (bw * bh).max(1) as i64;
let mean = sum / count;
(sumsq / count - mean * mean).max(0) as f64
}
fn aq_qg_targets(
src_y: &[i32],
cw: usize,
ch: usize,
qg: usize,
qg_cols: usize,
qg_rows: usize,
base_qp: u8,
) -> Vec<i32> {
let n = qg_cols * qg_rows;
let mut energy = vec![0f64; n];
for r in 0..qg_rows {
for c in 0..qg_cols {
let v = region_variance(src_y, cw, ch, c * qg, r * qg, qg);
energy[r * qg_cols + c] = (1.0 + v).log2();
}
}
let mean = energy.iter().sum::<f64>() / n as f64;
let lo = AQ_QP_MIN.min(base_qp as i32);
energy
.iter()
.map(|&e| {
let off = (AQ_STRENGTH * (e - mean)).round() as i32;
(base_qp as i32 + off.clamp(-AQ_RANGE, AQ_RANGE)).clamp(lo, AQ_QP_MAX)
})
.collect()
}
pub(crate) fn aq_predict_qp(
qg_map: &[i32],
qg_cols: usize,
qg: usize,
ctu: usize,
qg_x: usize,
qg_y: usize,
prev_qp: i32,
) -> i32 {
let cell = |px: usize, py: usize| qg_map[(py / qg) * qg_cols + px / qg];
if qg_x == 0 && qg_y.is_multiple_of(ctu) && qg_y > 0 {
cell(qg_x, qg_y - 1)
} else {
let a = if !qg_y.is_multiple_of(ctu) {
cell(qg_x, qg_y - 1)
} else {
prev_qp
};
let b = if !qg_x.is_multiple_of(ctu) {
cell(qg_x - 1, qg_y)
} else {
prev_qp
};
(a + b + 1) >> 1
}
}
pub(crate) fn aq_fill_qg(
qg_map: &mut [i32],
qg_cols: usize,
qg: usize,
x: usize,
y: usize,
n: usize,
qp: i32,
) {
let span = if n >= qg { n / qg } else { 1 };
let (ox, oy) = (x / qg, y / qg);
for cy in 0..span {
for cx in 0..span {
let i = (oy + cy) * qg_cols + ox + cx;
if ox + cx < qg_cols && i < qg_map.len() {
qg_map[i] = qp;
}
}
}
}
#[allow(dead_code)]
struct CoreOutput {
stream: Vec<u8>,
slice_data: Vec<u8>,
rec_y: Vec<i32>,
rec_cb: Vec<i32>,
rec_cr: Vec<i32>,
cw: usize,
ch: usize,
cwc: usize,
chc: usize,
qp: u8,
bit_depth: u8,
leaf_count: usize,
}
const FWD_PREC: i32 = 13;
const FWD_BIAS: i32 = (1 << (FWD_PREC - 1)) - 1; const FWD_YR: i32 = 2449;
const FWD_YG: i32 = 4809;
const FWD_YB: i32 = 934;
const FWD_CB_R: i32 = -1382;
const FWD_CB_G: i32 = -2714;
const FWD_CB_B: i32 = 4096;
const FWD_CR_R: i32 = 4096;
const FWD_CR_G: i32 = -3430;
const FWD_CR_B: i32 = -666;
#[inline(always)]
fn rgb_to_y_q13(r: i32, g: i32, b: i32) -> i32 {
(FWD_YR * r + FWD_YG * g + FWD_YB * b + FWD_BIAS) >> FWD_PREC
}
fn encode_core<S: Copy + Into<i32>>(
rgb: &[S],
width: u32,
height: u32,
qp: u8,
bit_depth: u8,
stride_px: usize,
lossless: bool,
chroma: ChromaFormat,
scale_shift: u32,
threads: usize,
rdoq: bool,
aq: bool,
mtt: bool,
lfnst: bool,
dep_quant: bool,
mts: bool,
dual_tree: bool,
cclm: bool,
deblock: bool,
) -> CoreOutput {
let chroma = chroma.for_dimensions(width, height);
let dual = dual_tree && !lossless && chroma.allows_dual_tree();
let lfnst_eff = lfnst;
let dep_quant_eff = dep_quant;
let headers = Headers {
width,
height,
chroma,
bit_depth: crate::fmt::BitDepth::from_bits(bit_depth),
qp,
lossless,
aq: aq && !lossless,
mtt: mtt && !lossless,
lfnst: lfnst_eff,
dep_quant: dep_quant_eff,
mts,
dual_tree: dual,
cclm: cclm && !lossless,
deblock: deblock && bit_depth == 8 && !aq && !dual && !lossless && !mtt,
};
let cw = headers.coded_width() as usize;
let ch = headers.coded_height() as usize;
let (w, h) = (width as usize, height as usize);
let shift = scale_shift;
let mid = 1i32 << (bit_depth - 1);
let max_val = (1i32 << bit_depth) - 1;
let mut src_y = vec![0i32; cw * ch];
let (sub_w, sub_h) = (chroma.sub_w(), chroma.sub_h());
let has_chroma = !chroma.is_monochrome();
let cwc = if has_chroma { cw / sub_w } else { 0 };
let chc = if has_chroma { ch / sub_h } else { 0 };
let mut src_cb = vec![0i32; cwc * chc];
let mut src_cr = vec![0i32; cwc * chc];
let sample = |x: usize, y: usize| {
let sx = x.min(w - 1);
let sy = y.min(h - 1);
let o = (sy * w + sx) * stride_px;
(rgb[o].into(), rgb[o + 1].into(), rgb[o + 2].into())
};
for y in 0..ch {
for x in 0..cw {
let (r, g, b) = sample(x, y);
src_y[y * cw + x] = (rgb_to_y_q13(r, g, b) << shift).clamp(0, max_val);
}
}
let region_shift = (sub_w * sub_h).trailing_zeros();
let region_half = (1i32 << region_shift) >> 1; for cy in 0..chc {
for cx in 0..cwc {
let (mut sr, mut sg, mut sb) = (0i32, 0i32, 0i32);
for dy in 0..sub_h {
for dx in 0..sub_w {
let (r, g, b) = sample(cx * sub_w + dx, cy * sub_h + dy);
sr += r;
sg += g;
sb += b;
}
}
let r = (sr + region_half) >> region_shift;
let g = (sg + region_half) >> region_shift;
let b = (sb + region_half) >> region_shift;
let cb = ((FWD_CB_R * r + FWD_CB_G * g + FWD_CB_B * b + FWD_BIAS) >> FWD_PREC) << shift;
let cr = ((FWD_CR_R * r + FWD_CR_G * g + FWD_CR_B * b + FWD_BIAS) >> FWD_PREC) << shift;
src_cb[cy * cwc + cx] = (mid + cb).clamp(0, max_val);
src_cr[cy * cwc + cx] = (mid + cr).clamp(0, max_val);
}
}
encode_planes(headers, src_y, src_cb, src_cr, threads, rdoq)
}
struct LeafDecision {
x: usize,
y: usize,
w: usize,
h: usize,
luma_bdpcm: u8,
best_mode: u8,
luma_dir_mode: u8,
luma_ts: bool,
luma_levels: Vec<i32>,
lfnst_idx: u8,
luma_mts_idx: u8,
chroma: Option<ChromaDecision>,
}
struct ChromaDecision {
ccw: usize,
cch: usize,
chroma_mode: u8,
chroma_bdpcm: u8,
chroma_ts: bool,
cb_levels: Vec<i32>,
cr_levels: Vec<i32>,
}
struct ModeGrid {
mode_of: Vec<AtomicU8>,
cells_x: usize,
coded_w: u32,
coded_h: u32,
}
impl ModeGrid {
fn new(coded_w: u32, coded_h: u32) -> Self {
let cells_x = coded_w as usize / 4;
let cells_y = coded_h as usize / 4;
let mode_of = (0..cells_x * cells_y)
.map(|_| AtomicU8::new(PLANAR_IDX))
.collect();
ModeGrid {
mode_of,
cells_x,
coded_w,
coded_h,
}
}
#[inline]
fn cell(&self, x: u32, y: u32) -> usize {
(y as usize / 4) * self.cells_x + (x as usize / 4)
}
fn set_mode_rect(&self, x: u32, y: u32, w: u32, h: u32, mode: u8) {
let mut cy = y;
while cy < y + h && cy < self.coded_h {
let mut cx = x;
while cx < x + w && cx < self.coded_w {
let idx = self.cell(cx, cy);
self.mode_of[idx].store(mode, Relaxed);
cx += 4;
}
cy += 4;
}
}
fn left_mode_rect(&self, x: u32, y: u32, h: u32) -> Option<u8> {
if x > 0 {
Some(self.mode_of[self.cell(x - 1, y + h - 1)].load(Relaxed))
} else {
None
}
}
fn above_mode_rect(&self, x: u32, y: u32, w: u32) -> Option<u8> {
if y > 0 {
Some(self.mode_of[self.cell(x + w - 1, y - 1)].load(Relaxed))
} else {
None
}
}
fn chroma_dm(&self, x: u32, y: u32, w: u32, h: u32) -> u8 {
let cx = (x + (w >> 1)).min(self.coded_w - 1);
let cy = (y + (h >> 1)).min(self.coded_h - 1);
self.mode_of[self.cell(cx, cy)].load(Relaxed)
}
}
struct Planes {
rec_y: Vec<AtomicI32>,
av_y: Vec<AtomicU8>,
rec_cb: Vec<AtomicI32>,
av_cb: Vec<AtomicU8>,
rec_cr: Vec<AtomicI32>,
av_cr: Vec<AtomicU8>,
modes: ModeGrid,
}
impl Planes {
fn new(cw: usize, ch: usize, cwc: usize, chc: usize) -> Self {
let zeros = |n: usize| (0..n).map(|_| AtomicI32::new(0)).collect();
let avail = |n: usize| (0..n).map(|_| AtomicU8::new(0)).collect();
Planes {
rec_y: zeros(cw * ch),
av_y: avail(cw * ch),
rec_cb: zeros(cwc * chc),
av_cb: avail(cwc * chc),
rec_cr: zeros(cwc * chc),
av_cr: avail(cwc * chc),
modes: ModeGrid::new(cw as u32, ch as u32),
}
}
fn into_recon(self) -> (Vec<i32>, Vec<i32>, Vec<i32>) {
let conv = |v: Vec<AtomicI32>| v.into_iter().map(|a| a.into_inner()).collect();
(conv(self.rec_y), conv(self.rec_cb), conv(self.rec_cr))
}
}
struct LeafShared<'a> {
src_y: &'a [i32],
src_cb: &'a [i32],
src_cr: &'a [i32],
cw: usize,
ch: usize,
cwc: usize,
chc: usize,
sub_w: usize,
sub_h: usize,
has_chroma: bool,
bit_depth: u8,
qp: u8,
lossless: bool,
rdoq: bool,
lfnst: bool,
dep_quant: bool,
mts: bool,
cclm: bool,
aq: bool,
qg: usize,
qg_cols: usize,
qg_qpprime: &'a [u8],
max_val: i32,
rd_ctx: &'a Contexts,
}
struct LeafScratch {
pred_buf: Vec<i32>,
res_buf: Vec<i32>,
ang_scratch: Vec<i32>,
scored: Vec<(i64, u8)>,
cpred_cb: Vec<i32>,
cpred_cr: Vec<i32>,
cres: Vec<i32>,
cscratch: Vec<i32>,
best_pred: Vec<i32>,
sblk_y: Vec<i32>,
sblk_cb: Vec<i32>,
sblk_cr: Vec<i32>,
inv_buf: Vec<i32>,
deq_buf: Vec<i32>,
lv_buf: Vec<i16>,
}
impl LeafScratch {
fn new() -> Self {
const MAX_CU: usize = 64 * 64;
LeafScratch {
pred_buf: vec![0; MAX_CU],
res_buf: vec![0; MAX_CU],
ang_scratch: Vec::with_capacity(MAX_CU),
scored: Vec::with_capacity(NUM_LUMA_MODE as usize),
cpred_cb: vec![0; MAX_CU],
cpred_cr: vec![0; MAX_CU],
cres: vec![0; MAX_CU],
cscratch: Vec::with_capacity(MAX_CU),
best_pred: vec![0; MAX_CU],
sblk_y: vec![0; MAX_CU],
sblk_cb: vec![0; MAX_CU],
sblk_cr: vec![0; MAX_CU],
inv_buf: vec![0; MAX_CU],
deq_buf: vec![0; MAX_CU],
lv_buf: Vec::with_capacity(MAX_CU),
}
}
}
thread_local! {
static RD_ENC: std::cell::RefCell<CabacEncoder> = std::cell::RefCell::new(CabacEncoder::new());
}
fn run_ctu_wavefront<R, F>(ctu_cols: usize, ctu_rows: usize, threads: usize, process: F) -> Vec<R>
where
R: Send + Default,
F: Fn(usize, &mut LeafScratch) -> R + Sync,
{
let total = ctu_cols * ctu_rows;
if threads <= 1 {
let mut sc = LeafScratch::new();
return (0..total).map(|cidx| process(cidx, &mut sc)).collect();
}
use std::collections::VecDeque;
use std::sync::atomic::{
AtomicUsize,
Ordering::{AcqRel, Acquire},
};
use std::sync::{Condvar, Mutex};
let at = |r: usize, c: usize| r * ctu_cols + c;
let remaining: Vec<AtomicUsize> = (0..total)
.map(|i| {
let (r, c) = (i / ctu_cols, i % ctu_cols);
let mut n = 0usize;
if c > 0 {
n += 1;
}
if r > 0 {
n += 1;
}
if r > 0 && c + 1 < ctu_cols {
n += 1;
}
if r > 0 && c > 0 {
n += 1;
}
AtomicUsize::new(n)
})
.collect();
let queue = Mutex::new(VecDeque::<usize>::new());
let cvar = Condvar::new();
{
let mut q = queue.lock().unwrap();
for (i, rem) in remaining.iter().enumerate() {
if rem.load(Relaxed) == 0 {
q.push_back(i);
}
}
}
let done = AtomicUsize::new(0);
let nthreads = threads.min(total).max(1);
let remaining_ref = &remaining;
let queue_ref = &queue;
let cvar_ref = &cvar;
let done_ref = &done;
let process_ref = &process;
let results: Vec<Vec<(usize, R)>> = std::thread::scope(|s| {
let handles: Vec<_> = (0..nthreads)
.map(|_| {
s.spawn(move || {
let mut sc = LeafScratch::new();
let mut out: Vec<(usize, R)> = Vec::new();
loop {
let next = {
let mut q = queue_ref.lock().unwrap();
loop {
if let Some(c) = q.pop_front() {
break Some(c);
}
if done_ref.load(Acquire) >= total {
break None;
}
q = cvar_ref.wait(q).unwrap();
}
};
let Some(cidx) = next else { break };
let r = process_ref(cidx, &mut sc);
out.push((cidx, r));
let (rr, cc) = (cidx / ctu_cols, cidx % ctu_cols);
let deps = [
(cc + 1 < ctu_cols).then(|| at(rr, cc + 1)),
(rr + 1 < ctu_rows).then(|| at(rr + 1, cc)),
(rr + 1 < ctu_rows && cc > 0).then(|| at(rr + 1, cc - 1)),
(rr + 1 < ctu_rows && cc + 1 < ctu_cols).then(|| at(rr + 1, cc + 1)),
];
for e in deps.into_iter().flatten() {
if remaining_ref[e].fetch_sub(1, AcqRel) == 1 {
queue_ref.lock().unwrap().push_back(e);
cvar_ref.notify_one();
}
}
if done_ref.fetch_add(1, AcqRel) + 1 >= total {
cvar_ref.notify_all();
}
}
out
})
})
.collect();
handles.into_iter().map(|h| h.join().unwrap()).collect()
});
let mut assembled: Vec<R> = (0..total).map(|_| R::default()).collect();
for worker_out in results {
for (cidx, r) in worker_out {
assembled[cidx] = r;
}
}
assembled
}
#[allow(clippy::type_complexity)]
fn analyze_ctus_wavefront(
shared: &LeafShared,
planes: &Planes,
ctu_leaves: &[Vec<(usize, usize, usize, usize)>],
ctu_cols: usize,
ctu_rows: usize,
threads: usize,
) -> Vec<Vec<LeafDecision>> {
run_ctu_wavefront(ctu_cols, ctu_rows, threads, |cidx, sc| {
let mut decs = Vec::with_capacity(ctu_leaves[cidx].len());
for &(x, y, w, h) in &ctu_leaves[cidx] {
decs.push(analyze_leaf(
shared,
planes,
sc,
x,
y,
w,
h,
TreeType::Single,
));
}
decs
})
}
#[allow(unused, clippy::type_complexity)]
fn analyze_leaf(
sh: &LeafShared,
pl: &Planes,
sc: &mut LeafScratch,
x: usize,
y: usize,
w: usize,
h: usize,
tree: TreeType,
) -> LeafDecision {
let (cw, ch) = (sh.cw, sh.ch);
let bit_depth = sh.bit_depth;
let qp = if sh.aq {
sh.qg_qpprime[(y / sh.qg) * sh.qg_cols + x / sh.qg]
} else {
sh.qp
};
let lossless = sh.lossless;
let max_val = sh.max_val;
let ctx = sh.rd_ctx;
let refs_y = gather_refs_atomic_wh(&pl.rec_y, &pl.av_y, cw, ch, x, y, w, h, bit_depth);
extract_block_wh_into(&mut sc.sblk_y[..w * h], sh.src_y, cw, x, y, w, h);
let sblk_y = &sc.sblk_y[..w * h];
let mpm = build_mpm(
pl.modes.left_mode_rect(x as u32, y as u32, h as u32),
pl.modes.above_mode_rect(x as u32, y as u32, w as u32),
);
let filt_y = if w >= 8 && h >= 8 {
Some(crate::predict::filter_references(&refs_y, w, h))
} else {
None
};
let nn = w * h;
sc.scored.clear();
let mut done = [false; NUM_LUMA_MODE as usize];
macro_rules! eval_mode {
($m:expr) => {{
let mi = $m as usize;
if mi < NUM_LUMA_MODE as usize && !done[mi] {
done[mi] = true;
let mode = mi as u8;
predict_into(
&mut sc.pred_buf[..nn],
&mut sc.ang_scratch,
filt_y.as_ref(),
mode,
w,
h,
&refs_y,
bit_depth,
true,
);
for i in 0..nn {
sc.res_buf[i] = sblk_y[i] - sc.pred_buf[i];
}
sc.scored.push((satd_wh(&sc.res_buf[..nn], w, h), mode));
}
}};
}
eval_mode!(PLANAR_IDX);
eval_mode!(DC_IDX);
for &m in mpm.iter() {
eval_mode!(m);
}
let mut a = 2u8;
while a <= 66 {
eval_mode!(a);
a += 2;
}
sc.scored.sort_unstable_by_key(|&(s, _)| s);
if let Some(bm) = sc.scored.iter().map(|&(_, m)| m).find(|&m| m >= 2) {
if bm > 2 {
eval_mode!(bm - 1);
}
if bm < 66 {
eval_mode!(bm + 1);
}
}
sc.scored.sort_unstable_by_key(|&(s, _)| s);
let k = sc.scored.len().min(RDO_CANDIDATES);
let lambda = 0.57f64 * 2f64.powf((qp as f64 - 12.0) / 3.0);
let mut best_mode = sc.scored[0].1;
predict_into(
&mut sc.best_pred[..nn],
&mut sc.ang_scratch,
filt_y.as_ref(),
best_mode,
w,
h,
&refs_y,
bit_depth,
true,
);
let mut best_rd = f64::MAX;
let mut best_levels: Vec<i32> = Vec::new();
for i in 0..k {
if lossless {
break;
}
let mode = sc.scored[i].1;
predict_into(
&mut sc.pred_buf[..nn],
&mut sc.ang_scratch,
filt_y.as_ref(),
mode,
w,
h,
&refs_y,
bit_depth,
true,
);
let levels = analyze_wh(
sblk_y,
&sc.pred_buf[..nn],
w,
h,
qp,
bit_depth,
if sh.rdoq { lambda } else { 0.0 },
0,
0,
sh.dep_quant,
);
if sh.dep_quant {
crate::transform::dequantize_dq_wh_into(
&mut sc.deq_buf[..nn],
&levels,
crate::residual::scan_coords(w, h),
w,
h,
qp,
bit_depth,
);
} else {
sc.lv_buf.clear();
sc.lv_buf.extend(levels.iter().map(|&l| l as i16));
crate::transform::dequantize_wh_into(
&mut sc.deq_buf[..nn],
&sc.lv_buf,
w,
h,
qp,
bit_depth,
);
}
inv_transform_wh_into(&mut sc.inv_buf[..nn], &sc.deq_buf[..nn], w, h, bit_depth);
let ssd: i64 = (0..nn)
.map(|i| {
let d = (sblk_y[i] - (sc.pred_buf[i] + sc.inv_buf[i]).clamp(0, max_val)) as i64;
d * d
})
.sum();
let mut tctx = ctx.clone();
let tu = TuCoeffs {
tree: TreeType::Single,
luma: &levels,
lw: w,
lh: h,
chroma: None,
lossless: false,
luma_bdpcm: false,
chroma_bdpcm: false,
luma_ts: false,
chroma_ts: false,
code_dqp: None,
dep_quant: sh.dep_quant,
};
let bits = RD_ENC.with(|c| {
let tenc = &mut *c.borrow_mut();
tenc.reset();
encode_luma_mode(tenc, &mut tctx, &mpm, mode);
encode_transform_tree(tenc, &mut tctx, &tu);
tenc.flushed_len() * 8
}) as f64;
let rd = ssd as f64 + lambda * bits;
if rd < best_rd {
best_rd = rd;
best_mode = mode;
sc.best_pred[..nn].copy_from_slice(&sc.pred_buf[..nn]);
best_levels = levels;
}
}
let mut luma_ts = false;
if !lossless && w <= 32 && h <= 32 {
let r: Vec<i32> = sblk_y
.iter()
.zip(&sc.best_pred[..nn])
.map(|(&s, &p)| s - p)
.collect();
let ts_lv16 = quantize_ts_wh(&r, w, h, qp);
let ts_levels: Vec<i32> = ts_lv16[..nn].iter().map(|&l| l as i32).collect();
let deq = dequantize_ts_wh(&ts_lv16, w, h, qp);
let ssd: i64 = (0..nn)
.map(|i| {
let d = (sblk_y[i] - (sc.best_pred[i] + deq[i]).clamp(0, max_val)) as i64;
d * d
})
.sum();
let mut tctx = ctx.clone();
let tu = TuCoeffs {
tree: TreeType::Single,
luma: &ts_levels,
lw: w,
lh: h,
chroma: None,
lossless: false,
luma_bdpcm: false,
chroma_bdpcm: false,
luma_ts: true,
chroma_ts: false,
code_dqp: None,
dep_quant: sh.dep_quant,
};
let bits = RD_ENC.with(|c| {
let tenc = &mut *c.borrow_mut();
tenc.reset();
encode_luma_mode(tenc, &mut tctx, &mpm, best_mode);
encode_transform_tree(tenc, &mut tctx, &tu);
tenc.flushed_len() * 8
}) as f64;
let ts_rd = ssd as f64 + lambda * bits;
if ts_rd < best_rd {
best_levels = ts_levels;
luma_ts = true;
}
}
let mut luma_bdpcm = 0u8;
let mut luma_dir_mode = best_mode;
let luma_levels: Vec<i32> = if lossless {
let leaf_bits = |bdpcm: u8, mode: u8, levels: &[i32]| -> usize {
let mut tctx = ctx.clone();
let tu = TuCoeffs {
tree: TreeType::Single,
luma: levels,
lw: w,
lh: h,
chroma: None,
lossless: true,
luma_bdpcm: bdpcm != 0,
chroma_bdpcm: false,
luma_ts: false,
chroma_ts: false,
code_dqp: None,
dep_quant: sh.dep_quant,
};
RD_ENC.with(|c| {
let tenc = &mut *c.borrow_mut();
tenc.reset();
encode_bdpcm_mode(tenc, &mut tctx, bdpcm, true);
if bdpcm == 0 {
encode_luma_mode(tenc, &mut tctx, &mpm, mode);
}
encode_transform_tree(tenc, &mut tctx, &tu);
tenc.flushed_len()
})
};
let mut best_bits = usize::MAX;
let mut chosen: Vec<i32> = Vec::new();
for i in 0..k {
let mode = sc.scored[i].1;
predict_into(
&mut sc.pred_buf[..nn],
&mut sc.ang_scratch,
filt_y.as_ref(),
mode,
w,
h,
&refs_y,
bit_depth,
true,
);
let levels: Vec<i32> = sblk_y
.iter()
.zip(&sc.pred_buf[..nn])
.map(|(&s, &p)| s - p)
.collect();
let bits = leaf_bits(0, mode, &levels);
if bits < best_bits {
best_bits = bits;
best_mode = mode;
luma_bdpcm = 0;
luma_dir_mode = mode;
chosen = levels;
}
}
let hor = dpcm_levels_wh(sblk_y, &refs_y, w, h, 1);
let bh = leaf_bits(1, HOR_IDX, &hor);
if bh < best_bits {
best_bits = bh;
luma_bdpcm = 1;
luma_dir_mode = HOR_IDX;
chosen = hor;
}
let ver = dpcm_levels_wh(sblk_y, &refs_y, w, h, 2);
if leaf_bits(2, VER_IDX, &ver) < best_bits {
luma_bdpcm = 2;
luma_dir_mode = VER_IDX;
chosen = ver;
}
chosen
} else {
best_levels
};
let mut lfnst_idx = 0u8;
let mut lfnst_mode = 0usize;
let mut lfnst_levels: Vec<i32> = Vec::new();
let mut luma_mts_idx = 0u8;
let mut mts_levels: Vec<i32> = Vec::new();
if sh.lfnst && !lossless && !luma_ts && w <= 32 && h <= 32 {
thread_local! {
static LF_TRIAL: std::cell::RefCell<(Box<[i32; 4096]>, Box<[i32; 4096]>,Box<[i32; 4096]>)> =
std::cell::RefCell::new((Box::new([0; 64 * 64]), Box::new([0; 64 * 64]), Box::new([0; 64 * 64])));
}
let lf_mode = crate::lfnst::lfnst_intra_mode(crate::predict::lfnst_wide_angle(
w,
h,
best_mode as i32,
));
let lf_lambda = if sh.rdoq { lambda } else { 0.0 };
let mut best_lf_rd = best_rd;
LF_TRIAL.with(|c| {
let (res, dct, coeff) = &mut *c.borrow_mut();
for (r, (&s, &p)) in res[..nn]
.iter_mut()
.zip(sblk_y.iter().zip(&sc.best_pred[..nn]))
{
*r = s - p;
}
fwd_transform_wh_into(&mut dct[..nn], &res[..nn], w, h, bit_depth);
for idx in 1u8..=2 {
coeff[..nn].copy_from_slice(&dct[..nn]);
crate::lfnst::apply_fwd_lfnst(&mut coeff[..nn], w, h, lf_mode, idx as usize);
let lv16 = if lf_lambda > 0.0 {
rdoq_wh(&coeff[..nn], w, h, qp, bit_depth, lf_lambda)
} else {
quantize_wh(&coeff[..nn], w, h, qp, bit_depth)
};
let lv: Vec<i32> = lv16[..nn].iter().map(|&l| l as i32).collect();
if crate::residual::last_sig_scan_pos(&lv, w, h, crate::residual::Component::Luma)
< 1
{
continue;
}
let rec = reconstruct_block_wh(
&sc.best_pred[..nn],
&lv,
w,
h,
qp,
bit_depth,
max_val,
false,
false,
idx,
lf_mode,
sh.dep_quant,
0,
);
let ssd: i64 = (0..nn)
.map(|i| {
let d = (sblk_y[i] - rec[i]) as i64;
d * d
})
.sum();
let mut tctx = ctx.clone();
let tu = TuCoeffs {
tree: TreeType::Single,
luma: &lv,
lw: w,
lh: h,
chroma: None,
lossless: false,
luma_bdpcm: false,
chroma_bdpcm: false,
luma_ts: false,
chroma_ts: false,
code_dqp: None,
dep_quant: sh.dep_quant,
};
let bits = RD_ENC.with(|c| {
let tenc = &mut *c.borrow_mut();
tenc.reset();
encode_luma_mode(tenc, &mut tctx, &mpm, best_mode);
encode_transform_tree(tenc, &mut tctx, &tu);
tenc.flushed_len() * 8
}) as f64;
let rd = ssd as f64 + lambda * (bits + idx as f64);
if rd < best_lf_rd {
best_lf_rd = rd;
lfnst_idx = idx;
lfnst_mode = lf_mode;
lfnst_levels = lv;
} else if idx == 1 && lfnst_idx == 0 {
break;
}
}
});
}
if sh.mts
&& !lossless
&& !luma_ts
&& lfnst_idx == 0
&& w <= 32
&& h <= 32
&& matches!(w, 4 | 8 | 16 | 32)
&& matches!(h, 4 | 8 | 16 | 32)
{
thread_local! {
static MTS_TRIAL: std::cell::RefCell<(Box<[i32; 4096]>, Box<[i32; 4096]>, Box<[i32; 4096]>, Box<[i32; 4096]>)> =
std::cell::RefCell::new((Box::new([0; 64 * 64]), Box::new([0; 64 * 64]), Box::new([0; 64 * 64]), Box::new([0; 64 * 64])));
}
let mts_lambda = if sh.rdoq { lambda } else { 0.0 };
let dct2_nz = luma_levels.iter().filter(|&&v| v != 0).count();
if dct2_nz >= 2 {
let escale = crate::transform::err_scale(w, h, bit_depth);
let proxy_bits = |lv: &[i32]| -> f64 {
let mut b = 0.0f64;
for &l in &lv[..nn] {
if l != 0 {
let a = l.unsigned_abs();
b += 3.0 + 2.0 * (32 - a.leading_zeros()) as f64;
}
}
b
};
let mut best_cand = 0u8;
let mut best_proxy = f64::MAX;
let mut best_lv: Vec<i32> = Vec::new();
MTS_TRIAL.with(|c| {
let (res, coeff, tmp_dst7, tmp_dct8) = &mut *c.borrow_mut();
for (r, (&s, &p)) in res[..nn]
.iter_mut()
.zip(sblk_y.iter().zip(&sc.best_pred[..nn]))
{
*r = s - p;
}
crate::transform::fwd_mts_pass1_into(
&mut tmp_dst7[..nn],
&res[..nn],
w,
h,
bit_depth,
1,
);
crate::transform::fwd_mts_pass1_into(
&mut tmp_dct8[..nn],
&res[..nn],
w,
h,
bit_depth,
2,
);
for cand in 1u8..=4 {
let (th, tv) = crate::transform::mts_to_types(cand);
let tmp = if th == 1 {
&tmp_dst7[..nn]
} else {
&tmp_dct8[..nn]
};
crate::transform::fwd_mts_pass2_into(&mut coeff[..nn], tmp, w, h, th, tv);
let lv16 = quantize_wh(&coeff[..nn], w, h, qp, bit_depth);
let lv: Vec<i32> = lv16[..nn].iter().map(|&l| l as i32).collect();
if !crate::residual::mts_signallable(&lv, w, h) {
continue;
}
let deq = dequantize_wh(&lv16, w, h, qp, bit_depth);
let dist: f64 = coeff[..nn]
.iter()
.zip(&deq[..nn])
.map(|(&cc, &dd)| {
let e = (cc - dd) as f64;
e * e
})
.sum::<f64>()
* escale;
let proxy = dist + lambda * proxy_bits(&lv);
if proxy < best_proxy {
best_proxy = proxy;
best_cand = cand;
best_lv = lv;
}
}
if best_cand > 0 && mts_lambda > 0.0 {
let (th, tv) = crate::transform::mts_to_types(best_cand);
let tmp = if th == 1 {
&tmp_dst7[..nn]
} else {
&tmp_dct8[..nn]
};
crate::transform::fwd_mts_pass2_into(&mut coeff[..nn], tmp, w, h, th, tv);
let lv16 = rdoq_wh(&coeff[..nn], w, h, qp, bit_depth, mts_lambda);
let lv: Vec<i32> = lv16[..nn].iter().map(|&l| l as i32).collect();
if crate::residual::mts_signallable(&lv, w, h) {
best_lv = lv;
} else {
best_cand = 0;
}
}
});
if best_cand > 0 {
let rec = reconstruct_block_wh(
&sc.best_pred[..nn],
&best_lv,
w,
h,
qp,
bit_depth,
max_val,
false,
false,
0,
0,
sh.dep_quant,
best_cand,
);
let ssd: i64 = (0..nn)
.map(|i| {
let d = (sblk_y[i] - rec[i]) as i64;
d * d
})
.sum();
let mut tctx = ctx.clone();
let tu = TuCoeffs {
tree: TreeType::Single,
luma: &best_lv,
lw: w,
lh: h,
chroma: None,
lossless: false,
luma_bdpcm: false,
chroma_bdpcm: false,
luma_ts: false,
chroma_ts: false,
code_dqp: None,
dep_quant: sh.dep_quant,
};
let bits = RD_ENC.with(|c| {
let tenc = &mut *c.borrow_mut();
tenc.reset();
encode_luma_mode(tenc, &mut tctx, &mpm, best_mode);
encode_transform_tree(tenc, &mut tctx, &tu);
tenc.flushed_len() * 8
}) as f64;
let mts_bin_len = [2u32, 3, 4, 4][(best_cand - 1) as usize] as f64;
let rd = ssd as f64 + lambda * (bits + mts_bin_len);
if rd < best_rd {
luma_mts_idx = best_cand;
mts_levels = best_lv;
}
}
}
}
macro_rules! commit_luma {
($lf:expr) => {{
let lf: u8 = $lf;
let lv: &[i32] = if luma_mts_idx > 0 {
&mts_levels
} else if lf > 0 {
&lfnst_levels
} else {
&luma_levels
};
if lossless {
place_atomic_wh(&pl.rec_y, &pl.av_y, cw, x, y, w, h, &sc.sblk_y[..w * h]);
} else {
reconstruct_atomic_wh(
&pl.rec_y,
&pl.av_y,
cw,
x,
y,
w,
h,
&sc.best_pred[..nn],
lv,
qp,
bit_depth,
max_val,
lossless,
luma_ts,
lf,
lfnst_mode,
sh.dep_quant,
luma_mts_idx,
);
}
}};
}
let chroma_dec = if tree == TreeType::Luma {
if lfnst_idx > 0
&& !crate::residual::lfnst_present(
sh.lfnst,
w,
h,
(lfnst_levels.as_slice(), false),
None,
)
{
lfnst_idx = 0;
}
commit_luma!(lfnst_idx);
None
} else {
commit_luma!(lfnst_idx);
let mut cd = analyze_chroma(
sh,
pl,
sc,
x,
y,
w,
h,
qp,
lambda,
max_val,
luma_dir_mode,
sh.cclm,
);
if lfnst_idx > 0 {
let chroma = cd.as_ref().map(|c| {
(
c.cb_levels.as_slice(),
c.cr_levels.as_slice(),
c.ccw,
c.cch,
c.chroma_ts || c.chroma_bdpcm != 0,
)
});
if !crate::residual::lfnst_present(
sh.lfnst,
w,
h,
(lfnst_levels.as_slice(), false),
chroma,
) {
lfnst_idx = 0;
commit_luma!(0); if cd
.as_ref()
.is_some_and(|c| crate::intra::is_cclm_mode(c.chroma_mode))
{
cd = analyze_chroma(
sh,
pl,
sc,
x,
y,
w,
h,
qp,
lambda,
max_val,
luma_dir_mode,
sh.cclm,
);
}
}
}
cd
};
pl.modes
.set_mode_rect(x as u32, y as u32, w as u32, h as u32, luma_dir_mode);
let luma_levels = if luma_mts_idx > 0 {
mts_levels
} else if lfnst_idx > 0 {
lfnst_levels
} else {
luma_levels
};
LeafDecision {
x,
y,
w,
h,
luma_bdpcm,
best_mode,
luma_dir_mode,
luma_ts,
luma_levels,
lfnst_idx,
luma_mts_idx,
chroma: chroma_dec,
}
}
#[allow(clippy::too_many_arguments)]
fn analyze_chroma(
sh: &LeafShared,
pl: &Planes,
sc: &mut LeafScratch,
x: usize,
y: usize,
w: usize,
h: usize,
qp: u8,
lambda: f64,
max_val: i32,
dm_mode: u8,
cclm: bool,
) -> Option<ChromaDecision> {
if !sh.has_chroma {
return None;
}
thread_local! {
static CHR: std::cell::RefCell<(Vec<i16>, Vec<i32>, Vec<i32>)> =
std::cell::RefCell::new((Vec::with_capacity(64 * 64), vec![0; 64 * 64], vec![0; 64 * 64]));
}
let bit_depth = sh.bit_depth;
let lossless = sh.lossless;
let (sub_w, sub_h) = (sh.sub_w, sh.sub_h);
let (cwc, chc) = (sh.cwc, sh.chc);
let ctx = sh.rd_ctx;
let nn = w * h;
let (ccw, cch) = (w / sub_w, h / sub_h);
let (cx, cy) = (x / sub_w, y / sub_h);
let refs_cb =
gather_refs_atomic_wh(&pl.rec_cb, &pl.av_cb, cwc, chc, cx, cy, ccw, cch, bit_depth);
let refs_cr =
gather_refs_atomic_wh(&pl.rec_cr, &pl.av_cr, cwc, chc, cx, cy, ccw, cch, bit_depth);
extract_block_wh_into(
&mut sc.sblk_cb[..ccw * cch],
sh.src_cb,
cwc,
cx,
cy,
ccw,
cch,
);
extract_block_wh_into(
&mut sc.sblk_cr[..ccw * cch],
sh.src_cr,
cwc,
cx,
cy,
ccw,
cch,
);
let sblk_cb = &sc.sblk_cb[..ccw * cch];
let sblk_cr = &sc.sblk_cr[..ccw * cch];
let mut chroma_cands = vec![dm_mode];
for m in chroma_cand_modes(dm_mode) {
chroma_cands.push(m);
}
let mut chroma_mode = dm_mode;
let mut best_chroma_cost = i64::MAX;
let ncc = ccw * cch;
let is_422 = sub_w == 2 && sub_h == 1;
let pmode = |m: u8| if is_422 { chroma_422_mode(m) } else { m };
let cclm_eval: Option<(u8, i64, Vec<i32>, Vec<i32>)> = if cclm {
let (lw, lh) = (sh.cw, sh.ch);
let (cwc2, chc2) = (cwc, chc);
let luma = |xx: isize, yy: isize| {
let xx = xx.clamp(0, lw as isize - 1) as usize;
let yy = yy.clamp(0, lh as isize - 1) as usize;
pl.rec_y[yy * lw + xx].load(std::sync::atomic::Ordering::Relaxed)
};
let cba = |xx: isize, yy: isize| {
let xx = xx.clamp(0, cwc2 as isize - 1) as usize;
let yy = yy.clamp(0, chc2 as isize - 1) as usize;
pl.rec_cb[yy * cwc2 + xx].load(std::sync::atomic::Ordering::Relaxed)
};
let cra = |xx: isize, yy: isize| {
let xx = xx.clamp(0, cwc2 as isize - 1) as usize;
let yy = yy.clamp(0, chc2 as isize - 1) as usize;
pl.rec_cr[yy * cwc2 + xx].load(std::sync::atomic::Ordering::Relaxed)
};
let avail = |xx: usize, yy: usize| {
pl.av_cb[yy * cwc2 + xx].load(std::sync::atomic::Ordering::Relaxed) != 0
};
let first_row = (y & (crate::partition::CTU_SIZE as usize - 1)) == 0;
let (above, left) = (cy > 0, cx > 0);
let uw = 4 >> if sub_w == 2 { 1 } else { 0 };
let uh = 4 >> if sub_h == 2 { 1 } else { 0 };
let avai_ar = if above {
let mut n = 0;
for u in 0..ccw / uw {
let col = cx + ccw + u * uw;
if col >= cwc2 || !avail(col, cy - 1) {
break;
}
n += 1;
}
n
} else {
0
};
let avai_bl = if left {
let mut n = 0;
for u in 0..cch / uh {
let row = cy + cch + u * uh;
if row >= chc2 || !avail(cx - 1, row) {
break;
}
n += 1;
}
n
} else {
0
};
let mut cands = vec![crate::intra::CCLM_LT_MODE];
if above {
cands.push(crate::intra::CCLM_T_MODE);
}
if left {
cands.push(crate::intra::CCLM_L_MODE);
}
let mut best: Option<(u8, i64, Vec<i32>, Vec<i32>)> = None;
for &m in &cands {
let (pcb, pcr) = crate::cclm::cclm_predict(
luma, cba, cra, x, y, cx, cy, ccw, cch, sub_w, sub_h, above, left, first_row, m,
avai_ar, avai_bl, max_val, bit_depth,
);
let mut res = vec![0i32; ncc];
for i in 0..ncc {
res[i] = sblk_cb[i] - pcb[i];
}
let mut cost = satd_wh(&res, ccw, cch);
for i in 0..ncc {
res[i] = sblk_cr[i] - pcr[i];
}
cost += satd_wh(&res, ccw, cch);
if best.as_ref().is_none_or(|b| cost < b.1) {
best = Some((m, cost, pcb, pcr));
}
}
best
} else {
None
};
for &cm in &chroma_cands {
let pm = pmode(cm);
predict_into(
&mut sc.cpred_cb[..ncc],
&mut sc.cscratch,
None,
pm,
ccw,
cch,
&refs_cb,
bit_depth,
false,
);
predict_into(
&mut sc.cpred_cr[..ncc],
&mut sc.cscratch,
None,
pm,
ccw,
cch,
&refs_cr,
bit_depth,
false,
);
#[allow(clippy::needless_range_loop)]
for i in 0..ncc {
sc.cres[i] = sblk_cb[i] - sc.cpred_cb[i];
}
let mut cost = satd_wh(&sc.cres[..ncc], ccw, cch);
#[allow(clippy::needless_range_loop)]
for i in 0..ncc {
sc.cres[i] = sblk_cr[i] - sc.cpred_cr[i];
}
cost += satd_wh(&sc.cres[..ncc], ccw, cch);
if cost < best_chroma_cost {
best_chroma_cost = cost;
chroma_mode = cm;
}
}
if let Some((m, ccost, _, _)) = &cclm_eval
&& *ccost < best_chroma_cost
{
chroma_mode = *m;
}
if crate::intra::is_cclm_mode(chroma_mode) {
let (_, _, pcb, pcr) = cclm_eval.unwrap();
sc.cpred_cb[..ncc].copy_from_slice(&pcb);
sc.cpred_cr[..ncc].copy_from_slice(&pcr);
} else {
predict_into(
&mut sc.cpred_cb[..ncc],
&mut sc.cscratch,
None,
pmode(chroma_mode),
ccw,
cch,
&refs_cb,
bit_depth,
false,
);
predict_into(
&mut sc.cpred_cr[..ncc],
&mut sc.cscratch,
None,
pmode(chroma_mode),
ccw,
cch,
&refs_cr,
bit_depth,
false,
);
}
let mut chroma_bdpcm = 0u8;
let mut chroma_ts = false;
let (cb_levels, cr_levels) = if lossless {
let cb_n: Vec<i32> = sblk_cb
.iter()
.zip(&sc.cpred_cb[..ncc])
.map(|(&s, &p)| s - p)
.collect();
let cr_n: Vec<i32> = sblk_cr
.iter()
.zip(&sc.cpred_cr[..ncc])
.map(|(&s, &p)| s - p)
.collect();
let cb_h = dpcm_levels_wh(sblk_cb, &refs_cb, ccw, cch, 1);
let cr_h = dpcm_levels_wh(sblk_cr, &refs_cr, ccw, cch, 1);
let cb_v = dpcm_levels_wh(sblk_cb, &refs_cb, ccw, cch, 2);
let cr_v = dpcm_levels_wh(sblk_cr, &refs_cr, ccw, cch, 2);
let cost = |a: &[i32], b: &[i32]| {
a.iter()
.chain(b)
.map(|&v| v.unsigned_abs() as u64)
.sum::<u64>()
};
let (c_n, c_h, c_v) = (cost(&cb_n, &cr_n), cost(&cb_h, &cr_h), cost(&cb_v, &cr_v));
if c_h <= c_n && c_h <= c_v {
chroma_bdpcm = 1;
(cb_h, cr_h)
} else if c_v <= c_n {
chroma_bdpcm = 2;
(cb_v, cr_v)
} else {
(cb_n, cr_n)
}
} else {
let rlambda = if sh.rdoq { lambda } else { 0.0 };
let cb_dct = residual_levels_wh(
sblk_cb,
&sc.cpred_cb[..ncc],
ccw,
cch,
qp,
bit_depth,
false,
rlambda,
sh.dep_quant,
);
let cr_dct = residual_levels_wh(
sblk_cr,
&sc.cpred_cr[..ncc],
ccw,
cch,
qp,
bit_depth,
false,
rlambda,
sh.dep_quant,
);
let ssd_dct = CHR.with(|c| {
let (l16, deq, inv) = &mut *c.borrow_mut();
let mut s = 0i64;
for (sblk, pred, lv) in [
(sblk_cb, &sc.cpred_cb[..ncc], &cb_dct),
(sblk_cr, &sc.cpred_cr[..ncc], &cr_dct),
] {
l16.clear();
l16.extend(lv.iter().map(|&x| x as i16));
crate::transform::dequantize_wh_into(&mut deq[..ncc], l16, ccw, cch, qp, bit_depth);
inv_transform_wh_into(&mut inv[..ncc], &deq[..ncc], ccw, cch, bit_depth);
for i in 0..ncc {
let d = (sblk[i] - (pred[i] + inv[i]).clamp(0, max_val)) as i64;
s += d * d;
}
}
s
});
let zero_luma = vec![0i32; nn];
let chroma_bits = |cb_lv: &[i32], cr_lv: &[i32], ts: bool| -> f64 {
let mut tctx = ctx.clone();
let tu = TuCoeffs {
tree: TreeType::Single,
luma: &zero_luma,
lw: w,
lh: h,
chroma: Some((cb_lv, cr_lv, ccw, cch)),
lossless: false,
luma_bdpcm: false,
chroma_bdpcm: false,
luma_ts: false,
chroma_ts: ts,
code_dqp: None,
dep_quant: sh.dep_quant,
};
RD_ENC.with(|c| {
let tenc = &mut *c.borrow_mut();
tenc.reset();
encode_transform_tree(tenc, &mut tctx, &tu);
tenc.flushed_len() * 8
}) as f64
};
let rd_dct = ssd_dct as f64 + lambda * chroma_bits(&cb_dct, &cr_dct, false);
let ts_pick = if ccw <= 32 && cch <= 32 {
let cb_res: Vec<i32> = sblk_cb
.iter()
.zip(&sc.cpred_cb[..ncc])
.map(|(&s, &p)| s - p)
.collect();
let cr_res: Vec<i32> = sblk_cr
.iter()
.zip(&sc.cpred_cr[..ncc])
.map(|(&s, &p)| s - p)
.collect();
let cb_ts16 = quantize_ts_wh(&cb_res, ccw, cch, qp);
let cr_ts16 = quantize_ts_wh(&cr_res, ccw, cch, qp);
let cb_ts: Vec<i32> = cb_ts16[..ncc].iter().map(|&l| l as i32).collect();
let cr_ts: Vec<i32> = cr_ts16[..ncc].iter().map(|&l| l as i32).collect();
let ssd_ts = {
let mut s = 0i64;
for (sblk, pred, lv16) in [
(sblk_cb, &sc.cpred_cb[..ncc], &cb_ts16),
(sblk_cr, &sc.cpred_cr[..ncc], &cr_ts16),
] {
let deq = dequantize_ts_wh(lv16, ccw, cch, qp);
for i in 0..ncc {
let d = (sblk[i] - (pred[i] + deq[i]).clamp(0, max_val)) as i64;
s += d * d;
}
}
s
};
let rd_ts = ssd_ts as f64 + lambda * chroma_bits(&cb_ts, &cr_ts, true);
if rd_ts < rd_dct {
Some((cb_ts, cr_ts))
} else {
None
}
} else {
None
};
match ts_pick {
Some(lv) => {
chroma_ts = true;
lv
}
None => (cb_dct, cr_dct),
}
};
if lossless {
place_atomic_wh(&pl.rec_cb, &pl.av_cb, cwc, cx, cy, ccw, cch, sblk_cb);
place_atomic_wh(&pl.rec_cr, &pl.av_cr, cwc, cx, cy, ccw, cch, sblk_cr);
} else {
reconstruct_atomic_wh(
&pl.rec_cb,
&pl.av_cb,
cwc,
cx,
cy,
ccw,
cch,
&sc.cpred_cb[..ncc],
&cb_levels,
qp,
bit_depth,
max_val,
lossless,
chroma_ts,
0,
0,
sh.dep_quant,
0,
);
reconstruct_atomic_wh(
&pl.rec_cr,
&pl.av_cr,
cwc,
cx,
cy,
ccw,
cch,
&sc.cpred_cr[..ncc],
&cr_levels,
qp,
bit_depth,
max_val,
lossless,
chroma_ts,
0,
0,
sh.dep_quant,
0,
);
}
let _ = (cx, cy);
Some(ChromaDecision {
ccw,
cch,
chroma_mode,
chroma_bdpcm,
chroma_ts,
cb_levels,
cr_levels,
})
}
fn leaf_has_cbf(d: &LeafDecision) -> bool {
d.luma_levels.iter().any(|&v| v != 0)
|| d.chroma.as_ref().is_some_and(|c| {
c.cb_levels.iter().any(|&v| v != 0) || c.cr_levels.iter().any(|&v| v != 0)
})
}
#[allow(clippy::too_many_arguments)]
fn replay_luma(
enc: &mut CabacEncoder,
ctx: &mut Contexts,
grid: &mut crate::partition::NeighborGrid,
lossless: bool,
sps_lfnst: bool,
dep_quant: bool,
sps_mts: bool,
code_dqp: Option<i32>,
d: &LeafDecision,
) {
let mpm = build_mpm(
grid.left_mode_rect(d.x as u32, d.y as u32, d.h as u32),
grid.above_mode_rect(d.x as u32, d.y as u32, d.w as u32),
);
if lossless {
encode_bdpcm_mode(enc, ctx, d.luma_bdpcm, true);
}
if d.luma_bdpcm == 0 {
encode_luma_mode(enc, ctx, &mpm, d.best_mode);
}
grid.set_mode_rect(
d.x as u32,
d.y as u32,
d.w as u32,
d.h as u32,
d.luma_dir_mode,
);
let tu = TuCoeffs {
tree: TreeType::Luma,
luma: &d.luma_levels,
lw: d.w,
lh: d.h,
chroma: None,
lossless,
luma_bdpcm: d.luma_bdpcm != 0,
chroma_bdpcm: false,
luma_ts: d.luma_ts,
chroma_ts: false,
code_dqp,
dep_quant,
};
encode_transform_tree(enc, ctx, &tu);
if sps_lfnst && !lossless {
let lts = d.luma_ts || d.luma_bdpcm != 0;
if crate::residual::lfnst_present(
sps_lfnst,
d.w,
d.h,
(d.luma_levels.as_slice(), lts),
None,
) {
encode_lfnst_idx(enc, ctx, d.lfnst_idx, true);
}
}
if sps_mts
&& !lossless
&& d.lfnst_idx == 0
&& !d.luma_ts
&& d.luma_bdpcm == 0
&& d.w <= 32
&& d.h <= 32
&& crate::residual::mts_signallable(&d.luma_levels, d.w, d.h)
{
encode_mts_idx(enc, ctx, d.luma_mts_idx);
}
}
fn replay_chroma(
enc: &mut CabacEncoder,
ctx: &mut Contexts,
lossless: bool,
sps_lfnst: bool,
dep_quant: bool,
dm_mode: u8,
cclm: bool,
c: &ChromaDecision,
) {
if lossless {
encode_bdpcm_mode(enc, ctx, c.chroma_bdpcm, false);
}
if c.chroma_bdpcm == 0 {
encode_chroma_mode(enc, ctx, dm_mode, c.chroma_mode, cclm);
}
let tu = TuCoeffs {
tree: TreeType::Chroma,
luma: &[],
lw: c.ccw,
lh: c.cch,
chroma: Some((c.cb_levels.as_slice(), c.cr_levels.as_slice(), c.ccw, c.cch)),
lossless,
luma_bdpcm: false,
chroma_bdpcm: c.chroma_bdpcm != 0,
luma_ts: false,
chroma_ts: c.chroma_ts,
code_dqp: None,
dep_quant,
};
encode_transform_tree(enc, ctx, &tu);
if sps_lfnst && !lossless {
let cts = c.chroma_ts || c.chroma_bdpcm != 0;
let chroma = Some((
c.cb_levels.as_slice(),
c.cr_levels.as_slice(),
c.ccw,
c.cch,
cts,
));
if crate::residual::lfnst_present(sps_lfnst, c.ccw, c.cch, (&[], false), chroma) {
encode_lfnst_idx(enc, ctx, 0, true);
}
}
}
#[allow(clippy::too_many_arguments)]
fn replay_leaf(
enc: &mut CabacEncoder,
ctx: &mut Contexts,
grid: &mut crate::partition::NeighborGrid,
lossless: bool,
sps_lfnst: bool,
dep_quant: bool,
sps_mts: bool,
sps_cclm: bool,
code_dqp: Option<i32>,
d: &LeafDecision,
) {
let mpm = build_mpm(
grid.left_mode_rect(d.x as u32, d.y as u32, d.h as u32),
grid.above_mode_rect(d.x as u32, d.y as u32, d.w as u32),
);
if lossless {
encode_bdpcm_mode(enc, ctx, d.luma_bdpcm, true);
}
if d.luma_bdpcm == 0 {
encode_luma_mode(enc, ctx, &mpm, d.best_mode);
}
grid.set_mode_rect(
d.x as u32,
d.y as u32,
d.w as u32,
d.h as u32,
d.luma_dir_mode,
);
if let Some(c) = &d.chroma {
if lossless {
encode_bdpcm_mode(enc, ctx, c.chroma_bdpcm, false);
}
if c.chroma_bdpcm == 0 {
encode_chroma_mode(enc, ctx, d.luma_dir_mode, c.chroma_mode, sps_cclm);
}
}
let tu = TuCoeffs {
tree: TreeType::Single,
luma: &d.luma_levels,
lw: d.w,
lh: d.h,
chroma: d
.chroma
.as_ref()
.map(|c| (c.cb_levels.as_slice(), c.cr_levels.as_slice(), c.ccw, c.cch)),
lossless,
luma_bdpcm: d.luma_bdpcm != 0,
chroma_bdpcm: d.chroma.as_ref().is_some_and(|c| c.chroma_bdpcm != 0),
luma_ts: d.luma_ts,
chroma_ts: d.chroma.as_ref().is_some_and(|c| c.chroma_ts),
code_dqp,
dep_quant,
};
encode_transform_tree(enc, ctx, &tu);
if sps_lfnst && !lossless {
let lts = d.luma_ts || d.luma_bdpcm != 0;
let chroma = d.chroma.as_ref().map(|c| {
(
c.cb_levels.as_slice(),
c.cr_levels.as_slice(),
c.ccw,
c.cch,
c.chroma_ts || c.chroma_bdpcm != 0,
)
});
if crate::residual::lfnst_present(
sps_lfnst,
d.w,
d.h,
(d.luma_levels.as_slice(), lts),
chroma,
) {
encode_lfnst_idx(enc, ctx, d.lfnst_idx, false);
}
}
if sps_mts
&& !lossless
&& d.lfnst_idx == 0
&& !d.luma_ts
&& d.luma_bdpcm == 0
&& d.w <= 32
&& d.h <= 32
&& crate::residual::mts_signallable(&d.luma_levels, d.w, d.h)
{
encode_mts_idx(enc, ctx, d.luma_mts_idx);
}
}
fn encode_lfnst_idx(enc: &mut CabacEncoder, ctx: &mut Contexts, idx: u8, sep_tree: bool) {
let c0 = if sep_tree { 1 } else { 0 };
enc.encode_bin((idx > 0) as u8, &mut ctx.lfnst_idx[c0]);
if idx > 0 {
enc.encode_bin((idx == 2) as u8, &mut ctx.lfnst_idx[2]);
}
}
fn encode_mts_idx(enc: &mut CabacEncoder, ctx: &mut Contexts, idx: u8) {
enc.encode_bin((idx > 0) as u8, &mut ctx.mts_idx[0]);
if idx > 0 {
let k = idx - 1;
for i in 0..3u8 {
let bin = (k > i) as u8;
enc.encode_bin(bin, &mut ctx.mts_idx[1 + i as usize]);
if bin == 0 {
break;
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn mtt_rd_decide(
src: &[i32],
cw: usize,
x: u32,
y: u32,
w: u32,
h: u32,
qp: u8,
can: &[bool; 6],
min_w: u32,
min_h: u32,
) -> crate::partition::MttSplit {
use crate::partition::MttSplit::*;
let allowed = |s: crate::partition::MttSplit| match s {
None => can[0],
Quad => can[1],
BinH => can[2],
BinV => can[3],
TriH => can[4],
TriV => can[5],
};
let usable = |s: crate::partition::MttSplit| {
allowed(s)
&& match s {
None => true,
Quad => w / 2 >= min_w && h / 2 >= min_h,
BinV => w / 2 >= min_w,
BinH => h / 2 >= min_h,
TriV => w / 4 >= min_w,
TriH => h / 4 >= min_h,
}
};
let must_split = w > 64 || h > 64;
let ch = src.len() / cw;
let (xx, yy) = (x as usize, y as usize);
let bw = (w as usize).min(cw - xx);
let bh = (h as usize).min(ch - yy);
let (mut sum, mut sumsq, mut gx, mut gy, mut n) = (0i64, 0i64, 0i64, 0i64, 0i64);
for j in 0..bh {
let row = (yy + j) * cw + xx;
for i in 0..bw {
let v = src[row + i] as i64;
sum += v;
sumsq += v * v;
n += 1;
if i + 1 < bw {
gx += (src[row + i + 1] as i64 - v).abs();
}
if j + 1 < bh {
gy += (src[row + i + cw] as i64 - v).abs();
}
}
}
let var = if n > 0 {
sumsq / n - (sum / n) * (sum / n)
} else {
0
};
let thr = 64 + (qp as i64) * (qp as i64);
if !must_split && var < thr && allowed(None) {
return None;
}
let vert = (gx as i128) * 4 > (gy as i128) * 5;
let horz = (gy as i128) * 4 > (gx as i128) * 5;
let order = if w >= 2 * h {
[BinV, TriV, Quad, BinH, TriH]
} else if h >= 2 * w {
[BinH, TriH, Quad, BinV, TriV]
} else if vert {
[BinV, TriV, Quad, BinH, TriH]
} else if horz {
[BinH, TriH, Quad, BinV, TriV]
} else {
[Quad, BinV, BinH, TriV, TriH]
};
for s in order {
if usable(s) {
return s;
}
}
if allowed(None) && !must_split {
return None;
}
for s in [Quad, BinV, BinH, TriV, TriH] {
if allowed(s) {
return s;
}
}
None
}
fn encode_planes(
headers: Headers,
src_y: Vec<i32>,
src_cb: Vec<i32>,
src_cr: Vec<i32>,
threads: usize,
rdoq: bool,
) -> CoreOutput {
let chroma = headers.chroma;
let bit_depth = headers.bit_depth.bits();
let qp = headers.qp;
let lossless = headers.lossless;
let cw = headers.coded_width() as usize;
let ch = headers.coded_height() as usize;
let (sub_w, sub_h) = (chroma.sub_w(), chroma.sub_h());
let has_chroma = !chroma.is_monochrome();
let cwc = if has_chroma { cw / sub_w } else { 0 };
let chc = if has_chroma { ch / sub_h } else { 0 };
let max_val = (1i32 << bit_depth) - 1;
let slice_qp = (qp as i32 - 6 * (bit_depth as i32 - 8)).clamp(0, 63) as u8;
let rd_ctx = Contexts::new_intra(slice_qp);
let split_threshold = 64 + (qp as i64) * (qp as i64);
let decide_fn = |x: u32, y: u32, size: u32| -> bool {
if size > 64 {
return true;
}
if lossless && size > 32 {
return true;
}
size > 8
&& block_variance(&src_y, cw, x as usize, y as usize, size as usize) > split_threshold
};
let ctu = CTU_SIZE as usize;
let ctu_cols = cw.div_ceil(ctu);
let ctu_rows = ch.div_ceil(ctu);
let mut ctu_leaves: Vec<Vec<(usize, usize, usize, usize)>> =
vec![Vec::new(); ctu_cols * ctu_rows];
if !headers.mtt {
let mut throwaway = CabacEncoder::new();
let mut tctx = Contexts::new_intra(slice_qp);
let _ = code_partitions(
&mut throwaway,
&mut tctx,
cw as u32,
ch as u32,
&decide_fn,
|_enc, _ctx, _grid, x, y, size| {
let (xu, yu, nu) = (x as usize, y as usize, size as usize);
let cidx = (yu / ctu) * ctu_cols + (xu / ctu);
ctu_leaves[cidx].push((xu, yu, nu, nu));
},
);
}
let total_leaves: usize = ctu_leaves.iter().map(|v| v.len()).sum();
let qp_bd_offset = 6 * (bit_depth as i32 - 8);
let base_qpy = qp as i32 - qp_bd_offset; let qg = if headers.aq && headers.dual_tree {
64 } else if headers.aq && !headers.mtt {
AQ_QG
} else {
ctu
};
let qg_cols = cw.div_ceil(qg);
let qg_rows = ch.div_ceil(qg);
let qg_dq: Vec<i32> = if headers.aq {
aq_qg_targets(&src_y, cw, ch, qg, qg_cols, qg_rows, qp)
} else {
vec![qp as i32; qg_cols * qg_rows]
};
let qg_target_qpy: Vec<i32> = qg_dq.iter().map(|&d| d - qp_bd_offset).collect();
let qg_qpprime: Vec<u8> = qg_dq
.iter()
.map(|&d| d.clamp(0, 63 + qp_bd_offset) as u8)
.collect();
let shared = LeafShared {
src_y: &src_y,
src_cb: &src_cb,
src_cr: &src_cr,
cw,
ch,
cwc,
chc,
sub_w,
sub_h,
has_chroma,
bit_depth,
qp,
lossless,
rdoq,
aq: headers.aq,
lfnst: headers.lfnst,
dep_quant: headers.dep_quant,
mts: headers.mts,
cclm: headers.cclm,
qg,
qg_cols,
qg_qpprime: &qg_qpprime,
max_val,
rd_ctx: &rd_ctx,
};
if headers.dual_tree && has_chroma {
use crate::partition::{MttCfg, MttSplit, code_partitions_dual};
fn first_legal(can: &[bool; 6]) -> MttSplit {
if can[1] {
return MttSplit::Quad;
}
if can[2] {
return MttSplit::BinH;
}
if can[3] {
return MttSplit::BinV;
}
if can[4] {
return MttSplit::TriH;
}
if can[5] {
return MttSplit::TriV;
}
MttSplit::None
}
let planes = Planes::new(cw, ch, cwc, chc);
let lambda = 0.57f64 * 2f64.powf((qp as f64 - 12.0) / 3.0);
let luma_cfg = if headers.mtt {
crate::partition::mtt_cfg()
} else {
MttCfg {
max_mtt_depth: 0,
min_qt_size: 1 << crate::headers::LOG2_MIN_CB_SIZE,
max_bt_size: 64,
max_tt_size: 64,
}
};
let chroma_cfg = MttCfg {
max_mtt_depth: 0,
min_qt_size: 64,
max_bt_size: 64,
max_tt_size: 64,
};
let use_mtt = headers.mtt;
let luma_decide = |x: u32, y: u32, w: u32, h: u32, can: &[bool; 6]| -> MttSplit {
if use_mtt {
return mtt_rd_decide(&src_y, cw, x, y, w, h, qp, can, 4, 4);
}
if w > 64 || h > 64 {
return MttSplit::Quad;
}
if !can[0] {
return first_legal(can);
}
if can[1]
&& w > 8
&& block_variance(&src_y, cw, x as usize, y as usize, w as usize) > split_threshold
{
MttSplit::Quad
} else {
MttSplit::None
}
};
let chroma_decide = |_x: u32, _y: u32, _w: u32, _h: u32, can: &[bool; 6]| -> MttSplit {
if can[0] {
MttSplit::None
} else {
first_legal(can)
}
};
let nctu = ctu_cols * ctu_rows;
let mut luma_geom: Vec<Vec<(u32, u32, u32, u32)>> = vec![Vec::new(); nctu];
let mut chroma_geom: Vec<Vec<(u32, u32, u32, u32)>> = vec![Vec::new(); nctu];
{
let mut tenc = CabacEncoder::new();
let mut tctx = Contexts::new_intra(slice_qp);
let lg = std::cell::RefCell::new(&mut luma_geom);
let cg = std::cell::RefCell::new(&mut chroma_geom);
let ctu_u = ctu as u32;
code_partitions_dual(
&mut tenc,
&mut tctx,
cw as u32,
ch as u32,
luma_cfg,
chroma_cfg,
luma_decide,
|_enc, _ctx, _grid, x, y, w, h| {
let cidx = (y as usize / ctu) * ctu_cols + x as usize / ctu;
lg.borrow_mut()[cidx].push((x, y, w, h));
let _ = ctu_u;
},
chroma_decide,
|_enc, _ctx, _grid, x, y, w, h| {
let cidx = (y as usize / ctu) * ctu_cols + x as usize / ctu;
cg.borrow_mut()[cidx].push((x, y, w, h));
},
);
}
let luma_res: Vec<Vec<LeafDecision>> =
run_ctu_wavefront(ctu_cols, ctu_rows, threads, |cidx, sc| {
let mut ld = Vec::with_capacity(luma_geom[cidx].len());
for &(x, y, w, h) in &luma_geom[cidx] {
ld.push(analyze_leaf(
&shared,
&planes,
sc,
x as usize,
y as usize,
w as usize,
h as usize,
TreeType::Luma,
));
}
ld
});
let mut qg_map: Vec<i32> = vec![base_qpy; qg_cols * qg_rows];
let mut qg_delta: Vec<Option<i32>> = vec![None; qg_cols * qg_rows];
let cw4 = cw.div_ceil(4);
let mut luma_qp: Vec<u8> = vec![qp; cw4 * ch.div_ceil(4)];
if headers.aq {
let mut cur_qg = usize::MAX;
let mut prev = base_qpy;
let mut cur_qp_y = base_qpy;
let mut dqp_done = false;
#[allow(clippy::needless_range_loop)]
for cidx in 0..nctu {
for d in &luma_res[cidx] {
let (qx, qy) = (d.x & !(qg - 1), d.y & !(qg - 1));
let qgi = (qy / qg) * qg_cols + qx / qg;
if qgi != cur_qg {
prev = cur_qp_y;
cur_qp_y = aq_predict_qp(&qg_map, qg_cols, qg, ctu, qx, qy, prev);
aq_fill_qg(&mut qg_map, qg_cols, qg, d.x, d.y, d.w, cur_qp_y);
dqp_done = false;
cur_qg = qgi;
}
if !dqp_done && leaf_has_cbf(d) {
let delta = qg_target_qpy[qgi] - cur_qp_y;
qg_delta[qgi] = Some(delta);
cur_qp_y = if delta == 0 {
cur_qp_y
} else {
((cur_qp_y + delta + 64 + 2 * qp_bd_offset) % (64 + qp_bd_offset))
- qp_bd_offset
};
aq_fill_qg(&mut qg_map, qg_cols, qg, d.x, d.y, d.w, cur_qp_y);
dqp_done = true;
}
let lqp = (cur_qp_y + qp_bd_offset).clamp(0, 63 + qp_bd_offset) as u8;
let _ = prev;
for yy in (d.y / 4)..((d.y + d.h).div_ceil(4)) {
for xx in (d.x / 4)..((d.x + d.w).div_ceil(4)) {
luma_qp[yy * cw4 + xx] = lqp;
}
}
}
}
}
let chroma_qp_at = |x: usize, y: usize, w: usize, h: usize| -> u8 {
let lx = ((x / sub_w + ((w / sub_w) >> 1)) * sub_w).min(cw - 1);
let ly = ((y / sub_h + ((h / sub_h) >> 1)) * sub_h).min(ch - 1);
luma_qp[(ly / 4) * cw4 + lx / 4]
};
let chroma_res: Vec<Vec<Option<ChromaDecision>>> =
run_ctu_wavefront(ctu_cols, ctu_rows, threads, |cidx, sc| {
let mut cd = Vec::with_capacity(chroma_geom[cidx].len());
for &(x, y, w, h) in &chroma_geom[cidx] {
let cqp = chroma_qp_at(x as usize, y as usize, w as usize, h as usize);
let dm = planes.modes.chroma_dm(x, y, w, h);
cd.push(analyze_chroma(
&shared,
&planes,
sc,
x as usize,
y as usize,
w as usize,
h as usize,
cqp,
lambda,
max_val,
dm,
headers.cclm,
));
}
cd
});
let mut enc = CabacEncoder::new();
let mut ctx = Contexts::new_intra(slice_qp);
let lcur = std::cell::RefCell::new(vec![0usize; nctu]);
let ccur = std::cell::RefCell::new(vec![0usize; nctu]);
let dqp_coded = std::cell::RefCell::new(vec![false; qg_cols * qg_rows]);
let aq = headers.aq;
let (luma_leaves, _chroma_leaves) = code_partitions_dual(
&mut enc,
&mut ctx,
cw as u32,
ch as u32,
luma_cfg,
chroma_cfg,
luma_decide,
|enc, ctx, grid, x, y, _w, _h| {
let cidx = (y as usize / ctu) * ctu_cols + x as usize / ctu;
let k = {
let mut c = lcur.borrow_mut();
let k = c[cidx];
c[cidx] += 1;
k
};
let d = &luma_res[cidx][k];
let code_dqp = if aq && leaf_has_cbf(d) {
let qgi = (y as usize / qg) * qg_cols + x as usize / qg;
let mut dc = dqp_coded.borrow_mut();
if !dc[qgi] {
dc[qgi] = true;
qg_delta[qgi]
} else {
None
}
} else {
None
};
replay_luma(
enc,
ctx,
grid,
lossless,
headers.lfnst,
headers.dep_quant,
headers.mts,
code_dqp,
d,
);
},
chroma_decide,
|enc, ctx, _grid, x, y, w, h| {
let cidx = (y as usize / ctu) * ctu_cols + x as usize / ctu;
let k = {
let mut c = ccur.borrow_mut();
let k = c[cidx];
c[cidx] += 1;
k
};
if let Some(c) = &chroma_res[cidx][k] {
let dm = planes.modes.chroma_dm(x, y, w, h);
replay_chroma(
enc,
ctx,
lossless,
headers.lfnst,
headers.dep_quant,
dm,
headers.cclm,
c,
);
}
},
);
enc.encode_terminate(1);
let slice_data = enc.finish();
let (rec_y, rec_cb, rec_cr) = planes.into_recon();
return CoreOutput {
stream: headers.write_still_picture(&slice_data),
slice_data,
rec_y,
rec_cb,
rec_cr,
cw,
ch,
cwc,
chc,
qp,
bit_depth,
leaf_count: luma_leaves.len(),
};
}
if headers.mtt {
let cfg = crate::partition::mtt_cfg();
let (min_w, min_h) = (4 * sub_w as u32, 4 * sub_h as u32);
let mut mtt_leaves: Vec<Vec<(usize, usize, usize, usize)>> =
vec![Vec::new(); ctu_cols * ctu_rows];
{
let mut throwaway = CabacEncoder::new();
let mut tctx = Contexts::new_intra(slice_qp);
let _ = crate::partition::code_partitions_mtt(
&mut throwaway,
&mut tctx,
cw as u32,
ch as u32,
cfg,
|x, y, w, h, can| mtt_rd_decide(&src_y, cw, x, y, w, h, qp, can, min_w, min_h),
|_enc, _ctx, _grid, x, y, w, h| {
let (xu, yu) = (x as usize, y as usize);
let cidx = (yu / ctu) * ctu_cols + xu / ctu;
mtt_leaves[cidx].push((xu, yu, w as usize, h as usize));
},
);
}
let planes = Planes::new(cw, ch, cwc, chc);
let ctu_decisions =
analyze_ctus_wavefront(&shared, &planes, &mtt_leaves, ctu_cols, ctu_rows, threads);
let mut enc = CabacEncoder::new();
let mut ctx = Contexts::new_intra(slice_qp);
let mut cursor = vec![0usize; ctu_cols * ctu_rows];
let aq = headers.aq;
let mut qg_map: Vec<i32> = vec![base_qpy; qg_cols * qg_rows];
let mut cur_qg: usize = usize::MAX;
let mut prev_qp: i32 = base_qpy;
let mut cur_qg_qp_y: i32 = base_qpy;
let mut qg_dqp_coded = false;
let leaves = crate::partition::code_partitions_mtt(
&mut enc,
&mut ctx,
cw as u32,
ch as u32,
cfg,
|x, y, w, h, can| mtt_rd_decide(&src_y, cw, x, y, w, h, qp, can, min_w, min_h),
|enc, ctx, grid, x, y, w, h| {
let (xu, yu, wu) = (x as usize, y as usize, w as usize);
let cidx = (yu / ctu) * ctu_cols + xu / ctu;
let k = cursor[cidx];
cursor[cidx] += 1;
let d = &ctu_decisions[cidx][k];
let _ = (w, h);
let mut code_dqp = None;
if aq {
let (qx, qy) = (xu & !(qg - 1), yu & !(qg - 1));
let qidx = (qy / qg) * qg_cols + qx / qg;
if qidx != cur_qg {
prev_qp = cur_qg_qp_y;
cur_qg_qp_y = aq_predict_qp(&qg_map, qg_cols, qg, ctu, qx, qy, prev_qp);
aq_fill_qg(&mut qg_map, qg_cols, qg, xu, yu, wu, cur_qg_qp_y);
qg_dqp_coded = false;
cur_qg = qidx;
}
if !qg_dqp_coded && leaf_has_cbf(d) {
let target = qg_target_qpy[qidx];
let delta = target - cur_qg_qp_y;
cur_qg_qp_y = if delta == 0 {
cur_qg_qp_y
} else {
((cur_qg_qp_y + delta + 64 + 2 * qp_bd_offset) % (64 + qp_bd_offset))
- qp_bd_offset
};
aq_fill_qg(&mut qg_map, qg_cols, qg, xu, yu, wu, cur_qg_qp_y);
qg_dqp_coded = true;
code_dqp = Some(delta);
}
}
replay_leaf(
enc,
ctx,
grid,
lossless,
headers.lfnst,
headers.dep_quant,
headers.mts,
headers.cclm,
code_dqp,
d,
);
},
);
enc.encode_terminate(1);
let slice_data = enc.finish();
let (rec_y, rec_cb, rec_cr) = planes.into_recon();
return CoreOutput {
stream: headers.write_still_picture(&slice_data),
slice_data,
rec_y,
rec_cb,
rec_cr,
cw,
ch,
cwc,
chc,
qp,
bit_depth,
leaf_count: leaves.len(),
};
}
let planes = Planes::new(cw, ch, cwc, chc);
let ctu_decisions =
analyze_ctus_wavefront(&shared, &planes, &ctu_leaves, ctu_cols, ctu_rows, threads);
let mut enc = CabacEncoder::new();
let mut ctx = Contexts::new_intra(slice_qp);
let mut cursor = vec![0usize; ctu_cols * ctu_rows];
let aq = headers.aq;
let mut qg_map: Vec<i32> = vec![base_qpy; qg_cols * qg_rows];
let mut cur_qg: usize = usize::MAX;
let mut prev_qp: i32 = base_qpy;
let mut cur_qg_qp_y: i32 = base_qpy;
let mut qg_dqp_coded = false;
let _leaves = code_partitions(
&mut enc,
&mut ctx,
cw as u32,
ch as u32,
decide_fn,
|enc, ctx, grid, x, y, size| {
let (xu, yu, nu) = (x as usize, y as usize, size as usize);
let cidx = (yu / ctu) * ctu_cols + (xu / ctu);
let k = cursor[cidx];
cursor[cidx] += 1;
let d = &ctu_decisions[cidx][k];
let mut code_dqp = None;
if aq {
let (qx, qy) = (xu & !(qg - 1), yu & !(qg - 1));
let qidx = (qy / qg) * qg_cols + qx / qg;
if qidx != cur_qg {
prev_qp = cur_qg_qp_y;
cur_qg_qp_y = aq_predict_qp(&qg_map, qg_cols, qg, ctu, qx, qy, prev_qp);
aq_fill_qg(&mut qg_map, qg_cols, qg, xu, yu, nu, cur_qg_qp_y);
qg_dqp_coded = false;
cur_qg = qidx;
}
if !qg_dqp_coded && leaf_has_cbf(d) {
let target = qg_target_qpy[qidx];
let delta = target - cur_qg_qp_y;
cur_qg_qp_y = if delta == 0 {
cur_qg_qp_y
} else {
((cur_qg_qp_y + delta + 64 + 2 * qp_bd_offset) % (64 + qp_bd_offset))
- qp_bd_offset
};
aq_fill_qg(&mut qg_map, qg_cols, qg, xu, yu, nu, cur_qg_qp_y);
qg_dqp_coded = true;
code_dqp = Some(delta);
}
}
replay_leaf(
enc,
ctx,
grid,
lossless,
headers.lfnst,
headers.dep_quant,
headers.mts,
headers.cclm,
code_dqp,
d,
);
},
);
enc.encode_terminate(1);
let slice_data = enc.finish();
let (mut rec_y, mut rec_cb, mut rec_cr) = planes.into_recon();
if headers.deblock {
let mut grid = crate::deblock::Grid::new(cw, ch);
for leaves in &ctu_leaves {
for &(x, y, w, h) in leaves {
grid.set_cu(x, y, w, h, qp);
}
}
crate::deblock::deblock_luma(&mut rec_y, cw, ch, &grid, bit_depth, ctu);
if has_chroma {
let subx = sub_w.trailing_zeros() as usize;
let suby = sub_h.trailing_zeros() as usize;
crate::deblock::deblock_chroma(
&mut rec_cb,
cwc,
chc,
&grid,
subx,
suby,
bit_depth,
ctu,
);
crate::deblock::deblock_chroma(
&mut rec_cr,
cwc,
chc,
&grid,
subx,
suby,
bit_depth,
ctu,
);
}
}
CoreOutput {
stream: headers.write_still_picture(&slice_data),
slice_data,
rec_y,
rec_cb,
rec_cr,
cw,
ch,
cwc,
chc,
qp,
bit_depth,
leaf_count: total_leaves,
}
}
pub(crate) fn encode_still_yuv(
planes: &[u8],
width: u32,
height: u32,
qp: u8,
bit_depth: u8,
lossless: bool,
source_chroma: ChromaFormat,
threads: usize,
rdoq: bool,
aq: bool,
mtt: bool,
lfnst: bool,
dep_quant: bool,
mts: bool,
dual_tree: bool,
cclm: bool,
deblock: bool,
) -> Result<Vec<u8>, crate::error::EncodeError> {
let chroma = source_chroma.for_dimensions(width, height);
let dual = dual_tree && !lossless && chroma.allows_dual_tree();
let lfnst_eff = lfnst;
let dep_quant_eff = dep_quant;
let headers = Headers {
width,
height,
chroma,
bit_depth: crate::fmt::BitDepth::from_bits(bit_depth),
qp,
lossless,
aq: aq && !lossless,
mtt: mtt && !lossless,
lfnst: lfnst_eff,
dep_quant: dep_quant_eff,
mts,
dual_tree: dual,
cclm: cclm && !lossless,
deblock: deblock && bit_depth == 8 && !aq && !dual && !lossless && !mtt,
};
let cw = headers.coded_width() as usize;
let ch = headers.coded_height() as usize;
let (w, h) = (width as usize, height as usize);
let (sub_w, sub_h) = (chroma.sub_w(), chroma.sub_h());
let (source_sub_w, source_sub_h) = (source_chroma.sub_w(), source_chroma.sub_h());
let has_chroma = !chroma.is_monochrome();
let cwc = if has_chroma { cw / sub_w } else { 0 };
let chc = if has_chroma { ch / sub_h } else { 0 };
let dcw = if has_chroma {
w.div_ceil(source_sub_w)
} else {
0
};
let dch = if has_chroma {
h.div_ceil(source_sub_h)
} else {
0
};
let two_byte = bit_depth > 8;
let bpp = if two_byte { 2 } else { 1 };
let max_val = (1i32 << bit_depth) - 1;
let expect = (w * h + 2 * dcw * dch) * bpp;
if planes.len() != expect {
return Err(crate::error::EncodeError::Unsupported(
"YCbCr buffer length does not match the given dimensions, chroma format and bit depth",
));
}
let rd = |plane: &[u8], idx: usize| -> i32 {
if two_byte {
u16::from_le_bytes([plane[idx * 2], plane[idx * 2 + 1]]) as i32
} else {
plane[idx] as i32
}
};
let y_plane = &planes[..w * h * bpp];
let cb_plane = &planes[w * h * bpp..(w * h + dcw * dch) * bpp];
let cr_plane = &planes[(w * h + dcw * dch) * bpp..];
let mut src_y = vec![0i32; cw * ch];
for y in 0..ch {
let sy = y.min(h - 1);
for x in 0..cw {
let sx = x.min(w - 1);
src_y[y * cw + x] = rd(y_plane, sy * w + sx).clamp(0, max_val);
}
}
let mut src_cb = vec![0i32; cwc * chc];
let mut src_cr = vec![0i32; cwc * chc];
if has_chroma {
for cy in 0..chc {
let sy = (cy * sub_h / source_sub_h).min(dch - 1);
for cx in 0..cwc {
let sx = (cx * sub_w / source_sub_w).min(dcw - 1);
src_cb[cy * cwc + cx] = rd(cb_plane, sy * dcw + sx).clamp(0, max_val);
src_cr[cy * cwc + cx] = rd(cr_plane, sy * dcw + sx).clamp(0, max_val);
}
}
}
Ok(encode_planes(headers, src_y, src_cb, src_cr, threads, rdoq).stream)
}
pub(crate) fn encode_still(
rgb: &[u8],
width: u32,
height: u32,
qp: u8,
bit_depth: u8,
stride_px: usize,
lossless: bool,
chroma: ChromaFormat,
threads: usize,
rdoq: bool,
aq: bool,
mtt: bool,
lfnst: bool,
dep_quant: bool,
mts: bool,
dual_tree: bool,
cclm: bool,
deblock: bool,
) -> Vec<u8> {
let scale_shift = (bit_depth - 8) as u32;
encode_core(
rgb,
width,
height,
qp,
bit_depth,
stride_px,
lossless,
chroma,
scale_shift,
threads,
rdoq,
aq,
mtt,
lfnst,
dep_quant,
mts,
dual_tree,
cclm,
deblock,
)
.stream
}
pub(crate) fn encode_still_wide(
rgb: &[u16],
width: u32,
height: u32,
qp: u8,
bit_depth: u8,
stride_px: usize,
lossless: bool,
chroma: ChromaFormat,
threads: usize,
rdoq: bool,
aq: bool,
mtt: bool,
lfnst: bool,
dep_quant: bool,
mts: bool,
dual_tree: bool,
cclm: bool,
deblock: bool,
) -> Vec<u8> {
encode_core(
rgb, width, height, qp, bit_depth, stride_px, lossless, chroma, 0, threads, rdoq, aq, mtt,
lfnst, dep_quant, mts, dual_tree, cclm, deblock,
)
.stream
}
pub(crate) fn encode_with_recon(
rgb: &[u8],
width: u32,
height: u32,
qp: u8,
bit_depth: u8,
stride_px: usize,
lossless: bool,
chroma: ChromaFormat,
rdoq: bool,
aq: bool,
mtt: bool,
lfnst: bool,
dep_quant: bool,
mts: bool,
dual_tree: bool,
cclm: bool,
deblock: bool,
) -> (Vec<u8>, Vec<u8>) {
let chroma = chroma.for_dimensions(width, height);
let scale_shift = (bit_depth - 8) as u32;
let o = encode_core(
rgb,
width,
height,
qp,
bit_depth,
stride_px,
lossless,
chroma,
scale_shift,
1,
rdoq,
aq,
mtt,
lfnst,
dep_quant,
mts,
dual_tree,
cclm,
deblock,
);
let (w, h) = (width as usize, height as usize);
let has_chroma = !chroma.is_monochrome();
let (dcw, dch) = if has_chroma {
(w.div_ceil(chroma.sub_w()), h.div_ceil(chroma.sub_h()))
} else {
(0, 0)
};
let two_byte = bit_depth > 8;
let bytes_per = if two_byte { 2 } else { 1 };
let mut yuv = Vec::with_capacity((w * h + 2 * dcw * dch) * bytes_per);
let push = |v: i32, out: &mut Vec<u8>| {
if two_byte {
out.extend_from_slice(&(v as u16).to_le_bytes());
} else {
out.push(v as u8);
}
};
for y in 0..h {
for x in 0..w {
push(o.rec_y[y * o.cw + x], &mut yuv);
}
}
if has_chroma {
for y in 0..dch {
for x in 0..dcw {
push(o.rec_cb[y * o.cwc + x], &mut yuv);
}
}
for y in 0..dch {
for x in 0..dcw {
push(o.rec_cr[y * o.cwc + x], &mut yuv);
}
}
}
(o.stream, yuv)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cabac::engine::CabacDecoder;
use crate::intra::{build_mpm, decode_chroma_mode, decode_luma_mode};
use crate::partition::test_support::decode_partitions;
use crate::tu::test_support::decode_transform_unit;
fn recon_planar(o: &CoreOutput, has_chroma: bool, two_byte: bool) -> Vec<u8> {
let push = |v: i32, out: &mut Vec<u8>| {
if two_byte {
out.extend_from_slice(&(v as u16).to_le_bytes());
} else {
out.push(v as u8);
}
};
let mut yuv = Vec::new();
for &v in &o.rec_y[..o.cw * o.ch] {
push(v, &mut yuv);
}
if has_chroma {
for &v in &o.rec_cb[..o.cwc * o.chc] {
push(v, &mut yuv);
}
for &v in &o.rec_cr[..o.cwc * o.chc] {
push(v, &mut yuv);
}
}
yuv
}
#[test]
fn aq_encoder_decoder_agree_8_and_10_bit() {
let (w, h) = (256usize, 256usize);
let mut rgb8 = vec![0u8; w * h * 3];
for y in 0..h {
for x in 0..w {
let o = (y * w + x) * 3;
let noisy = (x / 40 + y / 40) % 2 == 1;
let v = if noisy {
(((x * 131) ^ (y * 197)) & 0xff) as u8
} else {
100
};
rgb8[o] = v;
rgb8[o + 1] = v;
rgb8[o + 2] = v;
}
}
let rgb10: Vec<u16> = rgb8.iter().map(|&b| (b as u16) << 2).collect();
for &qp in &[9u8, 18, 33] {
for chroma in [
ChromaFormat::Yuv420,
ChromaFormat::Yuv444,
ChromaFormat::Monochrome,
] {
let has_chroma = !chroma.is_monochrome();
let o8 = encode_core(
&rgb8, w as u32, h as u32, qp, 8, 3, false, chroma, 0, 1, true, true, false,
false, false, false, false, false, false,
);
let img8 = crate::decode_266(&o8.stream).expect("8-bit AQ decode");
assert_eq!(
img8.planes,
recon_planar(&o8, has_chroma, false),
"8-bit AQ enc/dec mismatch qp{qp} {chroma:?}"
);
let o10 = encode_core(
&rgb10, w as u32, h as u32, qp, 10, 3, false, chroma, 0, 1, true, true, false,
false, false, false, false, false, false,
);
let img10 = crate::decode_266(&o10.stream).expect("10-bit AQ decode");
assert_eq!(
img10.planes,
recon_planar(&o10, has_chroma, true),
"10-bit AQ enc/dec mismatch qp{qp} {chroma:?}"
);
}
}
}
#[test]
fn aq_changes_stream_and_is_deterministic() {
let (w, h) = (256u32, 256u32);
let mut rgb = vec![0u8; (w * h) as usize * 3];
for y in 0..h as usize {
for x in 0..w as usize {
let o = (y * w as usize + x) * 3;
let noisy = (x / 128 + y / 128) % 2 == 1;
let v = if noisy {
(((x * 131) ^ (y * 197)) & 0xff) as u8
} else {
100
};
rgb[o] = v;
rgb[o + 1] = v;
rgb[o + 2] = v;
}
}
for q in [40u8, 70, 90] {
let base = crate::EncodeConfig::new()
.with_quality(q)
.with_chroma(ChromaFormat::Yuv420);
let off = crate::encode_rgb_266(&rgb, w, h, &base.clone().with_aq(false)).unwrap();
let on = crate::encode_rgb_266(&rgb, w, h, &base.clone().with_aq(true)).unwrap();
assert_ne!(off, on, "AQ should change the stream at q{q}");
crate::decode_266(&on).expect("AQ stream must decode");
let on2 = crate::encode_rgb_266(&rgb, w, h, &base.with_aq(true)).unwrap();
assert_eq!(on, on2, "AQ must be deterministic at q{q}");
}
}
fn ebsp_to_rbsp(ebsp: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(ebsp.len());
let mut zeros = 0;
let mut i = 0;
while i < ebsp.len() {
let b = ebsp[i];
if zeros >= 2 && b == 0x03 && i + 1 < ebsp.len() && ebsp[i + 1] <= 0x03 {
zeros = 0; } else {
out.push(b);
zeros = if b == 0 { zeros + 1 } else { 0 };
}
i += 1;
}
out
}
fn split_nals(stream: &[u8]) -> Vec<Vec<u8>> {
let mut nals = Vec::new();
let mut i = 0;
let mut start: Option<usize> = None;
while i + 3 <= stream.len() {
let sc3 = stream[i] == 0 && stream[i + 1] == 0 && stream[i + 2] == 1;
if sc3 {
if let Some(s) = start {
let mut end = i;
if end > s && stream[end - 1] == 0 {
end -= 1; }
nals.push(stream[s..end].to_vec());
}
i += 3;
start = Some(i);
} else {
i += 1;
}
}
if let Some(s) = start {
nals.push(stream[s..].to_vec());
}
nals
}
fn decode_slice(o: &CoreOutput) -> (Vec<i32>, Vec<i32>, Vec<i32>) {
let (cw, ch, cwc, chc) = (o.cw, o.ch, o.cwc, o.chc);
let (qp, bd) = (o.qp, o.bit_depth);
let max_val = (1i32 << bd) - 1;
let mut dec = CabacDecoder::new(&o.slice_data);
let mut ctx = Contexts::new_intra(qp);
let mut ry = vec![0i32; cw * ch];
let mut ay = vec![false; cw * ch];
let mut rcb = vec![0i32; cwc * chc];
let mut acb = vec![false; cwc * chc];
let mut rcr = vec![0i32; cwc * chc];
let mut acr = vec![false; cwc * chc];
decode_partitions(
&mut dec,
&mut ctx,
cw as u32,
ch as u32,
|dec, ctx, grid, x, y, size| {
let (x, y, n) = (x as usize, y as usize, size as usize);
let mpm = build_mpm(
grid.left_mode(x as u32, y as u32, n as u32),
grid.above_mode(x as u32, y as u32, n as u32),
);
let mode = decode_luma_mode(dec, ctx, &mpm);
grid.set_mode(x as u32, y as u32, n as u32, mode);
let chroma_mode = decode_chroma_mode(dec, ctx, mode, false);
let tu = decode_transform_unit(dec, ctx, n, n, Some((n / 2, n / 2)));
let refs_y = gather_refs(&ry, &ay, cw, ch, x, y, n, bd);
let pred_y = crate::predict::predict(mode, n, n, &refs_y, bd, true);
reconstruct(
&mut ry, &mut ay, cw, x, y, n, &pred_y, &tu.luma, qp, bd, max_val, false,
tu.luma_ts, 0, 0, false, 0,
);
let (cx, cy, cn) = (x / 2, y / 2, n / 2);
let refs_cb = gather_refs(&rcb, &acb, cwc, chc, cx, cy, cn, bd);
let pred_cb = crate::predict::predict(chroma_mode, cn, cn, &refs_cb, bd, false);
reconstruct(
&mut rcb, &mut acb, cwc, cx, cy, cn, &pred_cb, &tu.cb, qp, bd, max_val, false,
tu.cb_ts, 0, 0, false, 0,
);
let refs_cr = gather_refs(&rcr, &acr, cwc, chc, cx, cy, cn, bd);
let pred_cr = crate::predict::predict(chroma_mode, cn, cn, &refs_cr, bd, false);
reconstruct(
&mut rcr, &mut acr, cwc, cx, cy, cn, &pred_cr, &tu.cr, qp, bd, max_val, false,
tu.cr_ts, 0, 0, false, 0,
);
},
);
assert_eq!(
dec.decode_terminate(),
1,
"slice data did not terminate cleanly"
);
(ry, rcb, rcr)
}
#[test]
fn yuv444_64wide_chroma_roundtrips() {
let (w, h) = (160usize, 160usize);
let mut rgb = vec![0u8; w * h * 3];
let mut s = 0xABCDEF123456u64;
for y in 0..h {
for x in 0..w {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
let n = (s & 7) as i32;
let i = (y * w + x) * 3;
rgb[i] = ((x * 255 / w) as i32 + n).clamp(0, 255) as u8;
rgb[i + 1] = ((y * 255 / h) as i32 + n).clamp(0, 255) as u8;
rgb[i + 2] = (128 + n) as u8;
}
}
for &q in &[85u8, 70, 50] {
let cfg = crate::EncodeConfig::new()
.with_quality(q)
.with_chroma(crate::ChromaFormat::Yuv444);
let (stream, recon) =
crate::encode_rgb_with_reconstruction(&rgb, w as u32, h as u32, &cfg).unwrap();
let img = crate::decode_266(&stream).expect("decode failed");
assert_eq!(
img.planes, recon,
"4:4:4 64-wide chroma round-trip mismatch q={q}"
);
}
}
#[test]
fn dual_tree_decodes_to_encoder_reconstruction() {
for &chroma in &[
ChromaFormat::Yuv420,
ChromaFormat::Yuv422,
ChromaFormat::Yuv444,
] {
for &(w, h) in &[(64usize, 64usize), (128, 128), (96, 80), (192, 136)] {
let img = gradient_rgb(w, h);
for &q in &[80u8, 55] {
let cfg = crate::EncodeConfig::new()
.with_quality(q)
.with_chroma(chroma)
.with_dual_tree(true);
let (stream, recon) =
crate::encode_rgb_with_reconstruction(&img, w as u32, h as u32, &cfg)
.unwrap();
let dec = crate::decode_266(&stream).expect("dual-tree decode");
assert_eq!(
dec.planes, recon,
"dual round-trip mismatch {chroma:?} {w}x{h} q={q}"
);
}
}
}
}
#[test]
fn cclm_decodes_to_encoder_reconstruction() {
for &chroma in &[
ChromaFormat::Yuv420,
ChromaFormat::Yuv422,
ChromaFormat::Yuv444,
] {
for &(w, h) in &[(64usize, 64usize), (128, 128), (192, 136)] {
let img = gradient_rgb(w, h);
for &q in &[80u8, 55] {
let cfg = crate::EncodeConfig::new()
.with_quality(q)
.with_chroma(chroma)
.with_dual_tree(true)
.with_cclm(true);
let (stream, recon) =
crate::encode_rgb_with_reconstruction(&img, w as u32, h as u32, &cfg)
.unwrap();
let dec = crate::decode_266(&stream).expect("cclm decode");
assert_eq!(
dec.planes, recon,
"cclm round-trip mismatch {chroma:?} {w}x{h} q={q}"
);
}
}
}
}
#[test]
fn slice_data_decodes_to_encoder_reconstruction() {
let (w, h) = (48usize, 40usize);
let img = gradient_rgb(w, h);
for &qp in &[10u8, 27, 45] {
let o = encode_core(
&img,
w as u32,
h as u32,
qp,
8,
3,
false,
ChromaFormat::Yuv420,
0,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
);
let (ry, rcb, rcr) = decode_slice(&o);
assert_eq!(ry, o.rec_y, "luma mismatch qp={qp}");
assert_eq!(rcb, o.rec_cb, "Cb mismatch qp={qp}");
assert_eq!(rcr, o.rec_cr, "Cr mismatch qp={qp}");
}
}
#[test]
fn full_annexb_stream_decodes_to_reconstruction() {
let (w, h) = (40usize, 24usize);
let img = gradient_rgb(w, h);
let o = encode_core(
&img,
w as u32,
h as u32,
26,
8,
3,
false,
ChromaFormat::Yuv420,
0,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
);
let nals = split_nals(&o.stream);
assert_eq!(nals.len(), 3, "expected SPS, PPS, IDR");
let idr = &nals[2];
let rbsp = ebsp_to_rbsp(&idr[2..]);
let slice_data = &rbsp[2..];
assert_eq!(
slice_data,
&o.slice_data[..],
"recovered slice data mismatch"
);
let mut o2 = encode_core(
&img,
w as u32,
h as u32,
26,
8,
3,
false,
ChromaFormat::Yuv420,
0,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
);
o2.slice_data = slice_data.to_vec();
let (ry, _, _) = decode_slice(&o2);
assert_eq!(ry, o.rec_y);
}
#[test]
fn lfnst_round_trips_and_engages() {
use crate::fmt::BitDepth;
let (w, h) = (128usize, 128usize);
let mut src_y = vec![0i32; w * h];
for j in 0..h {
for i in 0..w {
let (fi, fj) = (i as f32, j as f32);
let a = 46.0 * (0.13 * fi + 0.05 * fj).sin();
let b = 30.0 * (0.05 * fi - 0.16 * fj).sin();
src_y[j * w + i] = (128.0 + a + b).clamp(0.0, 255.0) as i32;
}
}
let hdr = |lfnst: bool| Headers {
width: w as u32,
height: h as u32,
chroma: ChromaFormat::Monochrome,
bit_depth: BitDepth::Eight,
qp: 35,
lossless: false,
aq: false,
mtt: false,
lfnst,
dep_quant: false,
mts: false,
dual_tree: false,
cclm: false,
deblock: false,
};
let off = encode_planes(hdr(false), src_y.clone(), Vec::new(), Vec::new(), 1, true);
let on = encode_planes(hdr(true), src_y.clone(), Vec::new(), Vec::new(), 1, true);
let img = crate::decode_266(&on.stream).expect("LFNST stream must decode");
let enc_planes: Vec<u8> = on.rec_y.iter().map(|&v| v as u8).collect();
assert_eq!(
img.planes, enc_planes,
"LFNST decoder disagrees with encoder"
);
assert_ne!(on.stream, off.stream, "LFNST did not affect the stream");
}
fn recon_mse(rgb: &[u8], w: usize, h: usize, qp: u8) -> f64 {
let o = encode_core(
rgb,
w as u32,
h as u32,
qp,
8,
3,
false,
ChromaFormat::Yuv420,
0,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
);
let (recon, cw) = (o.rec_y, o.cw);
let mut sse = 0i64;
for y in 0..h {
for x in 0..w {
let o = (y * w + x) * 3;
let (r, g, b) = (rgb[o] as i32, rgb[o + 1] as i32, rgb[o + 2] as i32);
let yv = rgb_to_y_q13(r, g, b);
let d = (yv - recon[y * cw + x]) as i64;
sse += d * d;
}
}
sse as f64 / (w * h) as f64
}
#[test]
fn mtt_stream_round_trips_through_decoder() {
use crate::fmt::BitDepth;
let build_plane = |n: usize, salt: u64, kind: u8| -> Vec<i32> {
let mut st = 0x9E3779B97F4A7C15u64 ^ salt;
let mut out = vec![0i32; n];
for (i, v) in out.iter_mut().enumerate() {
st ^= st << 13;
st ^= st >> 7;
st ^= st << 17;
let noise = (st >> 40) as i32 & 15;
*v = match kind {
0 => ((i % 251) as i32 + noise).clamp(0, 255),
_ => (96 + (i % 64) as i32 / 2 + noise / 4).clamp(0, 255),
};
}
out
};
for chroma in [
ChromaFormat::Yuv420,
ChromaFormat::Yuv444,
ChromaFormat::Yuv422,
ChromaFormat::Monochrome,
] {
let (sw, sh) = (chroma.sub_w(), chroma.sub_h());
let has_chroma = !chroma.is_monochrome();
for &(w, h) in &[(128usize, 128usize), (192, 128), (200, 136)] {
let (cwc, chc) = if has_chroma { (w / sw, h / sh) } else { (0, 0) };
let src_y = build_plane(w * h, 1, 0);
let src_cb = if has_chroma {
build_plane(cwc * chc, 2, 1)
} else {
Vec::new()
};
let src_cr = if has_chroma {
build_plane(cwc * chc, 3, 1)
} else {
Vec::new()
};
let qp = 30u8;
let headers = Headers {
width: w as u32,
height: h as u32,
chroma,
bit_depth: BitDepth::Eight,
qp,
lossless: false,
aq: false,
mtt: true,
lfnst: false,
dep_quant: false,
mts: false,
dual_tree: false,
cclm: false,
deblock: false,
};
let o = encode_planes(
headers,
src_y.clone(),
src_cb.clone(),
src_cr.clone(),
1,
true,
);
assert!(
o.leaf_count > (w / 32) * (h / 32),
"too few MTT leaves {chroma:?} {w}x{h}"
);
let img = crate::decode_266(&o.stream).expect("MTT stream must decode");
let mut enc_planes: Vec<u8> = o.rec_y.iter().map(|&v| v as u8).collect();
if has_chroma {
enc_planes.extend(o.rec_cb.iter().map(|&v| v as u8));
enc_planes.extend(o.rec_cr.iter().map(|&v| v as u8));
}
assert_eq!(
img.planes, enc_planes,
"MTT decoder disagrees {chroma:?} {w}x{h}"
);
let tag = format!("{:?}_{w}x{h}", chroma);
let _ = std::fs::write(format!("/tmp/mtt_{tag}.266"), &o.stream);
let _ = std::fs::write(format!("/tmp/mtt_{tag}.enc.yuv"), &enc_planes);
}
}
}
#[test]
fn mtt_vs_qt_rate_distortion() {
let (w, h) = (256usize, 256usize);
let mut src_y = vec![0i32; w * h];
for y in 0..h {
for x in 0..w {
let v = if y < h / 2 && x < w / 2 {
120
} else if x >= w / 2 && y < h / 2 {
if (x / 5) % 2 == 0 { 60 } else { 200 }
} else if (y / 5) % 2 == 0 {
60
} else {
200
};
src_y[y * w + x] = v;
}
}
let (cwc, chc) = (w / 2, h / 2);
let src_cb = vec![128i32; cwc * chc];
let src_cr = vec![128i32; cwc * chc];
let psnr = |rec: &[i32]| -> f64 {
let sse: i64 = rec
.iter()
.zip(&src_y)
.map(|(a, b)| {
let d = (a - b) as i64;
d * d
})
.sum();
if sse == 0 {
return 99.0;
}
10.0 * (255.0f64 * 255.0 / (sse as f64 / (w * h) as f64)).log10()
};
let run = |aq: bool, mtt: bool| {
let headers = Headers {
width: w as u32,
height: h as u32,
chroma: ChromaFormat::Yuv420,
bit_depth: crate::fmt::BitDepth::Eight,
qp: 28,
lossless: false,
aq,
mtt,
lfnst: false,
dep_quant: false,
mts: false,
dual_tree: false,
cclm: false,
deblock: false,
};
encode_planes(
headers,
src_y.clone(),
src_cb.clone(),
src_cr.clone(),
1,
true,
)
};
for (name, aq, mtt) in [
("QT", false, false),
("AQ", true, false),
("MTT", false, true),
("AQ+MTT", true, true),
] {
let o = run(aq, mtt);
crate::decode_266(&o.stream).expect("must decode");
eprintln!(
"{name:8}: {:5} B {:.2} dB",
o.slice_data.len(),
psnr(&o.rec_y)
);
}
let qt = run(false, false);
let mt = run(false, true);
assert!(
mt.slice_data.len() < qt.slice_data.len() * 2,
"MTT bloated vs QT"
);
}
fn gradient_rgb(w: usize, h: usize) -> Vec<u8> {
let mut v = vec![0u8; w * h * 3];
for y in 0..h {
for x in 0..w {
let o = (y * w + x) * 3;
v[o] = ((x * 5 + y * 7) & 0xff) as u8;
v[o + 1] = ((x * 3) & 0xff) as u8;
v[o + 2] = ((y * 4) & 0xff) as u8;
}
}
v
}
#[test]
fn rdoq_toggle_changes_levels_and_stays_decodable() {
let (w, h) = (64usize, 64usize);
let mut rgb = vec![0u8; w * h * 3];
for y in 0..h {
for x in 0..w {
let o = (y * w + x) * 3;
let v = ((x * 37 + y * 53) ^ (x.wrapping_mul(y) >> 1)) as u8;
rgb[o] = v;
rgb[o + 1] = v.wrapping_add(40);
rgb[o + 2] = v.wrapping_mul(3);
}
}
let enc = |rdoq: bool, q: u8, lossless: bool| {
let cfg = crate::EncodeConfig::new()
.with_quality(q)
.with_rdoq(rdoq)
.with_lossless(lossless)
.with_aq(false)
.with_mtt(false)
.with_dual_tree(false);
crate::encode_rgb(&rgb, w as u32, h as u32, &cfg).unwrap()
};
let off = enc(false, 60, false);
let on = enc(true, 60, false);
assert!(crate::decode(&off).is_ok());
assert!(crate::decode(&on).is_ok());
let mut differed = false;
for q in [40u8, 55, 75] {
let o = enc(false, q, false);
let n = enc(true, q, false);
assert!(crate::decode(&n).is_ok());
assert!(
n.len() <= o.len() + 8,
"RDOQ grew the stream by >8 bytes at q{q}: {} > {}",
n.len(),
o.len()
);
differed |= o != n;
}
assert!(
differed,
"RDOQ altered no quality point on high-frequency content"
);
assert_eq!(enc(false, 60, true), enc(true, 60, true));
}
#[test]
fn luma_reconstruction_faithful_at_high_quality() {
let (w, h) = (48usize, 32usize);
let mse = recon_mse(&gradient_rgb(w, h), w, h, 8);
assert!(mse < 8.0, "high-quality luma MSE too large: {mse}");
}
#[test]
fn quality_degrades_with_qp() {
let (w, h) = (48usize, 32usize);
let img = gradient_rgb(w, h);
assert!(recon_mse(&img, w, h, 42) >= recon_mse(&img, w, h, 8));
}
#[test]
fn flat_image_reconstructs_exactly() {
let (w, h) = (40usize, 40usize);
let mut rgb = vec![0u8; w * h * 3];
for p in rgb.chunks_mut(3) {
p[0] = 90;
p[1] = 130;
p[2] = 60;
}
assert_eq!(recon_mse(&rgb, w, h, 32), 0.0);
}
#[test]
fn produces_420_annexb_stream() {
let (w, h) = (40usize, 24usize);
let bytes = encode_still(
&gradient_rgb(w, h),
w as u32,
h as u32,
30,
8,
3,
false,
ChromaFormat::Yuv420,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
);
assert_eq!(&bytes[..4], &[0, 0, 0, 1]);
let starts = bytes.windows(3).filter(|wnd| wnd == &[0, 0, 1]).count();
assert!(starts >= 3, "expected >=3 NAL units, found {starts}");
}
#[test]
fn high_detail_splits_more_than_flat() {
let (w, h) = (64usize, 64usize);
let mut flat = vec![0u8; w * h * 3];
for p in flat.chunks_mut(3) {
p[0] = 100;
p[1] = 100;
p[2] = 100;
}
let flat_leaves = encode_core(
&flat,
w as u32,
h as u32,
20,
8,
3,
false,
ChromaFormat::Yuv420,
0,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
)
.leaf_count;
let mut noise = vec![0u8; w * h * 3];
for (i, p) in noise.chunks_mut(3).enumerate() {
let v = (((i * 97 + (i / w) * 53) % 256) as u8) ^ ((i & 1) as u8 * 200);
p[0] = v;
p[1] = v.wrapping_mul(3);
p[2] = !v;
}
let noise_leaves = encode_core(
&noise,
w as u32,
h as u32,
20,
8,
3,
false,
ChromaFormat::Yuv420,
0,
1,
true,
false,
false,
false,
false,
false,
false,
false,
false,
)
.leaf_count;
assert!(
noise_leaves > flat_leaves,
"detail should split more: flat={flat_leaves} noise={noise_leaves}"
);
assert_eq!(flat_leaves, 1, "flat image should not split");
}
}