use crate::color::ColorMetadata;
use crate::config;
use crate::error::DecodeError;
use crate::fmt::{BitDepth, ChromaFormat};
use crate::heif;
use crate::metadata::{CleanAperture, ContentLightLevel, Orientation, PixelAspectRatio};
#[derive(Clone, Debug, PartialEq)]
pub struct ImageInfo {
pub width: u32,
pub height: u32,
pub coded_width: u32,
pub coded_height: u32,
pub bit_depth: BitDepth,
pub chroma: ChromaFormat,
pub color: ColorMetadata,
pub orientation: Orientation,
pub has_alpha: bool,
pub is_grid: bool,
pub content_light_level: Option<ContentLightLevel>,
pub clean_aperture: Option<CleanAperture>,
pub pixel_aspect_ratio: Option<PixelAspectRatio>,
pub has_exif: bool,
}
impl ImageInfo {
pub fn channels(&self) -> u8 {
let color = if self.chroma.is_monochrome() { 1 } else { 3 };
color + u8::from(self.has_alpha)
}
pub fn bits_per_pixel(&self) -> f32 {
let samples_per_pixel = if self.chroma.is_monochrome() {
1.0
} else {
let sub = (self.chroma.sub_w() * self.chroma.sub_h()) as f32;
1.0 + 2.0 / sub
};
let alpha = if self.has_alpha { 1.0 } else { 0.0 };
(samples_per_pixel + alpha) * self.bit_depth.bits() as f32
}
pub fn pixel_count(&self) -> u64 {
self.width as u64 * self.height as u64
}
}
pub fn read_heic_info(file: &[u8]) -> Result<ImageInfo, DecodeError> {
let heif = heif::parse(file)?;
let (coded_w, coded_h, orientation, hvcc): (u32, u32, Orientation, &[u8]) =
if let Some(grid) = &heif.grid {
let hvcc = grid
.tiles
.iter()
.map(|t| t.hvcc.as_slice())
.find(|h| !h.is_empty())
.unwrap_or(&[]);
(
grid.output_width,
grid.output_height,
grid.orientation,
hvcc,
)
} else {
(
heif.primary.display_w,
heif.primary.display_h,
heif.primary.orientation,
heif.primary.hvcc.as_slice(),
)
};
let (sps, _pps) = config::parse_hvcc_full(hvcc)?;
let bit_depth = sps.bit_depth()?;
let chroma = sps.chroma;
let has_alpha = heif
.alpha
.as_ref()
.map(|a| !a.hvcc.is_empty())
.unwrap_or(false);
Ok(ImageInfo {
width: coded_w,
height: coded_h,
coded_width: coded_w,
coded_height: coded_h,
bit_depth,
chroma,
color: heif.primary.color.clone(),
orientation,
has_alpha,
is_grid: heif.grid.is_some(),
content_light_level: heif.primary.cll,
clean_aperture: heif.primary.clap,
pixel_aspect_ratio: heif.primary.pasp,
has_exif: heif.exif.is_some(),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn info(chroma: ChromaFormat, bd: BitDepth, alpha: bool) -> ImageInfo {
ImageInfo {
width: 100,
height: 50,
coded_width: 100,
coded_height: 50,
bit_depth: bd,
chroma,
color: ColorMetadata::default(),
orientation: Orientation::Normal,
has_alpha: alpha,
is_grid: false,
content_light_level: None,
clean_aperture: None,
pixel_aspect_ratio: None,
has_exif: false,
}
}
#[test]
fn bpp_matches_subsampling() {
assert_eq!(
info(ChromaFormat::Yuv420, BitDepth::Eight, false).bits_per_pixel(),
12.0
);
assert_eq!(
info(ChromaFormat::Yuv420, BitDepth::Ten, false).bits_per_pixel(),
15.0
);
assert_eq!(
info(ChromaFormat::Yuv422, BitDepth::Eight, false).bits_per_pixel(),
16.0
);
assert_eq!(
info(ChromaFormat::Yuv444, BitDepth::Eight, false).bits_per_pixel(),
24.0
);
assert_eq!(
info(ChromaFormat::Monochrome, BitDepth::Eight, false).bits_per_pixel(),
8.0
);
}
#[test]
fn alpha_adds_one_plane() {
assert_eq!(
info(ChromaFormat::Yuv444, BitDepth::Eight, true).bits_per_pixel(),
32.0
);
assert_eq!(
info(ChromaFormat::Yuv420, BitDepth::Eight, true).bits_per_pixel(),
20.0
);
}
#[test]
fn channel_counts() {
assert_eq!(
info(ChromaFormat::Yuv420, BitDepth::Eight, false).channels(),
3
);
assert_eq!(
info(ChromaFormat::Yuv420, BitDepth::Eight, true).channels(),
4
);
assert_eq!(
info(ChromaFormat::Monochrome, BitDepth::Eight, false).channels(),
1
);
assert_eq!(
info(ChromaFormat::Monochrome, BitDepth::Eight, true).channels(),
2
);
}
#[test]
fn pixel_count_uses_display_dims() {
assert_eq!(
info(ChromaFormat::Yuv420, BitDepth::Eight, false).pixel_count(),
5000
);
}
}