use crate::codec::jpeg::JpegImage;
use crate::stego::armor::ecc;
use crate::stego::armor::selection::compute_stability_map;
use crate::stego::armor::spreading::SPREAD_LEN;
use crate::stego::error::StegoError;
use crate::stego::frame::{FRAME_OVERHEAD, FRAME_OVERHEAD_EXT};
pub fn estimate_armor_capacity(img: &JpegImage) -> Result<usize, StegoError> {
let grid = img.dct_grid(0);
let qt_id = img.frame_info().components[0].quant_table_id as usize;
let qt = img
.quant_table(qt_id)
.ok_or(StegoError::NoLuminanceChannel)?;
let cost_map = compute_stability_map(grid, qt);
let bt = cost_map.blocks_tall();
let bw = cost_map.blocks_wide();
let mut stable_count = 0usize;
for br in 0..bt {
for bc in 0..bw {
for i in 0..8 {
for j in 0..8 {
if i == 0 && j == 0 {
continue;
}
if cost_map.get(br, bc, i, j).is_finite() {
stable_count += 1;
}
}
}
}
}
let num_units = stable_count / SPREAD_LEN;
let qf_header_units = super::embedding::HEADER_UNITS;
if num_units <= qf_header_units {
return Ok(0);
}
let payload_units = num_units - qf_header_units;
let embeddable_bytes = payload_units / 8;
if embeddable_bytes == 0 {
return Ok(0);
}
let parity = ecc::parity_len();
if embeddable_bytes <= parity {
return Ok(0);
}
let max_frame_bytes = (embeddable_bytes as f64 * 191.0 / 255.0).floor() as usize;
if max_frame_bytes <= FRAME_OVERHEAD {
return Ok(0);
}
let plaintext_cap = max_frame_bytes - FRAME_OVERHEAD;
let (capacity, overhead) = if plaintext_cap > u16::MAX as usize {
(max_frame_bytes.saturating_sub(FRAME_OVERHEAD_EXT), FRAME_OVERHEAD_EXT)
} else {
(plaintext_cap, FRAME_OVERHEAD)
};
let frame_len = capacity + overhead;
let rs_len = ecc::rs_encoded_len(frame_len);
if rs_len > embeddable_bytes {
if capacity == 0 {
return Ok(0);
}
return Ok(capacity - 1);
}
Ok(capacity)
}