pub const DEFAULT_QP: u8 = 10;
pub const MIN_QP: u8 = 1;
pub const MAX_QP: u8 = 31;
pub const QUANT_STEP: &[i32] = &[
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, ];
pub const ZIGZAG_8X8: &[(usize, usize)] = &[
(0, 0),
(0, 1),
(1, 0),
(2, 0),
(1, 1),
(0, 2),
(0, 3),
(1, 2),
(2, 1),
(3, 0),
(4, 0),
(3, 1),
(2, 2),
(1, 3),
(0, 4),
(0, 5),
(1, 4),
(2, 3),
(3, 2),
(4, 1),
(5, 0),
(6, 0),
(5, 1),
(4, 2),
(3, 3),
(2, 4),
(1, 5),
(0, 6),
(0, 7),
(1, 6),
(2, 5),
(3, 4),
(4, 3),
(5, 2),
(6, 1),
(7, 0),
(7, 1),
(6, 2),
(5, 3),
(4, 4),
(3, 5),
(2, 6),
(1, 7),
(2, 7),
(3, 6),
(4, 5),
(5, 4),
(6, 3),
(7, 2),
(7, 3),
(6, 4),
(5, 5),
(4, 6),
(3, 7),
(4, 7),
(5, 6),
(6, 5),
(7, 4),
(7, 5),
(6, 6),
(5, 7),
(6, 7),
(7, 6),
(7, 7),
];
pub const INVERSE_ZIGZAG_8X8: &[(usize, usize)] = &[
(0, 0),
(0, 1),
(1, 0),
(2, 0),
(1, 1),
(0, 2),
(0, 3),
(1, 2),
(2, 1),
(3, 0),
(4, 0),
(3, 1),
(2, 2),
(1, 3),
(0, 4),
(0, 5),
(1, 4),
(2, 3),
(3, 2),
(4, 1),
(5, 0),
(6, 0),
(5, 1),
(4, 2),
(3, 3),
(2, 4),
(1, 5),
(0, 6),
(0, 7),
(1, 6),
(2, 5),
(3, 4),
(4, 3),
(5, 2),
(6, 1),
(7, 0),
(7, 1),
(6, 2),
(5, 3),
(4, 4),
(3, 5),
(2, 6),
(1, 7),
(2, 7),
(3, 6),
(4, 5),
(5, 4),
(6, 3),
(7, 2),
(7, 3),
(6, 4),
(5, 5),
(4, 6),
(3, 7),
(4, 7),
(5, 6),
(6, 5),
(7, 4),
(7, 5),
(6, 6),
(5, 7),
(6, 7),
(7, 6),
(7, 7),
];
#[must_use]
pub fn get_quant_step(qp: u8) -> i32 {
if qp > MAX_QP {
return 0;
}
QUANT_STEP[qp as usize]
}
#[must_use]
pub fn quantize_coeff(coeff: i16, qp: u8, is_dc: bool) -> i16 {
if coeff == 0 {
return 0;
}
let step = get_quant_step(qp);
if step == 0 {
return 0;
}
if is_dc {
let dc_step = 8;
let abs_coeff = coeff.abs() as i32;
let quantized = (abs_coeff + dc_step / 2) / dc_step;
let result = quantized.clamp(0, 255) as i16;
if coeff < 0 {
-result
} else {
result
}
} else {
let abs_coeff = coeff.abs() as i32;
let quantized = abs_coeff / step;
let result = quantized.clamp(0, 127) as i16;
if coeff < 0 {
-result
} else {
result
}
}
}
#[must_use]
pub fn dequantize_coeff(level: i16, qp: u8, is_dc: bool) -> i16 {
if level == 0 {
return 0;
}
let step = get_quant_step(qp);
if step == 0 {
return 0;
}
if is_dc {
let dc_step = 8;
let dequant = level as i32 * dc_step;
dequant.clamp(-2048, 2047) as i16
} else {
let abs_level = level.abs() as i32;
let dequant = (2 * abs_level + 1) * step;
let result = dequant.clamp(0, 2047) as i16;
if level < 0 {
-result
} else {
result
}
}
}
#[must_use]
pub fn quantize_block(block: &[[i16; 8]; 8], qp: u8) -> [[i16; 8]; 8] {
let mut quantized = [[0i16; 8]; 8];
for row in 0..8 {
for col in 0..8 {
let is_dc = row == 0 && col == 0;
quantized[row][col] = quantize_coeff(block[row][col], qp, is_dc);
}
}
quantized
}
#[must_use]
pub fn dequantize_block(block: &[[i16; 8]; 8], qp: u8) -> [[i16; 8]; 8] {
let mut dequantized = [[0i16; 8]; 8];
for row in 0..8 {
for col in 0..8 {
let is_dc = row == 0 && col == 0;
dequantized[row][col] = dequantize_coeff(block[row][col], qp, is_dc);
}
}
dequantized
}
#[must_use]
pub fn block_to_zigzag(block: &[[i16; 8]; 8]) -> [i16; 64] {
let mut zigzag = [0i16; 64];
for (idx, &(row, col)) in ZIGZAG_8X8.iter().enumerate() {
zigzag[idx] = block[row][col];
}
zigzag
}
#[must_use]
pub fn zigzag_to_block(zigzag: &[i16; 64]) -> [[i16; 8]; 8] {
let mut block = [[0i16; 8]; 8];
for (idx, &(row, col)) in ZIGZAG_8X8.iter().enumerate() {
block[row][col] = zigzag[idx];
}
block
}
#[must_use]
pub fn adjust_qp(target_bits: usize, actual_bits: usize, current_qp: u8) -> u8 {
if actual_bits > target_bits {
(current_qp + 1).min(MAX_QP)
} else if actual_bits < target_bits / 2 {
(current_qp.saturating_sub(1)).max(MIN_QP)
} else {
current_qp
}
}
#[must_use]
pub fn adaptive_qp(block: &[[u8; 8]; 8], base_qp: u8) -> u8 {
let mut sum = 0i32;
let mut sum_sq = 0i32;
for row in 0..8 {
for col in 0..8 {
let val = block[row][col] as i32;
sum += val;
sum_sq += val * val;
}
}
let mean = sum / 64;
let variance = (sum_sq / 64) - (mean * mean);
if variance > 1000 {
base_qp.saturating_sub(2).max(MIN_QP)
} else if variance < 100 {
(base_qp + 2).min(MAX_QP)
} else {
base_qp
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
pub fn quantize_with_deadzone(coeff: i16, qp: u8, deadzone_factor: f32) -> i16 {
if coeff == 0 {
return 0;
}
let step = get_quant_step(qp);
if step == 0 {
return 0;
}
let abs_coeff = coeff.abs() as i32;
let deadzone = ((step as f32) * deadzone_factor) as i32;
if abs_coeff < deadzone {
return 0;
}
let quantized = (abs_coeff - deadzone + step / 2) / step;
let result = quantized.clamp(0, 127) as i16;
if coeff < 0 {
-result
} else {
result
}
}
const PERCEPTUAL_QUANT_LUMA: [[f32; 8]; 8] = [
[1.00, 1.00, 1.00, 1.00, 1.10, 1.20, 1.30, 1.40],
[1.00, 1.00, 1.00, 1.10, 1.20, 1.30, 1.40, 1.50],
[1.00, 1.00, 1.10, 1.20, 1.30, 1.40, 1.50, 1.60],
[1.00, 1.10, 1.20, 1.30, 1.40, 1.50, 1.60, 1.70],
[1.10, 1.20, 1.30, 1.40, 1.50, 1.60, 1.70, 1.80],
[1.20, 1.30, 1.40, 1.50, 1.60, 1.70, 1.80, 1.90],
[1.30, 1.40, 1.50, 1.60, 1.70, 1.80, 1.90, 2.00],
[1.40, 1.50, 1.60, 1.70, 1.80, 1.90, 2.00, 2.10],
];
#[must_use]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
pub fn quantize_perceptual_luma(block: &[[i16; 8]; 8], qp: u8) -> [[i16; 8]; 8] {
let mut quantized = [[0i16; 8]; 8];
let base_step = get_quant_step(qp) as f32;
for row in 0..8 {
for col in 0..8 {
let coeff = block[row][col];
if coeff == 0 {
continue;
}
let weighted_step = base_step * PERCEPTUAL_QUANT_LUMA[row][col];
let abs_coeff = coeff.abs() as f32;
let quantized_val = (abs_coeff / weighted_step) as i16;
quantized[row][col] = if coeff < 0 {
-quantized_val
} else {
quantized_val
};
}
}
quantized
}
const PERCEPTUAL_QUANT_CHROMA: [[f32; 8]; 8] = [
[1.00, 1.10, 1.20, 1.30, 1.40, 1.50, 1.60, 1.70],
[1.10, 1.20, 1.30, 1.40, 1.50, 1.60, 1.70, 1.80],
[1.20, 1.30, 1.40, 1.50, 1.60, 1.70, 1.80, 1.90],
[1.30, 1.40, 1.50, 1.60, 1.70, 1.80, 1.90, 2.00],
[1.40, 1.50, 1.60, 1.70, 1.80, 1.90, 2.00, 2.10],
[1.50, 1.60, 1.70, 1.80, 1.90, 2.00, 2.10, 2.20],
[1.60, 1.70, 1.80, 1.90, 2.00, 2.10, 2.20, 2.30],
[1.70, 1.80, 1.90, 2.00, 2.10, 2.20, 2.30, 2.40],
];
#[must_use]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
pub fn quantize_perceptual_chroma(block: &[[i16; 8]; 8], qp: u8) -> [[i16; 8]; 8] {
let mut quantized = [[0i16; 8]; 8];
let base_step = get_quant_step(qp) as f32;
for row in 0..8 {
for col in 0..8 {
let coeff = block[row][col];
if coeff == 0 {
continue;
}
let weighted_step = base_step * PERCEPTUAL_QUANT_CHROMA[row][col];
let abs_coeff = coeff.abs() as f32;
let quantized_val = (abs_coeff / weighted_step) as i16;
quantized[row][col] = if coeff < 0 {
-quantized_val
} else {
quantized_val
};
}
}
quantized
}