#[allow(unused_imports)]
mod aq;
pub(crate) mod av2_itx;
mod avif;
mod ccso;
mod cdf_para;
mod cdf_state;
mod cdfs_qctx;
pub(crate) mod cdfs_uv_qcx;
mod cdfx_4tx;
#[allow(dead_code)]
mod cfl;
mod chroma422;
mod coder;
mod csc;
#[allow(dead_code)]
mod directional;
mod encode400;
mod encode420;
mod encode422;
mod encode444;
mod entropy;
mod fdct;
mod headers;
mod helpers;
mod intrapred;
pub mod itx422;
mod layout;
mod leaf;
mod lossless;
mod mhccp;
mod partition;
mod proj;
mod quant;
pub mod simple;
pub(crate) mod tables;
mod tables_tx32;
mod wht;
use crate::av2::avif::{Av2Color, Av2Format};
use crate::av2::cdfs_qctx::{
CHROMA_EOB_HI_BIT_QC, CHROMA_SKIP_TX32_QC, CHROMA_SKIP_TX64_QC, SKIP_TX8_QC, SKIP_TX16_QC,
};
use crate::av2::cdfx_4tx::{TXB_SKIP_TX4_Q0, V_TXB_SKIP_TX4_Q0};
use crate::av2::chroma422::{
ChromaNeighbors, ChromaPlanes, ChromaTxSpec, code_422_chroma_tu, recon_422_chroma,
};
use crate::av2::coder::{
Coeff, EobCdf, encode_chroma_block, encode_chroma_block_ex, encode_chroma_block_rect,
encode_chroma_block_rect_w, encode_chroma_tu4, encode_lossless_luma_sb,
encode_luma_block_horz4, encode_luma_block_split, encode_luma_block_split_dir,
encode_luma_block_vert4, encode_luma_leaf_8x8, encode_luma_leaf_8x32,
encode_luma_leaf_16x16_full, encode_luma_leaf_16x32, encode_luma_leaf_16x64,
encode_luma_leaf_32x8, encode_luma_leaf_32x16, encode_luma_leaf_32x32, encode_luma_leaf_32x64,
encode_luma_leaf_64x16, encode_luma_leaf_64x32,
};
use crate::av2::csc::{
CB_B, CB_G, CB_R, CR_B, CR_G, CR_R, HALF, Q, Y_B, Y_G, Y_R, get_q_ctx, validate_dims,
};
use crate::av2::encode444::{assemble_multitile, extract_subplane, tile_grid_for, tile_specs};
use crate::av2::entropy::RangeEncoder;
use crate::av2::headers::{Config, frame_header, obu, sequence_header};
use crate::av2::helpers::{
dc_pred, dc_pred_rect, dc_pred_rect_subsampled, get_residual, get_residual_rect,
levels_to_coeffs, lossless_sb_tus, pad_plane, put_block, put_block_rect, sb_align,
sb_tu_contexts, sb_tu_contexts_64x32, sb_tu_contexts_pos, sb_tu_contexts_rect,
sb_tu4_chroma_skip, sb_tu4_contexts,
};
use crate::av2::itx422::reconstruct_luma;
use crate::av2::layout::Layout;
use crate::av2::leaf::{
encode_luma_leaf_s32x32, encode_luma_leaf_v32x64, encode_luma_leaf32, encode_luma_sb,
};
use crate::av2::proj::Basis;
use crate::av2::tables::{SCAN8X8, SCAN8X32, SCAN16, SCAN16X32, SCAN32X8, SCAN32X16};
use crate::err::EncodeError;
use crate::metadata::{ContentLightLevel, Orientation};
use crate::{ChromaFormat, Cicp, Pixel, PlanarImage, Speed};
pub struct Av2Frame {
data: Vec<u8>,
width: usize,
height: usize,
coded_width: usize,
coded_height: usize,
bit_depth: u8,
color: Cicp,
chroma_format: ChromaFormat,
}
impl Av2Frame {
pub fn view(&self) -> &[u8] {
self.data.as_slice()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum TxPart {
#[default]
ThreeWay,
Rd2,
Split,
Vert4,
Horz4,
}
#[derive(Clone, Copy, Debug)]
pub struct Tuning {
pub tile_cols: usize,
pub tile_rows: usize,
pub txpart: TxPart,
pub rdoq_lambda: f64,
pub chroma_rdoq_lambda: f64,
pub part_lambda_c: f64,
pub deblock: bool,
pub chroma_deblock: bool,
pub ccso: bool,
pub ccso_rd_scale: f64,
pub db_delta_y: i32,
pub db_delta_uv: i32,
pub cdef: bool,
pub cfl: bool,
pub mhccp: bool,
pub aq: bool,
pub vb_octile: u8,
pub vb_strength: f32,
pub vb_boost_only: bool,
pub chroma_mode_search: bool,
pub chroma_split: bool,
pub updating_cdf: bool,
}
impl Default for Tuning {
fn default() -> Self {
Tuning {
tile_cols: 1,
tile_rows: 1,
txpart: TxPart::ThreeWay,
rdoq_lambda: proj::DEFAULT_RDOQ_LAMBDA,
chroma_rdoq_lambda: proj::DEFAULT_RDOQ_LAMBDA,
part_lambda_c: 0.0001,
deblock: true,
chroma_deblock: true,
ccso: false,
ccso_rd_scale: 1.0,
db_delta_y: i32::MIN,
db_delta_uv: 0,
cdef: false,
cfl: true,
mhccp: true,
aq: true,
vb_octile: 6,
vb_strength: 0.6,
vb_boost_only: true,
chroma_mode_search: true,
chroma_split: true,
updating_cdf: true,
}
}
}
pub struct Av2Encoder {
bases: proj::Bases,
base_q_idx: u8,
bit_depth: u8,
tune: Tuning,
speed: Speed,
threads: usize,
}
fn lossy_native_mi(width: usize, height: usize) -> Option<(i64, i64)> {
let mc = (((width + 7) & !7) / 4) as i64;
let mr = (((height + 7) & !7) / 4) as i64;
let ok = |m: i64| m % 16 == 0 || m % 16 >= 6 || m % 16 == 4 || m % 16 == 2;
if !(ok(mc) && ok(mr)) {
return None;
}
Some((mc, mr))
}
fn lossy_needs_partition(width: usize, height: usize) -> bool {
let mc = (((width + 7) & !7) / 4) as i64;
let mr = (((height + 7) & !7) / 4) as i64;
let part = |m: i64| m % 16 == 6 || m % 16 == 8 || m % 16 == 4 || m % 16 == 2;
part(mc) || part(mr)
}
fn native_420_mi(width: usize, height: usize) -> Option<(i64, i64)> {
let mc = (((width + 7) & !7) / 4) as i64;
let mr = (((height + 7) & !7) / 4) as i64;
let ok = |m: i64| m % 16 == 0 || m % 16 >= 6 || m % 16 == 4 || m % 16 == 2;
if !(ok(mc) && ok(mr)) {
return None;
}
Some((mc, mr))
}
fn native_422_mi(width: usize, height: usize) -> Option<(i64, i64)> {
lossy_native_mi(width, height)
}
#[derive(Clone, Copy)]
struct QuantCtx {
qc: usize,
neutral: f32,
qstep: i32,
rdoq_lambda: f64,
}
#[derive(Clone, Copy)]
struct PartitionPass {
luma_stride: usize,
chroma_stride: usize,
width: usize,
height: usize,
sb_rows: usize,
sb_cols: usize,
tmc: i64,
tmr: i64,
quant: QuantCtx,
}
struct LumaPlanes<'a> {
rec: &'a mut [f32],
src: &'a [f32],
}
struct ChromaPlaneRefs<'a> {
rec_u: &'a mut [f32],
rec_v: &'a mut [f32],
src_u: &'a [f32],
src_v: &'a [f32],
}
struct PartitionNeighbors<'a> {
above: &'a mut [u8],
left: &'a mut [u8],
above_pctx: &'a mut [u8],
left_pctx: &'a mut [u8],
}
struct ChromaNeighborBufs<'a> {
u_above: &'a mut [i32],
v_above: &'a mut [i32],
u_left: &'a mut [i32],
v_left: &'a mut [i32],
}
impl Av2Encoder {
pub fn new(base_q_idx: u8) -> Self {
Self::with_bit_depth(base_q_idx, 8)
}
pub fn with_bit_depth(base_q_idx: u8, bit_depth: u8) -> Self {
assert!(
matches!(bit_depth, 8 | 10 | 12),
"bit_depth must be 8, 10 or 12, got {bit_depth}"
);
let mut bases = proj::default_bases().rescaled_to_q(base_q_idx as u32);
bases.set_bit_depth(bit_depth);
Av2Encoder {
bases,
base_q_idx,
bit_depth,
tune: Tuning::default(),
speed: crate::Speed::Slow,
threads: 1,
}
}
pub fn with_threads(mut self, threads: usize) -> Self {
self.threads = threads;
self
}
pub fn with_tuning(mut self, tune: Tuning) -> Self {
self.tune = tune;
self
}
pub fn with_tiles(mut self, cols: usize, rows: usize) -> Self {
self.tune.tile_cols = cols.max(1);
self.tune.tile_rows = rows.max(1);
self
}
pub fn with_txpart(mut self, txpart: TxPart) -> Self {
self.tune.txpart = txpart;
self
}
pub fn with_rdoq_lambda(mut self, lambda: f64) -> Self {
self.tune.rdoq_lambda = lambda;
self
}
pub fn with_chroma_rdoq_lambda(mut self, lambda: f64) -> Self {
self.tune.chroma_rdoq_lambda = lambda.max(0.0);
self
}
pub fn with_speed(mut self, speed: Speed) -> Self {
self.speed = speed;
self
}
pub fn with_deblock(mut self, on: bool) -> Self {
self.tune.deblock = on;
self
}
pub fn with_cdef(mut self, on: bool) -> Self {
self.tune.cdef = on;
self
}
pub fn with_cfl(mut self, on: bool) -> Self {
self.tune.cfl = on;
self
}
pub fn with_aq(mut self, on: bool) -> Self {
self.tune.aq = on;
self
}
pub fn with_variance_boost(mut self, octile: u8, strength: f32, boost_only: bool) -> Self {
self.tune.vb_octile = octile.clamp(1, 8);
self.tune.vb_strength = strength.max(0.0);
self.tune.vb_boost_only = boost_only;
self
}
pub fn with_chroma_mode_search(mut self, on: bool) -> Self {
self.tune.chroma_mode_search = on;
self
}
pub fn with_ccso(mut self, on: bool) -> Self {
self.tune.ccso = on;
self
}
pub fn with_mhccp(mut self, on: bool) -> Self {
self.tune.mhccp = on;
self
}
pub fn with_updating_cdf(mut self, on: bool) -> Self {
self.tune.updating_cdf = on;
self
}
pub fn tuning(&self) -> Tuning {
self.tune
}
pub fn base_q_idx(&self) -> u8 {
self.base_q_idx
}
fn config(&self, layout: Layout) -> Config {
let adaptive_dy = if self.base_q_idx >= 48 { 1 } else { 0 };
let eff_dy = if self.tune.db_delta_y == i32::MIN {
adaptive_dy
} else {
self.tune.db_delta_y
};
Config {
layout,
base_q: self.base_q_idx as u32,
deblock: self.tune.deblock,
db_apply: (
self.tune.deblock,
self.tune.deblock,
self.tune.deblock && self.tune.chroma_deblock,
self.tune.deblock && self.tune.chroma_deblock,
),
db_delta: (eff_dy, eff_dy, self.tune.db_delta_uv, self.tune.db_delta_uv),
tx_switchable: true,
guided_deblock: None,
cdef: if self.tune.cdef {
let pri = ((self.base_q_idx as i32 - 120) / 8).clamp(0, 11) as u8;
(pri > 0).then_some((pri * 4, pri * 4, 3))
} else {
None
},
ccso: None,
bit_depth: self.bit_depth,
lossless: self.base_q_idx == 0,
cfl: self.tune.cfl && self.base_q_idx != 0,
mhccp: self.tune.mhccp && self.base_q_idx != 0 && layout.has_chroma(),
aq: self.tune.aq && self.base_q_idx != 0,
aq_res_log2: 2,
updating_cdf: self.tune.updating_cdf && self.base_q_idx != 0,
}
}
fn dc_neutral(&self) -> f32 {
(1u32 << (self.bit_depth - 1)) as f32
}
fn resolve_threads(threads: usize) -> usize {
if threads == 0 {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
} else {
threads
}
}
#[allow(clippy::too_many_arguments)]
fn finish(
&self,
enc: RangeEncoder,
config: &Config,
pw: usize,
ph: usize,
width: usize,
height: usize,
color: &Cicp,
) -> Av2Frame {
let ccso_u_result = enc.ccso_u_result.clone();
let ccso_v_result = enc.ccso_v_result.clone();
let tile = enc.finish();
let mi_cols = ((width + 7) & !7) / 4;
let mi_rows = ((height + 7) & !7) / 4;
const MIB: usize = 16; let aligned = mi_cols.is_multiple_of(MIB) && mi_rows.is_multiple_of(MIB);
let lossy_native = !config.lossless
&& ((config.layout == Layout::I444 && lossy_native_mi(width, height).is_some())
|| (config.layout == Layout::I422 && native_422_mi(width, height).is_some())
|| (config.layout == Layout::I420 && native_420_mi(width, height).is_some())
|| (config.layout == Layout::Monochrome
&& lossy_native_mi(width, height).is_some()));
let exact = config.lossless || aligned || lossy_native;
let (sw, sh) = if exact { (width, height) } else { (pw, ph) };
let mut config = (*config).clone();
let to_plane = |r: &ccso::PlaneResult| -> headers::CcsoPlane {
use crate::av2::ccso::PlaneResult::*;
match r {
Edge {
scale_idx,
quant_idx,
ext_filter_support,
edge_clf,
max_band_log2,
offsets,
} => crate::av2::headers::CcsoPlane {
bo_only: false,
scale_idx: *scale_idx,
quant_idx: *quant_idx,
ext_filter_support: *ext_filter_support,
edge_clf: *edge_clf,
max_band_log2: *max_band_log2,
offsets: offsets.clone(),
},
}
};
if ccso_u_result.is_some() || ccso_v_result.is_some() {
config.ccso = Some(headers::CcsoConfig {
enable: [false, ccso_u_result.is_some(), ccso_v_result.is_some()],
planes: [
None,
ccso_u_result.as_ref().map(&to_plane),
ccso_v_result.as_ref().map(&to_plane),
],
});
}
let config = &config;
let mut frame = frame_header(config, sw as u32, sh as u32, (0, 0, 1));
frame.extend(&tile);
let mut data = vec![];
data.extend(obu(2, &[]));
data.extend(obu(1, &sequence_header(config, sw as u32, sh as u32)));
data.extend(obu(4, &frame));
Av2Frame {
data,
width,
height,
coded_width: sw,
coded_height: sh,
bit_depth: self.bit_depth,
color: *color,
chroma_format: match config.layout {
Layout::Monochrome => ChromaFormat::Monochrome,
Layout::I420 => ChromaFormat::Yuv420,
Layout::I422 => ChromaFormat::Yuv422,
Layout::I444 => ChromaFormat::Yuv444,
},
}
}
pub fn wrap_avif(
frame: &Av2Frame,
icc_profile: Option<&[u8]>,
exif: Option<&[u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
) -> Result<Vec<u8>, EncodeError> {
let format = Av2Format {
bit_depth: frame.bit_depth,
monochrome: frame.chroma_format == ChromaFormat::Monochrome,
chroma_sub_x: frame.chroma_format == ChromaFormat::Yuv422
|| frame.chroma_format == ChromaFormat::Yuv420,
chroma_sub_y: frame.chroma_format == ChromaFormat::Yuv420,
};
let color = match icc_profile {
Some(icc) => Av2Color::Both {
cicp: frame.color,
icc: icc.to_vec(),
},
None => Av2Color::Cicp(frame.color),
};
Ok(avif::to_avif_color(
frame,
&format,
&color,
exif,
orientation,
clli,
))
}
pub fn wrap_avif_alpha(
frame: &Av2Frame,
alpha: &Av2Frame,
icc_profile: Option<&[u8]>,
exif: Option<&[u8]>,
orientation: Orientation,
clli: Option<ContentLightLevel>,
) -> Result<Vec<u8>, EncodeError> {
let format = Av2Format {
bit_depth: frame.bit_depth,
monochrome: frame.chroma_format == ChromaFormat::Monochrome,
chroma_sub_x: frame.chroma_format == ChromaFormat::Yuv422
|| frame.chroma_format == ChromaFormat::Yuv420,
chroma_sub_y: frame.chroma_format == ChromaFormat::Yuv420,
};
let color = match icc_profile {
Some(icc) => Av2Color::Both {
cicp: frame.color,
icc: icc.to_vec(),
},
None => Av2Color::Cicp(frame.color),
};
Ok(avif::to_avif_color_alpha(
frame,
alpha,
&format,
&color,
exif,
orientation,
clli,
))
}
}
pub fn av2_map_quality(quality: u8) -> u8 {
debug_assert!((1..=100).contains(&quality));
((100 - quality as u32) * 254 / 99).clamp(1, 254) as u8
}