use crate::types::PixelFormat;
use super::DecodeWarning;
use super::extras::DecodedExtras;
#[derive(Clone)]
#[non_exhaustive]
pub struct DecodedImage {
pub width: u32,
pub height: u32,
pub format: PixelFormat,
pub data: Vec<u8>,
pub(crate) extras: Option<DecodedExtras>,
pub(crate) warnings: Vec<DecodeWarning>,
}
impl core::fmt::Debug for DecodedImage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DecodedImage")
.field("width", &self.width)
.field("height", &self.height)
.field("format", &self.format)
.field("data_len", &self.data.len())
.field("has_extras", &self.extras.is_some())
.finish()
}
}
impl DecodedImage {
#[must_use]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub fn pixels(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn bytes_per_pixel(&self) -> usize {
self.format.bytes_per_pixel()
}
#[must_use]
pub fn stride(&self) -> usize {
self.width as usize * self.bytes_per_pixel()
}
#[must_use]
pub fn extras(&self) -> Option<&DecodedExtras> {
self.extras.as_ref()
}
#[must_use]
pub fn take_extras(&mut self) -> Option<DecodedExtras> {
self.extras.take()
}
#[must_use]
pub fn warnings(&self) -> &[DecodeWarning] {
&self.warnings
}
#[must_use]
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
#[must_use]
pub fn into_parts(self) -> (Vec<u8>, u32, u32, PixelFormat, Option<DecodedExtras>) {
(self.data, self.width, self.height, self.format, self.extras)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct DecodedImageF32 {
pub width: u32,
pub height: u32,
pub format: PixelFormat,
pub data: Vec<f32>,
pub(crate) warnings: Vec<DecodeWarning>,
}
impl DecodedImageF32 {
#[must_use]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub fn pixels(&self) -> &[f32] {
&self.data
}
#[must_use]
pub fn channels(&self) -> usize {
self.format.num_channels()
}
#[must_use]
pub fn stride(&self) -> usize {
self.width as usize * self.channels()
}
#[must_use]
pub fn to_u8(&self) -> DecodedImage {
let len = self.data.len();
let mut data = vec![0u8; len];
for i in 0..len {
data[i] = (self.data[i] * 255.0).round().clamp(0.0, 255.0) as u8;
}
DecodedImage {
width: self.width,
height: self.height,
format: self.format,
data,
extras: None,
warnings: self.warnings.clone(),
}
}
#[must_use]
pub fn to_u16(&self) -> Vec<u16> {
let len = self.data.len();
let mut result = vec![0u16; len];
for i in 0..len {
result[i] = (self.data[i] * 65535.0).round().clamp(0.0, 65535.0) as u16;
}
result
}
#[must_use]
pub fn warnings(&self) -> &[DecodeWarning] {
&self.warnings
}
#[must_use]
pub fn has_warnings(&self) -> bool {
!self.warnings.is_empty()
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct DecodedYCbCr {
pub y: Vec<f32>,
pub cb: Vec<f32>,
pub cr: Vec<f32>,
pub width: u32,
pub height: u32,
pub icc_profile: Option<Vec<u8>>,
}
impl DecodedYCbCr {
#[must_use]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub fn plane_size(&self) -> usize {
self.width as usize * self.height as usize
}
pub fn shift_to_jpeg_range(&mut self) {
for v in &mut self.y {
*v += 128.0;
}
for v in &mut self.cb {
*v += 128.0;
}
for v in &mut self.cr {
*v += 128.0;
}
}
#[must_use]
pub fn y_to_jpeg_range(&self) -> Vec<f32> {
self.y.iter().map(|&v| v + 128.0).collect()
}
#[must_use]
pub fn cb_to_jpeg_range(&self) -> Vec<f32> {
self.cb.iter().map(|&v| v + 128.0).collect()
}
#[must_use]
pub fn cr_to_jpeg_range(&self) -> Vec<f32> {
self.cr.iter().map(|&v| v + 128.0).collect()
}
}
#[derive(Debug, Clone)]
pub struct ComponentCoefficients {
pub id: u8,
pub coeffs: Vec<i16>,
pub blocks_wide: usize,
pub blocks_high: usize,
pub h_samp: u8,
pub v_samp: u8,
pub quant_table_idx: u8,
}
impl ComponentCoefficients {
#[must_use]
pub fn block(&self, block_idx: usize) -> &[i16] {
let start = block_idx * 64;
&self.coeffs[start..start + 64]
}
#[must_use]
pub fn block_at(&self, bx: usize, by: usize) -> &[i16] {
self.block(by * self.blocks_wide + bx)
}
#[must_use]
pub fn num_blocks(&self) -> usize {
self.blocks_wide * self.blocks_high
}
}
#[derive(Debug, Clone)]
pub struct DecodedCoefficients {
pub width: u32,
pub height: u32,
pub components: Vec<ComponentCoefficients>,
pub quant_tables: Vec<Option<[u16; 64]>>,
}
impl DecodedCoefficients {
#[must_use]
pub fn num_components(&self) -> usize {
self.components.len()
}
#[must_use]
pub fn compare(&self, other: &DecodedCoefficients) -> CoefficientComparison {
let mut total_blocks = 0usize;
let mut differing_blocks = 0usize;
let mut max_diff = 0i16;
let mut total_diff_coeffs = 0usize;
let mut diff_by_position = [0u64; 64];
for (comp_idx, (c1, c2)) in self.components.iter().zip(&other.components).enumerate() {
let num_blocks = c1.num_blocks().min(c2.num_blocks());
for block_idx in 0..num_blocks {
total_blocks += 1;
let b1 = c1.block(block_idx);
let b2 = c2.block(block_idx);
let mut has_diff = false;
for coeff_idx in 0..64 {
let diff = (b1[coeff_idx] as i32 - b2[coeff_idx] as i32).abs() as i16;
if diff != 0 {
has_diff = true;
total_diff_coeffs += 1;
diff_by_position[coeff_idx] += 1;
if diff > max_diff {
max_diff = diff;
}
}
}
if has_diff {
differing_blocks += 1;
}
}
if c1.num_blocks() != c2.num_blocks() {
eprintln!(
"Warning: component {} block count mismatch: {} vs {}",
comp_idx,
c1.num_blocks(),
c2.num_blocks()
);
}
}
CoefficientComparison {
total_blocks,
differing_blocks,
max_diff,
total_diff_coeffs,
diff_by_position,
}
}
}
#[derive(Debug, Clone)]
pub struct CoefficientComparison {
pub total_blocks: usize,
pub differing_blocks: usize,
pub max_diff: i16,
pub total_diff_coeffs: usize,
pub diff_by_position: [u64; 64],
}
impl CoefficientComparison {
#[must_use]
pub fn diff_block_pct(&self) -> f64 {
if self.total_blocks == 0 {
0.0
} else {
100.0 * self.differing_blocks as f64 / self.total_blocks as f64
}
}
#[must_use]
pub fn dc_diff_pct(&self) -> f64 {
if self.total_blocks == 0 {
0.0
} else {
100.0 * self.diff_by_position[0] as f64 / self.total_blocks as f64
}
}
}