use super::super::coder::{CoeffJob, Site, TileCoder};
use super::super::coeff::{CoeffBlock, CoeffCdfs, TxTypeCtx, encode_coeffs};
use super::super::symbol::SymbolEncoder;
use super::super::tile::TileState;
use super::super::transform::dsp::{ForwardBasis, quantize};
use super::super::transform::{TxSize, TxType};
use super::super::transform_type::chroma_tx_type;
use otf_pixels_core::Result;
const PARTITION_NONE: usize = 0;
const PARTITION_SPLIT: usize = 3;
const NO_ANGLE_DELTA: usize = 3;
const INTRA_MODES: usize = 13;
#[derive(Debug, Clone)]
pub(crate) struct SourcePlane {
pub data: Vec<u16>,
pub width: usize,
pub height: usize,
}
impl SourcePlane {
pub(crate) fn padded(
samples: &[u16],
width: usize,
height: usize,
padded_width: usize,
padded_height: usize,
) -> Self {
let mut data = Vec::with_capacity(padded_width * padded_height);
for y in 0..padded_height {
let row = y.min(height.saturating_sub(1)) * width;
for x in 0..padded_width {
data.push(
samples
.get(row + x.min(width.saturating_sub(1)))
.copied()
.unwrap_or(0),
);
}
}
Self {
data,
width: padded_width,
height: padded_height,
}
}
fn at(&self, x: usize, y: usize) -> i32 {
let (x, y) = (x.min(self.width - 1), y.min(self.height - 1));
i32::from(self.data.get(y * self.width + x).copied().unwrap_or(0))
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Tuning {
pub ac_q: i64,
pub dc_bias: f64,
pub ac_bias: f64,
}
pub(crate) struct TileEncoder<'a> {
enc: SymbolEncoder,
source: &'a [SourcePlane],
tuning: Tuning,
y_mode: usize,
uv_mode: usize,
bases: Vec<(TxSize, TxType, ForwardBasis)>,
}
impl<'a> TileEncoder<'a> {
pub(crate) fn new(source: &'a [SourcePlane], tuning: Tuning) -> Self {
Self {
enc: SymbolEncoder::new(false),
source,
tuning,
y_mode: 0,
uv_mode: 0,
bases: Vec::new(),
}
}
pub(crate) fn finish(self) -> Vec<u8> {
self.enc.finish()
}
fn should_split(&self, r: usize, c: usize, bsize4: usize) -> bool {
if bsize4 >= 16 {
return true;
}
if bsize4 <= 2 {
return false;
}
let Some(luma) = self.source.first() else {
return false;
};
let side = bsize4 * 4;
let (x0, y0) = (c * 4, r * 4);
let (mut sum, mut sum_sq) = (0_i64, 0_i64);
for y in y0..y0 + side {
for x in x0..x0 + side {
let v = i64::from(luma.at(x, y));
sum += v;
sum_sq += v * v;
}
}
let n = (side * side) as i64;
let variance = (sum_sq - sum * sum / n) / n;
let step = (self.tuning.ac_q as f64 / 8.0).max(1.0);
variance as f64 > step * step * 0.6 + 4.0
}
fn best_mode(
&self,
state: &TileState,
r: usize,
c: usize,
bw4: usize,
bh4: usize,
planes: &[usize],
) -> usize {
let mut best = (u64::MAX, 0);
for mode in 0..INTRA_MODES {
let mut cost = 0_u64;
for &plane in planes {
let (Some(pred), Some(src)) = (
state.preview_prediction(r, c, bw4, bh4, plane, mode),
self.source.get(plane),
) else {
return 0;
};
let sub = usize::from(
plane > 0
&& self
.source
.first()
.is_some_and(|luma| src.width < luma.width),
);
let (x0, y0) = ((c * 4) >> sub, (r * 4) >> sub);
let w = ((bw4 * 4) >> sub).max(4);
let h = pred.len() / w;
for y in 0..h {
for x in 0..w {
let p = i32::from(pred.get(y * w + x).copied().unwrap_or(0));
cost += u64::from((src.at(x0 + x, y0 + y) - p).unsigned_abs());
}
}
}
if mode != 0 {
cost += cost / 64;
}
if cost < best.0 {
best = (cost, mode);
}
}
best.1
}
fn basis(&mut self, tx_size: TxSize, tx_type: TxType) -> &ForwardBasis {
if let Some(i) = self
.bases
.iter()
.position(|(s, t, _)| *s == tx_size && *t == tx_type)
{
let (_, _, basis) = self.bases.swap_remove(i);
self.bases.push((tx_size, tx_type, basis));
} else {
self.bases
.push((tx_size, tx_type, ForwardBasis::new(tx_size, tx_type)));
}
let (_, _, basis) = self.bases.last_mut().unwrap_or_else(|| unreachable!());
basis
}
}
impl TileCoder for TileEncoder<'_> {
fn symbol(&mut self, cdf: &mut [u16], site: Site) -> Result<usize> {
let value = match site {
Site::Partition { r, c, bsize4 } => {
if self.should_split(r, c, bsize4) {
PARTITION_SPLIT
} else {
PARTITION_NONE
}
}
Site::SplitOr { .. } => 1,
Site::YMode => self.y_mode,
Site::UvMode => self.uv_mode,
Site::AngleDeltaY | Site::AngleDeltaUv => NO_ANGLE_DELTA,
_ => 0,
};
self.enc.write_symbol(cdf, value);
Ok(value)
}
fn literal(&mut self, n: u32, _site: Site) -> Result<u32> {
self.enc.write_literal(n, 0);
Ok(0)
}
fn ns(&mut self, n: u32) -> Result<u32> {
let w = u32::BITS - n.leading_zeros();
self.enc.write_literal(w.saturating_sub(1), 0);
Ok(0)
}
fn plan_block(&mut self, state: &TileState, r: usize, c: usize, bw4: usize, bh4: usize) {
self.y_mode = self.best_mode(state, r, c, bw4, bh4, &[0]);
self.uv_mode = if self.source.len() > 1 {
self.best_mode(state, r, c, bw4, bh4, &[1, 2])
} else {
0
};
}
fn coeffs(
&mut self,
cdfs: &mut CoeffCdfs,
tx_size: TxSize,
tx: TxTypeCtx<'_>,
ptype: usize,
all_zero_ctx: usize,
dc_sign_ctx: usize,
job: &CoeffJob<'_>,
) -> Result<CoeffBlock> {
let tx_type = if ptype == 0 || tx.lossless {
TxType::DctDct
} else {
chroma_tx_type(tx.uv_mode, tx.set)
};
let (w, h) = (tx_size.width(), tx_size.height());
let source = self.source.get(job.plane);
let mut residual = vec![0_i32; w * h];
if let Some(src) = source {
for (k, out) in residual.iter_mut().enumerate() {
let p = i32::from(job.prediction.get(k).copied().unwrap_or(0));
*out = src.at(job.x + k % w, job.y + k / w) - p;
}
}
let tuning = self.tuning;
let coefficients = self.basis(tx_size, tx_type).forward(&residual);
let levels: Vec<i32> = coefficients
.iter()
.enumerate()
.map(|(i, &coefficient)| {
let (q, bias) = if i == 0 {
(job.dc_q, tuning.dc_bias)
} else {
(job.ac_q, tuning.ac_bias)
};
quantize(coefficient, q, tx_size, bias)
})
.collect();
encode_coeffs(
&mut self.enc,
cdfs,
tx_size,
tx,
tx_type,
ptype,
all_zero_ctx,
dc_sign_ctx,
&levels,
)
}
}