use crate::error::{Jpeg2000Error, Result};
#[derive(Debug, Clone)]
pub struct QualityLayer {
pub layer_index: u16,
pub target_rate: Option<f64>,
pub target_psnr: Option<f64>,
}
impl QualityLayer {
pub fn with_rate(layer_index: u16, target_rate: f64) -> Self {
Self {
layer_index,
target_rate: Some(target_rate),
target_psnr: None,
}
}
pub fn with_psnr(layer_index: u16, target_psnr: f64) -> Self {
Self {
layer_index,
target_rate: None,
target_psnr: Some(target_psnr),
}
}
pub fn lossless(layer_index: u16) -> Self {
Self {
layer_index,
target_rate: None,
target_psnr: None,
}
}
pub fn is_lossless(&self) -> bool {
self.target_rate.is_none() && self.target_psnr.is_none()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SlopeEntry {
pub distortion_reduction: f64,
pub byte_cost: u32,
}
impl SlopeEntry {
pub fn slope(&self) -> f64 {
if self.byte_cost == 0 {
f64::INFINITY
} else {
self.distortion_reduction / self.byte_cost as f64
}
}
}
#[derive(Debug, Clone)]
pub struct RateController {
pub width: u32,
pub height: u32,
pub num_components: u16,
layers: Vec<QualityLayer>,
}
impl RateController {
pub fn new(width: u32, height: u32, num_components: u16) -> Self {
Self {
width,
height,
num_components,
layers: Vec::new(),
}
}
pub fn add_layer(&mut self, layer: QualityLayer) -> Result<()> {
if let Some(last) = self.layers.last() {
if layer.layer_index <= last.layer_index {
return Err(Jpeg2000Error::Tier2Error(format!(
"Quality layers must be added in ascending order; got {} after {}",
layer.layer_index, last.layer_index
)));
}
}
self.layers.push(layer);
Ok(())
}
pub fn num_layers(&self) -> usize {
self.layers.len()
}
pub fn get_layer(&self, idx: usize) -> Option<&QualityLayer> {
self.layers.get(idx)
}
pub fn layer_byte_budget(&self, layer_idx: u16) -> Option<u64> {
let layer = self.layers.get(layer_idx as usize)?;
if let Some(bpp) = layer.target_rate {
let total_pixels = self.width as u64 * self.height as u64 * self.num_components as u64;
let bytes = (bpp * total_pixels as f64 / 8.0).ceil() as u64;
Some(bytes)
} else {
None
}
}
pub fn allocate_passes(&self, slopes: &[SlopeEntry]) -> Result<Vec<u16>> {
if self.layers.is_empty() {
return Err(Jpeg2000Error::Tier2Error(
"No quality layers configured".to_string(),
));
}
let num_passes = slopes.len();
let num_layers = self.layers.len();
let mut order: Vec<usize> = (0..num_passes).collect();
order.sort_by(|&a, &b| {
slopes[b]
.slope()
.partial_cmp(&slopes[a].slope())
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut assignments = vec![0u16; num_passes];
let mut cumulative_bytes = 0u64;
let mut current_layer = 0usize;
for &pass_idx in &order {
let cost = slopes[pass_idx].byte_cost as u64;
while current_layer + 1 < num_layers {
if let Some(budget) = self.layer_byte_budget(current_layer as u16) {
if cumulative_bytes + cost > budget {
current_layer += 1;
continue;
}
}
break;
}
assignments[pass_idx] = current_layer as u16;
cumulative_bytes += cost;
}
Ok(assignments)
}
pub fn total_pixels(&self) -> u64 {
self.width as u64 * self.height as u64 * self.num_components as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_layer_with_rate() {
let layer = QualityLayer::with_rate(0, 1.5);
assert_eq!(layer.layer_index, 0);
assert_eq!(layer.target_rate, Some(1.5));
assert!(layer.target_psnr.is_none());
assert!(!layer.is_lossless());
}
#[test]
fn test_quality_layer_with_psnr() {
let layer = QualityLayer::with_psnr(1, 40.0);
assert_eq!(layer.layer_index, 1);
assert_eq!(layer.target_psnr, Some(40.0));
assert!(layer.target_rate.is_none());
assert!(!layer.is_lossless());
}
#[test]
fn test_quality_layer_lossless() {
let layer = QualityLayer::lossless(2);
assert!(layer.is_lossless());
}
#[test]
fn test_slope_entry_slope() {
let entry = SlopeEntry {
distortion_reduction: 100.0,
byte_cost: 25,
};
assert!((entry.slope() - 4.0).abs() < 1e-10);
}
#[test]
fn test_slope_entry_zero_cost() {
let entry = SlopeEntry {
distortion_reduction: 10.0,
byte_cost: 0,
};
assert_eq!(entry.slope(), f64::INFINITY);
}
#[test]
fn test_rate_controller_new() {
let rc = RateController::new(512, 512, 3);
assert_eq!(rc.width, 512);
assert_eq!(rc.height, 512);
assert_eq!(rc.num_components, 3);
assert_eq!(rc.num_layers(), 0);
}
#[test]
fn test_add_layers_in_order() {
let mut rc = RateController::new(256, 256, 1);
assert!(rc.add_layer(QualityLayer::with_rate(0, 0.5)).is_ok());
assert!(rc.add_layer(QualityLayer::with_rate(1, 1.0)).is_ok());
assert_eq!(rc.num_layers(), 2);
}
#[test]
fn test_add_layers_out_of_order_fails() {
let mut rc = RateController::new(256, 256, 1);
rc.add_layer(QualityLayer::with_rate(1, 1.0))
.expect("add layer index 1");
let err = rc.add_layer(QualityLayer::with_rate(0, 0.5));
assert!(err.is_err());
}
#[test]
fn test_layer_byte_budget_basic() {
let mut rc = RateController::new(256, 256, 1);
rc.add_layer(QualityLayer::with_rate(0, 1.0))
.expect("add 1bpp layer"); let budget = rc.layer_byte_budget(0);
assert_eq!(budget, Some(8192));
}
#[test]
fn test_layer_byte_budget_multi_component() {
let mut rc = RateController::new(64, 64, 3);
rc.add_layer(QualityLayer::with_rate(0, 8.0))
.expect("add 8bpp layer"); let budget = rc.layer_byte_budget(0);
assert_eq!(budget, Some(12288));
}
#[test]
fn test_layer_byte_budget_lossless_returns_none() {
let mut rc = RateController::new(256, 256, 1);
rc.add_layer(QualityLayer::lossless(0))
.expect("add lossless layer");
assert_eq!(rc.layer_byte_budget(0), None);
}
#[test]
fn test_layer_byte_budget_out_of_range() {
let rc = RateController::new(256, 256, 1);
assert_eq!(rc.layer_byte_budget(99), None);
}
#[test]
fn test_allocate_passes_basic() {
let mut rc = RateController::new(64, 64, 1);
rc.add_layer(QualityLayer::with_rate(0, 2.0))
.expect("add 2bpp layer");
rc.add_layer(QualityLayer::lossless(1))
.expect("add lossless layer 1");
let slopes = vec![
SlopeEntry {
distortion_reduction: 10.0,
byte_cost: 100,
},
SlopeEntry {
distortion_reduction: 100.0,
byte_cost: 100,
},
];
let assignments = rc.allocate_passes(&slopes).expect("allocate passes");
assert_eq!(assignments.len(), 2);
assert!(assignments[1] <= assignments[0]);
}
#[test]
fn test_allocate_passes_no_layers_fails() {
let rc = RateController::new(64, 64, 1);
let slopes = vec![SlopeEntry {
distortion_reduction: 1.0,
byte_cost: 10,
}];
assert!(rc.allocate_passes(&slopes).is_err());
}
#[test]
fn test_total_pixels() {
let rc = RateController::new(100, 200, 3);
assert_eq!(rc.total_pixels(), 60000);
}
#[test]
fn test_get_layer() {
let mut rc = RateController::new(256, 256, 1);
rc.add_layer(QualityLayer::with_rate(0, 1.0))
.expect("add layer for get_layer test");
assert!(rc.get_layer(0).is_some());
assert!(rc.get_layer(1).is_none());
}
}