use jxl_bitstream::{Bitstream, Result, U};
use jxl_color::header::*;
use jxl_oxide_common::{define_bundle, Bundle, Name};
#[derive(Debug)]
pub struct ImageHeader {
pub size: SizeHeader,
pub metadata: ImageMetadata,
}
impl<Ctx> Bundle<Ctx> for ImageHeader {
type Error = jxl_bitstream::Error;
fn parse(bitstream: &mut Bitstream, _: Ctx) -> Result<Self> {
let signature = bitstream.read_bits(16)?;
if signature != 0xaff {
return Err(jxl_bitstream::Error::ValidationFailed(
"JPEG XL signature mismatch",
));
}
let size = SizeHeader::parse(bitstream, ())?;
let metadata = ImageMetadata::parse(bitstream, ())?;
if metadata.ec_info.len() > 256 {
tracing::error!(num_extra = metadata.ec_info.len(), "num_extra too large");
return Err(jxl_bitstream::Error::ProfileConformance(
"num_extra too large",
));
}
let tone_mapping = &metadata.tone_mapping;
if tone_mapping.intensity_target <= 0.0 {
return Err(jxl_bitstream::Error::ValidationFailed(
"Invalid intensity target",
));
}
if tone_mapping.min_nits < 0.0 || tone_mapping.min_nits > tone_mapping.intensity_target {
return Err(jxl_bitstream::Error::ValidationFailed(
"Invalid tone mapping min_nits",
));
}
if tone_mapping.linear_below < 0.0
|| (tone_mapping.relative_to_max_display && tone_mapping.linear_below > 1.0)
{
return Err(jxl_bitstream::Error::ValidationFailed(
"Invalid tone mapping linear_below",
));
}
Ok(Self { size, metadata })
}
}
impl ImageHeader {
#[inline]
pub fn width_with_orientation(&self) -> u32 {
self.metadata
.apply_orientation(self.size.width, self.size.height, 0, 0, false)
.0
}
#[inline]
pub fn height_with_orientation(&self) -> u32 {
self.metadata
.apply_orientation(self.size.width, self.size.height, 0, 0, false)
.1
}
}
define_bundle! {
#[derive(Debug)]
pub struct SizeHeader {
div8: ty(Bool) default(false),
h_div8: ty(1 + u(5)) cond(div8) default(0),
pub height:
ty(U32(1 + u(9), 1 + u(13), 1 + u(18), 1 + u(30))) cond(!div8)
default(8 * h_div8),
ratio: ty(u(3)) default(0),
w_div8: ty(1 + u(5)) cond(div8 && ratio == 0) default(0),
pub width:
ty(U32(1 + u(9), 1 + u(13), 1 + u(18), 1 + u(30))) cond(!div8 && ratio == 0)
default(SizeHeader::compute_default_width(ratio, w_div8, height)),
}
}
impl SizeHeader {
fn compute_default_width(ratio: u32, w_div8: u32, height: u32) -> u32 {
let height = height as u64;
let res = match ratio {
0 => 8 * w_div8 as u64,
1 => height,
2 => height * 12 / 10,
3 => height * 4 / 3,
4 => height * 3 / 2,
5 => height * 16 / 9,
6 => height * 5 / 4,
7 => height * 2,
_ => panic!("Invalid ratio const: {}", ratio),
};
res as u32
}
}
define_bundle! {
#[derive(Debug)]
pub struct ImageMetadata {
all_default: ty(Bool) default(true),
extra_fields: ty(Bool) cond(!all_default) default(false),
pub orientation: ty(1 + u(3)) cond(extra_fields) default(1),
have_intr_size: ty(Bool) cond(extra_fields) default(false),
pub intrinsic_size: ty(Bundle(Option<SizeHeader>)) cond(have_intr_size),
have_preview: ty(Bool) cond(extra_fields) default(false),
pub preview: ty(Bundle(Option<PreviewHeader>)) cond(have_preview),
have_animation: ty(Bool) cond(extra_fields) default(false),
pub animation: ty(Bundle(Option<AnimationHeader>)) cond(have_animation),
pub bit_depth: ty(Bundle(BitDepth)) cond(!all_default),
pub modular_16bit_buffers: ty(Bool) cond(!all_default) default(true),
num_extra: ty(U32(0, 1, 2 + u(4), 1 + u(12))) cond(!all_default) default(0),
pub ec_info: ty(Vec[Bundle(ExtraChannelInfo)]; num_extra) cond(!all_default),
pub xyb_encoded: ty(Bool) cond(!all_default) default(true),
pub colour_encoding: ty(Bundle(ColourEncoding)) cond(!all_default),
pub tone_mapping: ty(Bundle(ToneMapping)) cond(extra_fields),
pub extensions: ty(Bundle(Extensions)) cond(!all_default),
default_m: ty(Bool),
pub opsin_inverse_matrix: ty(Bundle(OpsinInverseMatrix)) cond(!default_m && xyb_encoded),
cw_mask: ty(u(3)) cond(!default_m) default(0),
pub up2_weight: ty(Array[F16]; 15) cond(cw_mask & 1 != 0) default(Self::D_UP2),
pub up4_weight: ty(Array[F16]; 55) cond(cw_mask & 2 != 0) default(Self::D_UP4),
pub up8_weight: ty(Array[F16]; 210) cond(cw_mask & 4 != 0) default(Self::D_UP8),
}
#[derive(Debug)]
pub struct PreviewHeader {
div8: ty(Bool),
h_div8: ty(U32(16, 32, 1 + u(5), 33 + u(9))) cond(div8) default(1),
pub height:
ty(U32(1 + u(6), 65 + u(8), 321 + u(10), 1345 + u(12))) cond(!div8)
default(8 * h_div8),
ratio: ty(u(3)),
w_div8: ty(U32(16, 32, 1 + u(5), 33 + u(9))) cond(div8) default(1),
pub width:
ty(U32(1 + u(6), 65 + u(8), 321 + u(10), 1345 + u(12))) cond(!div8)
default(SizeHeader::compute_default_width(ratio, w_div8, height)),
}
#[derive(Debug)]
pub struct AnimationHeader {
pub tps_numerator: ty(U32(100, 1000, 1 + u(10), 1 + u(30))) default(0),
pub tps_denominator: ty(U32(1, 1001, 1 + u(8), 1 + u(10))) default(0),
pub num_loops: ty(U32(0, u(3), u(16), u(32))) default(0),
pub have_timecodes: ty(Bool) default(false),
}
}
#[derive(Debug, Default)]
#[allow(unused)]
pub struct Extensions {
extension_bits: u64,
}
impl<Ctx> Bundle<Ctx> for Extensions {
type Error = jxl_bitstream::Error;
fn parse(bitstream: &mut Bitstream, _: Ctx) -> jxl_bitstream::Result<Self> {
let extension_bits = bitstream.read_u64()?;
let mut bits = extension_bits;
let mut extension_data_bitlen = Vec::with_capacity(extension_bits.count_ones() as usize);
for extension_idx in 0..64 {
if bits & 1 != 0 {
tracing::warn!(extension_idx, "Unknown extension");
extension_data_bitlen.push(bitstream.read_u64()?);
}
bits >>= 1;
}
for len in extension_data_bitlen {
bitstream.skip_bits(len as usize)?;
}
Ok(Self { extension_bits })
}
}
impl ImageMetadata {
#[inline]
pub fn grayscale(&self) -> bool {
self.colour_encoding.colour_space() == ColourSpace::Grey
}
pub fn alpha(&self) -> Option<usize> {
self.ec_info
.iter()
.position(|info| matches!(info.ty, ExtraChannelType::Alpha { .. }))
}
#[inline]
pub fn apply_orientation(
&self,
width: u32,
height: u32,
left: i32,
top: i32,
inverse: bool,
) -> (u32, u32, i32, i32) {
let (left, top) = match self.orientation {
1 => (left, top),
2 => (width as i32 - left - 1, top),
3 => (width as i32 - left - 1, height as i32 - top - 1),
4 => (left, height as i32 - top - 1),
5 => (top, left),
6 if inverse => (top, width as i32 - left - 1),
6 => (height as i32 - top - 1, left),
7 => (height as i32 - top - 1, width as i32 - left - 1),
8 if inverse => (height as i32 - top - 1, left),
8 => (top, width as i32 - left - 1),
_ => unreachable!(),
};
let (width, height) = match self.orientation {
1..=4 => (width, height),
5..=8 => (height, width),
_ => unreachable!(),
};
(width, height, left, top)
}
}
#[derive(Debug, Default, Clone)]
pub struct ExtraChannelInfo {
pub ty: ExtraChannelType,
pub bit_depth: BitDepth,
pub dim_shift: u32,
pub name: Name,
}
impl<Ctx> Bundle<Ctx> for ExtraChannelInfo {
type Error = jxl_bitstream::Error;
fn parse(bitstream: &mut Bitstream, _: Ctx) -> Result<Self> {
let default_alpha_channel = bitstream.read_bool()?;
if default_alpha_channel {
return Ok(Self::default());
}
let ty_id = bitstream.read_enum::<ExtraChannelTypeRaw>()?;
let bit_depth = BitDepth::parse(bitstream, ())?;
let dim_shift = bitstream.read_u32(0, 3, 4, 1 + U(3))?;
let name = Name::parse(bitstream, ())?;
let ty = match ty_id {
ExtraChannelTypeRaw::Alpha => ExtraChannelType::Alpha {
alpha_associated: bitstream.read_bool()?,
},
ExtraChannelTypeRaw::Depth => ExtraChannelType::Depth,
ExtraChannelTypeRaw::SpotColour => ExtraChannelType::SpotColour {
red: bitstream.read_f16_as_f32()?,
green: bitstream.read_f16_as_f32()?,
blue: bitstream.read_f16_as_f32()?,
solidity: bitstream.read_f16_as_f32()?,
},
ExtraChannelTypeRaw::SelectionMask => ExtraChannelType::SelectionMask,
ExtraChannelTypeRaw::Black => ExtraChannelType::Black,
ExtraChannelTypeRaw::Cfa => ExtraChannelType::Cfa {
cfa_channel: bitstream.read_u32(1, U(2), 3 + U(4), 19 + U(8))?,
},
ExtraChannelTypeRaw::Thermal => ExtraChannelType::Thermal,
ExtraChannelTypeRaw::NonOptional => ExtraChannelType::NonOptional,
ExtraChannelTypeRaw::Optional => ExtraChannelType::Optional,
};
Ok(Self {
ty,
bit_depth,
dim_shift,
name,
})
}
}
impl ExtraChannelInfo {
#[inline]
pub fn is_alpha(&self) -> bool {
matches!(self.ty, ExtraChannelType::Alpha { .. })
}
#[inline]
pub fn alpha_associated(&self) -> Option<bool> {
if let ExtraChannelType::Alpha { alpha_associated } = self.ty {
Some(alpha_associated)
} else {
None
}
}
#[inline]
pub fn is_black(&self) -> bool {
self.ty == ExtraChannelType::Black
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum ExtraChannelType {
Alpha {
alpha_associated: bool,
} = 0,
Depth,
SpotColour {
red: f32,
green: f32,
blue: f32,
solidity: f32,
},
SelectionMask,
Black,
Cfa {
cfa_channel: u32,
},
Thermal,
NonOptional = 15,
Optional,
}
impl Default for ExtraChannelType {
fn default() -> Self {
Self::Alpha {
alpha_associated: false,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(u8)]
enum ExtraChannelTypeRaw {
Alpha = 0,
Depth,
SpotColour,
SelectionMask,
Black,
Cfa,
Thermal,
NonOptional = 15,
Optional,
}
impl TryFrom<u32> for ExtraChannelTypeRaw {
type Error = ();
fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
Ok(match value {
0 => Self::Alpha,
1 => Self::Depth,
2 => Self::SpotColour,
3 => Self::SelectionMask,
4 => Self::Black,
5 => Self::Cfa,
6 => Self::Thermal,
15 => Self::NonOptional,
16 => Self::Optional,
_ => return Err(()),
})
}
}
#[derive(Debug, Copy, Clone)]
pub enum BitDepth {
IntegerSample { bits_per_sample: u32 },
FloatSample { bits_per_sample: u32, exp_bits: u32 },
}
impl Default for BitDepth {
fn default() -> Self {
Self::IntegerSample { bits_per_sample: 8 }
}
}
impl BitDepth {
#[inline]
pub fn bits_per_sample(self) -> u32 {
match self {
Self::IntegerSample { bits_per_sample } => bits_per_sample,
Self::FloatSample {
bits_per_sample, ..
} => bits_per_sample,
}
}
#[inline]
pub fn parse_integer_sample(self, sample: i32) -> f32 {
match self {
Self::IntegerSample { bits_per_sample } => {
let div = (1i32 << bits_per_sample) - 1;
sample as f32 / div as f32
}
Self::FloatSample {
bits_per_sample,
exp_bits,
} => {
let sample = sample as u32;
let mantissa_bits = bits_per_sample - exp_bits - 1;
let mantissa_mask = (1u32 << mantissa_bits) - 1;
let exp_mask = ((1u32 << (bits_per_sample - 1)) - 1) ^ mantissa_mask;
let is_signed = (sample & (1u32 << (bits_per_sample - 1))) != 0;
let mantissa = sample & mantissa_mask;
let exp = ((sample & exp_mask) >> mantissa_bits) as i32;
let exp = exp - ((1 << (exp_bits - 1)) - 1);
let f32_mantissa_bits = f32::MANTISSA_DIGITS - 1;
let mantissa = match mantissa_bits.cmp(&f32_mantissa_bits) {
std::cmp::Ordering::Less => mantissa << (f32_mantissa_bits - mantissa_bits),
std::cmp::Ordering::Greater => mantissa >> (mantissa_bits - f32_mantissa_bits),
_ => mantissa,
};
let exp = (exp + 127) as u32;
let sign = is_signed as u32;
let bits = (sign << 31) | (exp << f32_mantissa_bits) | mantissa;
f32::from_bits(bits)
}
}
}
}
impl<Ctx> Bundle<Ctx> for BitDepth {
type Error = jxl_bitstream::Error;
fn parse(bitstream: &mut Bitstream, _ctx: Ctx) -> Result<Self> {
if bitstream.read_bool()? {
let bits_per_sample = bitstream.read_u32(32, 16, 24, 1 + U(6))?;
let exp_bits = bitstream.read_bits(4)? + 1;
if !(2..=8).contains(&exp_bits) {
return Err(jxl_bitstream::Error::ValidationFailed(
"Invalid exp_bits per float sample",
));
}
let mantissa_bits = bits_per_sample.wrapping_sub(exp_bits + 1);
if !(2..=23).contains(&mantissa_bits) {
return Err(jxl_bitstream::Error::ValidationFailed(
"Invalid mantissa_bits per float sample",
));
}
Ok(Self::FloatSample {
bits_per_sample,
exp_bits,
})
} else {
let bits_per_sample = bitstream.read_u32(8, 10, 12, 1 + U(6))?;
if bits_per_sample > 31 {
return Err(jxl_bitstream::Error::ValidationFailed(
"Invalid bits_per_sample",
));
}
Ok(Self::IntegerSample { bits_per_sample })
}
}
}
#[allow(clippy::excessive_precision)]
#[rustfmt::skip]
impl ImageMetadata {
const D_UP2: [f32; 15] = [
-0.01716200, -0.03452303, -0.04022174, -0.02921014, -0.00624645,
0.14111091, 0.28896755, 0.00278718, -0.01610267, 0.56661550,
0.03777607, -0.01986694, -0.03144731, -0.01185068, -0.00213539,
];
const D_UP4: [f32; 55] = [
-0.02419067, -0.03491987, -0.03693351, -0.03094285, -0.00529785,
-0.01663432, -0.03556863, -0.03888905, -0.03516850, -0.00989469,
0.23651958, 0.33392945, -0.01073543, -0.01313181, -0.03556694,
0.13048175, 0.40103025, 0.03951150, -0.02077584, 0.46914198,
-0.00209270, -0.01484589, -0.04064806, 0.18942530, 0.56279892,
0.06674400, -0.02335494, -0.03551682, -0.00754830, -0.02267919,
-0.02363578, 0.00315804, -0.03399098, -0.01359519, -0.00091653,
-0.00335467, -0.01163294, -0.01610294, -0.00974088, -0.00191622,
-0.01095446, -0.03198464, -0.04455121, -0.02799790, -0.00645912,
0.06390599, 0.22963888, 0.00630981, -0.01897349, 0.67537268,
0.08483369, -0.02534994, -0.02205197, -0.01667999, -0.00384443,
];
const D_UP8: [f32; 210] = [
-0.02928613, -0.03706353, -0.03783812, -0.03324558, -0.00447632,
-0.02519406, -0.03752601, -0.03901508, -0.03663285, -0.00646649,
-0.02066407, -0.03838633, -0.04002101, -0.03900035, -0.00901973,
-0.01626393, -0.03954148, -0.04046620, -0.03979621, -0.01224485,
0.29895328, 0.35757708, -0.02447552, -0.01081748, -0.04314594,
0.23903219, 0.41119301, -0.00573046, -0.01450239, -0.04246845,
0.17567618, 0.45220643, 0.02287757, -0.01936783, -0.03583255,
0.11572472, 0.47416733, 0.06284440, -0.02685066, 0.42720050,
-0.02248939, -0.01155273, -0.04562755, 0.28689496, 0.49093869,
-0.00007891, -0.01545926, -0.04562659, 0.21238920, 0.53980934,
0.03369474, -0.02070211, -0.03866988, 0.14229550, 0.56593398,
0.08045181, -0.02888298, -0.03680918, -0.00542229, -0.02920477,
-0.02788574, -0.02118180, -0.03942402, -0.00775547, -0.02433614,
-0.03193943, -0.02030828, -0.04044014, -0.01074016, -0.01930822,
-0.03620399, -0.01974125, -0.03919545, -0.01456093, -0.00045072,
-0.00360110, -0.01020207, -0.01231907, -0.00638988, -0.00071592,
-0.00279122, -0.00957115, -0.01288327, -0.00730937, -0.00107783,
-0.00210156, -0.00890705, -0.01317668, -0.00813895, -0.00153491,
-0.02128481, -0.04173044, -0.04831487, -0.03293190, -0.00525260,
-0.01720322, -0.04052736, -0.05045706, -0.03607317, -0.00738030,
-0.01341764, -0.03965629, -0.05151616, -0.03814886, -0.01005819,
0.18968273, 0.33063684, -0.01300105, -0.01372950, -0.04017465,
0.13727832, 0.36402234, 0.01027890, -0.01832107, -0.03365072,
0.08734506, 0.38194295, 0.04338228, -0.02525993, 0.56408126,
0.00458352, -0.01648227, -0.04887868, 0.24585519, 0.62026135,
0.04314807, -0.02213737, -0.04158014, 0.16637289, 0.65027023,
0.09621636, -0.03101388, -0.04082742, -0.00904519, -0.02790922,
-0.02117818, 0.00798662, -0.03995711, -0.01243427, -0.02231705,
-0.02946266, 0.00992055, -0.03600283, -0.01684920, -0.00111684,
-0.00411204, -0.01297130, -0.01723725, -0.01022545, -0.00165306,
-0.00313110, -0.01218016, -0.01763266, -0.01125620, -0.00231663,
-0.01374149, -0.03797620, -0.05142937, -0.03117307, -0.00581914,
-0.01064003, -0.03608089, -0.05272168, -0.03375670, -0.00795586,
0.09628104, 0.27129991, -0.00353779, -0.01734151, -0.03153981,
0.05686230, 0.28500998, 0.02230594, -0.02374955, 0.68214326,
0.05018048, -0.02320852, -0.04383616, 0.18459474, 0.71517975,
0.10805613, -0.03263677, -0.03637639, -0.01394373, -0.02511203,
-0.01728636, 0.05407331, -0.02867568, -0.01893131, -0.00240854,
-0.00446511, -0.01636187, -0.02377053, -0.01522848, -0.00333334,
-0.00819975, -0.02964169, -0.04499287, -0.02745350, -0.00612408,
0.02727416, 0.19446600, 0.00159832, -0.02232473, 0.74982506,
0.11452620, -0.03348048, -0.01605681, -0.02070339, -0.00458223,
];
}