#![allow(dead_code)]
#![allow(clippy::needless_range_loop)]
pub mod classifier;
pub mod histogram;
pub mod iterator;
pub mod prediction;
pub mod segment;
use alloc::vec::Vec;
pub use classifier::{
ClassifierDiag, ImageContentType, classify_image_type, classify_image_type_diag,
content_type_to_tuning,
};
#[cfg(feature = "analyzer")]
pub use classifier::{
ZenanalyzeDiag, classify_image_type_rgb8, classify_image_type_rgb8_diag,
decide_bucket_from_diag, decide_bucket_stable, rgba8_to_rgb8,
};
pub use histogram::{collect_histogram_bps, forward_dct_4x4};
pub use iterator::AnalysisIterator;
pub use segment::{assign_segments_kmeans, compute_segment_quant, smooth_segment_map};
pub const MAX_ALPHA: u8 = 255;
const ALPHA_SCALE: u32 = 2 * MAX_ALPHA as u32;
const MAX_COEFF_THRESH: usize = 31;
pub const NUM_SEGMENTS: usize = 4;
const MAX_INTRA16_MODE: usize = 2;
const MAX_UV_MODE: usize = 2;
const BPS: usize = 32;
const Y_OFF_ENC: usize = 0;
const U_OFF_ENC: usize = 16;
const V_OFF_ENC: usize = 24;
#[allow(clippy::erasing_op, clippy::identity_op)]
const VP8_DSP_SCAN: [usize; 24] = [
0 + 0 * BPS,
4 + 0 * BPS,
8 + 0 * BPS,
12 + 0 * BPS,
0 + 4 * BPS,
4 + 4 * BPS,
8 + 4 * BPS,
12 + 4 * BPS,
0 + 8 * BPS,
4 + 8 * BPS,
8 + 8 * BPS,
12 + 8 * BPS,
0 + 12 * BPS,
4 + 12 * BPS,
8 + 12 * BPS,
12 + 12 * BPS,
0 + 0 * BPS,
4 + 0 * BPS,
0 + 4 * BPS,
4 + 4 * BPS,
8 + 0 * BPS,
12 + 0 * BPS,
8 + 4 * BPS,
12 + 4 * BPS,
];
const I16DC16: usize = 0;
const I16TM16: usize = I16DC16 + 16;
#[allow(clippy::identity_op)] const I16VE16: usize = 1 * 16 * BPS;
const I16HE16: usize = I16VE16 + 16;
const VP8_I16_MODE_OFFSETS: [usize; 4] = [I16DC16, I16TM16, I16VE16, I16HE16];
const C8DC8: usize = 2 * 16 * BPS;
const C8TM8: usize = C8DC8 + 16;
const C8VE8: usize = 2 * 16 * BPS + 8 * BPS;
const C8HE8: usize = C8VE8 + 16;
const VP8_UV_MODE_OFFSETS: [usize; 4] = [C8DC8, C8TM8, C8VE8, C8HE8];
const PRED_SIZE_ENC: usize = 32 * BPS + 16 * BPS + 8 * BPS;
const YUV_SIZE_ENC: usize = BPS * 16;
#[derive(Default, Clone, Copy)]
pub struct DctHistogram {
pub max_value: u32,
pub last_non_zero: usize,
}
impl DctHistogram {
pub fn new() -> Self {
Self {
max_value: 0,
last_non_zero: 1,
}
}
pub fn from_distribution(distribution: &[u32; MAX_COEFF_THRESH + 1]) -> Self {
let mut max_value: u32 = 0;
let mut last_non_zero: usize = 1;
for (k, &count) in distribution.iter().enumerate() {
if count > 0 {
if count > max_value {
max_value = count;
}
last_non_zero = k;
}
}
Self {
max_value,
last_non_zero,
}
}
pub fn get_alpha(&self) -> i32 {
if self.max_value <= 1 {
return 0;
}
let value = ALPHA_SCALE * self.last_non_zero as u32 / self.max_value;
value as i32
}
}
#[inline]
fn final_alpha_value(alpha: i32) -> u8 {
let alpha = MAX_ALPHA as i32 - alpha;
alpha.clamp(0, MAX_ALPHA as i32) as u8
}
pub fn analyze_macroblock(
it: &mut AnalysisIterator,
method: u8,
sns_strength: u8,
cost_model: crate::encoder::api::CostModel,
) -> (u8, i32) {
let (best_alpha, _best_mode) = it.analyze_best_intra16_mode();
let (best_uv_alpha, _uv_mode) = it.analyze_best_uv_mode();
let alpha = (3 * best_alpha + best_uv_alpha + 2) >> 2;
let alpha = if method >= 4
&& sns_strength > 0
&& cost_model == crate::encoder::api::CostModel::ZenwebpDefault
{
let masking = crate::encoder::psy::compute_masking_alpha(&it.yuv_in[Y_OFF_ENC..], BPS);
crate::encoder::psy::blend_masking_alpha(alpha, masking, method)
} else {
alpha
};
(final_alpha_value(alpha), best_uv_alpha)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MbModeHint {
I16Dc,
I4AllDc,
}
pub struct AnalysisResult {
pub mb_alphas: Vec<u8>,
pub alpha_histogram: [u32; 256],
pub uv_alpha_avg: i32,
pub mb_mode_hints: Option<Vec<MbModeHint>>,
}
fn fast_mb_analyze(y_in: &[u8], stride: usize, quality: i32) -> MbModeHint {
let q = quality.clamp(0, 100);
let k_threshold: u64 = 8 + (17 - 8) * q as u64 / 100;
let mut dc = [0u32; 16];
for row_blk in 0..4 {
for col_blk in 0..4 {
let mut sum: u32 = 0;
for r in 0..4 {
let row_base = (row_blk * 4 + r) * stride + col_blk * 4;
for c in 0..4 {
sum += y_in[row_base + c] as u32;
}
}
dc[row_blk * 4 + col_blk] = sum;
}
}
let (mut m, mut m2): (u64, u64) = (0, 0);
for &d in &dc {
m += d as u64;
m2 += (d as u64) * (d as u64);
}
if k_threshold.saturating_mul(m2) < m.saturating_mul(m) {
MbModeHint::I16Dc
} else {
MbModeHint::I4AllDc
}
}
#[allow(clippy::too_many_arguments)]
pub fn analyze_image(
y_src: &[u8],
u_src: &[u8],
v_src: &[u8],
width: usize,
height: usize,
y_stride: usize,
uv_stride: usize,
method: u8,
sns_strength: u8,
cost_model: crate::encoder::api::CostModel,
quality: i32,
) -> AnalysisResult {
analyze_image_with_hint_gate(
y_src,
u_src,
v_src,
width,
height,
y_stride,
uv_stride,
method,
sns_strength,
cost_model,
quality,
method <= 1,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn analyze_image_with_hint_gate(
y_src: &[u8],
u_src: &[u8],
v_src: &[u8],
width: usize,
height: usize,
y_stride: usize,
uv_stride: usize,
method: u8,
sns_strength: u8,
cost_model: crate::encoder::api::CostModel,
quality: i32,
collect_mode_hints: bool,
) -> AnalysisResult {
let mut it = AnalysisIterator::new(width, height);
it.reset();
let total_mbs = it.mb_w * it.mb_h;
let mut mb_alphas = Vec::with_capacity(total_mbs);
let mut alpha_histogram = [0u32; 256];
let mut uv_alpha_sum: i64 = 0;
let mut mb_mode_hints: Vec<MbModeHint> = if collect_mode_hints {
Vec::with_capacity(total_mbs)
} else {
Vec::new()
};
loop {
it.import(y_src, u_src, v_src, y_stride, uv_stride);
if collect_mode_hints {
let hint = fast_mb_analyze(&it.yuv_in[Y_OFF_ENC..], BPS, quality);
mb_mode_hints.push(hint);
}
let (alpha, uv_alpha) = analyze_macroblock(&mut it, method, sns_strength, cost_model);
mb_alphas.push(alpha);
alpha_histogram[alpha as usize] += 1;
uv_alpha_sum += uv_alpha as i64;
if !it.next() {
break;
}
}
let uv_alpha_avg = if total_mbs > 0 {
(uv_alpha_sum / total_mbs as i64) as i32
} else {
64 };
AnalysisResult {
mb_alphas,
alpha_histogram,
uv_alpha_avg,
mb_mode_hints: if collect_mode_hints {
Some(mb_mode_hints)
} else {
None
},
}
}