// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #![allow(clippy::excessive_precision)] use crate::{ BLOCK_DIM, GROUP_DIM, bit_reader::BitReader, error::Error, headers::{encodings::*, extra_channels::ExtraChannelInfo}, image::Rect, util::FloorLog2, }; use jxl_macros::UnconditionalCoder; use num_derive::FromPrimitive; use std::cmp::min; use super::Animation; #[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)] pub enum FrameType { RegularFrame = 0, LFFrame = 1, ReferenceOnly = 2, SkipProgressive = 3, } #[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)] pub enum Encoding { VarDCT = 0, Modular = 1, } struct Flags; impl Flags { pub const ENABLE_NOISE: u64 = 1; pub const ENABLE_PATCHES: u64 = 2; pub const ENABLE_SPLINES: u64 = 0x10; pub const USE_LF_FRAME: u64 = 0x20; pub const SKIP_ADAPTIVE_LF_SMOOTHING: u64 = 0x80; } #[derive(UnconditionalCoder, Debug, PartialEq)] pub struct Passes { #[coder(u2S(1, 2, 3, Bits(3) + 4))] #[default(1)] pub num_passes: u32, #[coder(u2S(0, 1, 2, Bits(1) + 3))] #[default(0)] #[condition(num_passes != 1)] num_ds: u32, #[size_coder(explicit(num_passes - 1))] #[coder(Bits(2))] #[default_element(0)] #[condition(num_passes != 1)] pub shift: Vec, #[size_coder(explicit(num_ds))] #[coder(u2S(1, 2, 4, 8))] #[default_element(1)] #[condition(num_passes != 1)] downsample: Vec, #[size_coder(explicit(num_ds))] #[coder(u2S(0, 1, 2, Bits(3)))] #[default_element(0)] #[condition(num_passes != 1)] last_pass: Vec, } impl Passes { pub fn downsampling_bracket(&self, pass: usize) -> (usize, usize) { let mut max_shift = 2; let mut min_shift = 3; for i in 0..pass + 1 { for j in 0..self.num_ds as usize { if i == self.last_pass[j] as usize { min_shift = self.downsample[j].floor_log2(); } } if i + 1 == self.num_passes as usize { min_shift = 0; } if i != pass { max_shift = min_shift.saturating_sub(1); } } (min_shift as usize, max_shift as usize) } } #[derive(UnconditionalCoder, Copy, Clone, PartialEq, Debug, FromPrimitive)] pub enum BlendingMode { Replace = 0, Add = 1, Blend = 2, AlphaWeightedAdd = 3, Mul = 4, } #[derive(Default)] pub struct BlendingInfoNonserialized { num_extra_channels: u32, full_frame: bool, } #[derive(UnconditionalCoder, Debug, PartialEq, Clone)] #[nonserialized(BlendingInfoNonserialized)] pub struct BlendingInfo { #[coder(u2S(0, 1, 2, Bits(2) + 3))] #[default(BlendingMode::Replace)] pub mode: BlendingMode, /* Spec: "Let multi_extra be true if and only if and the number of extra channels is at least two." libjxl condition is num_extra_channels > 0 */ #[coder(u2S(0, 1, 2, Bits(3) + 3))] #[default(0)] #[condition(nonserialized.num_extra_channels > 0 && (mode == BlendingMode::Blend || mode == BlendingMode::AlphaWeightedAdd))] pub alpha_channel: u32, #[default(false)] #[condition(nonserialized.num_extra_channels > 0 && (mode == BlendingMode::Blend || mode == BlendingMode::AlphaWeightedAdd || mode == BlendingMode::Mul))] pub clamp: bool, #[coder(u2S(0, 1, 2, 3))] #[default(0)] // This condition is called `resets_canvas` in the spec #[condition(!(nonserialized.full_frame && mode == BlendingMode::Replace))] pub source: u32, } pub struct RestorationFilterNonserialized { encoding: Encoding, } #[derive(UnconditionalCoder, Debug, PartialEq)] #[nonserialized(RestorationFilterNonserialized)] pub struct RestorationFilter { #[all_default] all_default: bool, #[default(true)] pub gab: bool, #[default(false)] #[condition(gab)] gab_custom: bool, #[default(0.115169525)] #[condition(gab_custom)] pub gab_x_weight1: f32, #[default(0.061248592)] #[condition(gab_custom)] pub gab_x_weight2: f32, #[default(0.115169525)] #[condition(gab_custom)] pub gab_y_weight1: f32, #[default(0.061248592)] #[condition(gab_custom)] pub gab_y_weight2: f32, #[default(0.115169525)] #[condition(gab_custom)] pub gab_b_weight1: f32, #[default(0.061248592)] #[condition(gab_custom)] pub gab_b_weight2: f32, #[coder(Bits(2))] #[default(2)] pub epf_iters: u32, #[default(false)] #[condition(epf_iters > 0 && nonserialized.encoding == Encoding::VarDCT)] epf_sharp_custom: bool, #[default([0.0, 1.0 / 7.0, 2.0 / 7.0, 3.0 / 7.0, 4.0 / 7.0, 5.0 / 7.0, 6.0 / 7.0, 1.0])] #[condition(epf_sharp_custom)] pub epf_sharp_lut: [f32; 8], #[default(false)] #[condition(epf_iters > 0)] epf_weight_custom: bool, #[default([40.0, 5.0, 3.5])] #[condition(epf_weight_custom)] pub epf_channel_scale: [f32; 3], #[default(0.45)] #[condition(epf_weight_custom)] epf_pass1_zeroflush: f32, #[default(0.6)] #[condition(epf_weight_custom)] epf_pass2_zeroflush: f32, #[default(false)] #[condition(epf_iters > 0)] epf_sigma_custom: bool, #[default(0.46)] #[condition(epf_sigma_custom && nonserialized.encoding == Encoding::VarDCT)] pub epf_quant_mul: f32, #[default(0.9)] #[condition(epf_sigma_custom)] pub epf_pass0_sigma_scale: f32, #[default(6.5)] #[condition(epf_sigma_custom)] pub epf_pass2_sigma_scale: f32, #[default(2.0 / 3.0)] #[condition(epf_sigma_custom)] pub epf_border_sad_mul: f32, #[default(1.0)] #[condition(epf_iters > 0 && nonserialized.encoding == Encoding::Modular)] pub epf_sigma_for_modular: f32, #[default(Extensions::default())] extensions: Extensions, } pub struct PermutationNonserialized { pub num_entries: u32, pub permuted: bool, } pub struct FrameHeaderNonserialized { pub xyb_encoded: bool, pub num_extra_channels: u32, pub extra_channel_info: Vec, pub have_animation: bool, pub have_timecode: bool, pub img_width: u32, pub img_height: u32, } const H_SHIFT: [usize; 4] = [0, 1, 1, 0]; const V_SHIFT: [usize; 4] = [0, 1, 0, 1]; fn compute_jpeg_shift(jpeg_upsampling: &[u32], shift_table: &[usize]) -> u32 { jpeg_upsampling .iter() .map(|&ch| shift_table[ch as usize]) .max() .unwrap_or(0) as u32 } #[derive(UnconditionalCoder, Debug, PartialEq)] #[nonserialized(FrameHeaderNonserialized)] #[aligned] #[validate] pub struct FrameHeader { #[all_default] all_default: bool, #[coder(Bits(2))] #[default(FrameType::RegularFrame)] pub frame_type: FrameType, #[coder(Bits(1))] #[default(Encoding::VarDCT)] pub encoding: Encoding, #[default(0)] flags: u64, #[default(false)] #[condition(!nonserialized.xyb_encoded)] pub do_ycbcr: bool, #[coder(Bits(2))] #[default([0, 0, 0])] #[condition(do_ycbcr && flags & Flags::USE_LF_FRAME == 0)] jpeg_upsampling: [u32; 3], #[coder(u2S(1, 2, 4, 8))] #[default(1)] #[condition(flags & Flags::USE_LF_FRAME == 0)] pub upsampling: u32, #[size_coder(explicit(nonserialized.num_extra_channels))] #[coder(u2S(1, 2, 4, 8))] #[default_element(1)] #[condition(flags & Flags::USE_LF_FRAME == 0)] pub ec_upsampling: Vec, #[coder(Bits(2))] #[default(1)] #[condition(encoding == Encoding::Modular)] group_size_shift: u32, #[coder(Bits(3))] #[default(3)] #[condition(encoding == Encoding::VarDCT && nonserialized.xyb_encoded)] pub x_qm_scale: u32, #[coder(Bits(3))] #[default(2)] #[condition(encoding == Encoding::VarDCT && nonserialized.xyb_encoded)] pub b_qm_scale: u32, #[condition(frame_type != FrameType::ReferenceOnly)] #[default(Passes::default(&field_nonserialized))] pub passes: Passes, #[coder(u2S(1, 2, 3, 4))] #[default(0)] #[condition(frame_type == FrameType::LFFrame)] pub lf_level: u32, #[default(false)] #[condition(frame_type != FrameType::LFFrame)] have_crop: bool, #[coder(u2S(Bits(8), Bits(11) + 256, Bits(14) + 2304, Bits(30) + 18688))] #[default(0)] #[condition(have_crop && frame_type != FrameType::ReferenceOnly)] pub x0: i32, #[coder(u2S(Bits(8), Bits(11) + 256, Bits(14) + 2304, Bits(30) + 18688))] #[default(0)] #[condition(have_crop && frame_type != FrameType::ReferenceOnly)] pub y0: i32, #[coder(u2S(Bits(8), Bits(11) + 256, Bits(14) + 2304, Bits(30) + 18688))] #[default(0)] #[condition(have_crop)] frame_width: u32, #[coder(u2S(Bits(8), Bits(11) + 256, Bits(14) + 2304, Bits(30) + 18688))] #[default(0)] #[condition(have_crop)] frame_height: u32, // The following 2 fields are not actually serialized, but just used as variables to help with // defining later conditions. #[default(x0 <= 0 && y0 <= 0 && (frame_width as i64 + x0 as i64) >= nonserialized.img_width as i64 && (frame_height as i64 + y0 as i64) >= nonserialized.img_height as i64)] #[condition(false)] completely_covers: bool, #[default(!have_crop || completely_covers)] #[condition(false)] full_frame: bool, /* "normal_frame" denotes the condition !all_default && (frame_type == kRegularFrame || frame_type == kSkipProgressive) */ #[default(BlendingInfo::default(&field_nonserialized))] #[condition(frame_type == FrameType::RegularFrame || frame_type == FrameType::SkipProgressive)] #[nonserialized(num_extra_channels : nonserialized.num_extra_channels, full_frame : full_frame)] pub blending_info: BlendingInfo, #[size_coder(explicit(nonserialized.num_extra_channels))] #[condition(frame_type == FrameType::RegularFrame || frame_type == FrameType::SkipProgressive)] #[default_element(BlendingInfo::default(&field_nonserialized))] #[nonserialized(num_extra_channels : nonserialized.num_extra_channels, full_frame: full_frame)] pub ec_blending_info: Vec, #[coder(u2S(0, 1, Bits(8), Bits(32)))] #[default(0)] #[condition((frame_type == FrameType::RegularFrame || frame_type == FrameType::SkipProgressive) && nonserialized.have_animation)] pub duration: u32, #[coder(Bits(32))] #[default(0)] #[condition((frame_type == FrameType::RegularFrame || frame_type == FrameType::SkipProgressive) && nonserialized.have_timecode)] timecode: u32, #[default(frame_type == FrameType::RegularFrame)] #[condition(frame_type == FrameType::RegularFrame || frame_type == FrameType::SkipProgressive)] pub is_last: bool, #[coder(Bits(2))] #[default(0)] #[condition(frame_type != FrameType::LFFrame && !is_last)] pub save_as_reference: u32, // The following 2 fields are not actually serialized, but just used as variables to help with // defining later conditions. #[default(!is_last && frame_type != FrameType::LFFrame && (duration == 0 || save_as_reference != 0))] #[condition(false)] pub can_be_referenced: bool, #[default(can_be_referenced && blending_info.mode == BlendingMode::Replace && full_frame && (frame_type == FrameType::RegularFrame || frame_type == FrameType::SkipProgressive))] #[condition(false)] save_before_ct_def_false: bool, #[default(frame_type == FrameType::LFFrame)] #[condition(frame_type == FrameType::ReferenceOnly || save_before_ct_def_false)] pub save_before_ct: bool, pub name: String, #[default(RestorationFilter::default(&field_nonserialized))] #[nonserialized(encoding : encoding)] pub restoration_filter: RestorationFilter, #[default(Extensions::default())] extensions: Extensions, // The following fields are not actually serialized, but just used as variables for // implementing the methods below. #[coder(Bits(0))] #[default(if frame_width == 0 { nonserialized.img_width } else { frame_width })] #[condition(false)] pub width: u32, #[coder(Bits(0))] #[default(if frame_height == 0 { nonserialized.img_height } else { frame_height })] #[condition(false)] pub height: u32, #[coder(Bits(0))] #[default(compute_jpeg_shift(&jpeg_upsampling, &H_SHIFT))] #[condition(false)] pub maxhs: u32, #[coder(Bits(0))] #[default(compute_jpeg_shift(&jpeg_upsampling, &V_SHIFT))] #[condition(false)] pub maxvs: u32, #[coder(Bits(0))] #[default(nonserialized.num_extra_channels)] #[condition(false)] pub num_extra_channels: u32, } impl FrameHeader { pub fn log_group_dim(&self) -> usize { (GROUP_DIM.ilog2() - 1 + self.group_size_shift) as usize } pub fn group_dim(&self) -> usize { 1 << self.log_group_dim() } pub fn lf_group_dim(&self) -> usize { self.group_dim() * BLOCK_DIM } pub fn num_groups(&self) -> usize { self.size_groups().0 * self.size_groups().1 } pub fn num_lf_groups(&self) -> usize { self.size_lf_groups().0 * self.size_lf_groups().1 } pub fn num_toc_entries(&self) -> usize { let num_groups = self.num_groups(); let num_dc_groups = self.num_lf_groups(); if num_groups == 1 && self.passes.num_passes == 1 { 1 } else { 2 + num_dc_groups + num_groups * self.passes.num_passes as usize } } pub fn duration(&self, animation: &Animation) -> f64 { (self.duration as f64) * 1000.0 * (animation.tps_denominator as f64) / (animation.tps_numerator as f64) } pub fn has_patches(&self) -> bool { self.flags & Flags::ENABLE_PATCHES != 0 } pub fn has_noise(&self) -> bool { self.flags & Flags::ENABLE_NOISE != 0 } pub fn has_splines(&self) -> bool { self.flags & Flags::ENABLE_SPLINES != 0 } pub fn has_lf_frame(&self) -> bool { self.flags & Flags::USE_LF_FRAME != 0 } pub fn should_do_adaptive_lf_smoothing(&self) -> bool { self.flags & Flags::SKIP_ADAPTIVE_LF_SMOOTHING == 0 && !self.has_lf_frame() && self.encoding == Encoding::VarDCT } pub fn raw_hshift(&self, c: usize) -> usize { H_SHIFT[self.jpeg_upsampling[c] as usize] } pub fn hshift(&self, c: usize) -> usize { (self.maxhs as usize) - self.raw_hshift(c) } pub fn raw_vshift(&self, c: usize) -> usize { V_SHIFT[self.jpeg_upsampling[c] as usize] } pub fn vshift(&self, c: usize) -> usize { (self.maxvs as usize) - self.raw_vshift(c) } pub fn is444(&self) -> bool { self.hshift(0) == 0 && self.vshift(0) == 0 && // Cb self.hshift(2) == 0 && self.vshift(2) == 0 && // Cr self.hshift(1) == 0 && self.vshift(1) == 0 // Y } pub fn is420(&self) -> bool { self.hshift(0) == 1 && self.vshift(0) == 1 && // Cb self.hshift(2) == 1 && self.vshift(2) == 1 && // Cr self.hshift(1) == 0 && self.vshift(1) == 0 // Y } pub fn is422(&self) -> bool { self.hshift(0) == 1 && self.vshift(0) == 0 && // Cb self.hshift(2) == 1 && self.vshift(2) == 0 && // Cr self.hshift(1) == 0 && self.vshift(1) == 0 // Y } pub fn is440(&self) -> bool { self.hshift(0) == 0 && self.vshift(0) == 1 && // Cb self.hshift(2) == 0 && self.vshift(2) == 1 && // Cr self.hshift(1) == 0 && self.vshift(1) == 0 // Y } pub fn is_visible(&self) -> bool { (self.is_last || self.duration > 0) && (self.frame_type == FrameType::RegularFrame || self.frame_type == FrameType::SkipProgressive) } pub fn needs_blending(&self) -> bool { if !(self.frame_type == FrameType::RegularFrame || self.frame_type == FrameType::SkipProgressive) { return false; } let replace_all = self.blending_info.mode == BlendingMode::Replace && self .ec_blending_info .iter() .all(|x| x.mode == BlendingMode::Replace); self.have_crop || !replace_all } /// The dimensions of this frame, as coded in the codestream, excluding padding pixels. pub fn size(&self) -> (usize, usize) { let (width, height) = self.size_upsampled(); ( width.div_ceil(self.upsampling as usize), height.div_ceil(self.upsampling as usize), ) } /// The dimensions of this frame, as coded in the codestream, in 8x8 blocks. pub fn size_blocks(&self) -> (usize, usize) { ( self.size().0.div_ceil(BLOCK_DIM << self.maxhs) << self.maxhs, self.size().1.div_ceil(BLOCK_DIM << self.maxvs) << self.maxvs, ) } /// The dimensions of this frame, as coded in the codestream but including padding pixels. pub fn size_padded(&self) -> (usize, usize) { if self.encoding == Encoding::Modular { self.size() } else { ( self.size_blocks().0 * BLOCK_DIM, self.size_blocks().1 * BLOCK_DIM, ) } } /// The dimensions of this frame, after upsampling. pub fn size_upsampled(&self) -> (usize, usize) { ( self.width.div_ceil(1 << (3 * self.lf_level)) as usize, self.height.div_ceil(1 << (3 * self.lf_level)) as usize, ) } pub fn size_padded_upsampled(&self) -> (usize, usize) { let (xsize, ysize) = self.size_padded(); ( xsize * self.upsampling as usize, ysize * self.upsampling as usize, ) } /// The dimensions of this frame, in groups. pub fn size_groups(&self) -> (usize, usize) { ( self.size().0.div_ceil(self.group_dim()), self.size().1.div_ceil(self.group_dim()), ) } /// The dimensions of this frame, in LF groups. pub fn size_lf_groups(&self) -> (usize, usize) { ( self.size_blocks().0.div_ceil(self.group_dim()), self.size_blocks().1.div_ceil(self.group_dim()), ) } pub fn block_group_rect(&self, group: usize) -> Rect { let group_dims = self.size_groups(); let block_dims = self.size_blocks(); let group_dim_in_blocks = self.group_dim() >> 3; let gx = group % group_dims.0; let gy = group / group_dims.0; let origin = (gx * group_dim_in_blocks, gy * group_dim_in_blocks); let size = ( min( block_dims.0.checked_sub(origin.0).unwrap(), group_dim_in_blocks, ), min( block_dims.1.checked_sub(origin.1).unwrap(), group_dim_in_blocks, ), ); Rect { origin, size } } pub fn lf_group_rect(&self, group: usize) -> Rect { let lf_dims = self.size_lf_groups(); let block_dims = self.size_blocks(); let gx = group % lf_dims.0; let gy = group / lf_dims.0; let origin = (gx * self.group_dim(), gy * self.group_dim()); let size = ( min( block_dims.0.checked_sub(origin.0).unwrap(), self.group_dim(), ), min( block_dims.1.checked_sub(origin.1).unwrap(), self.group_dim(), ), ); Rect { origin, size } } pub fn postprocess(&mut self, nonserialized: &FrameHeaderNonserialized) { if self.upsampling > 1 { for i in 0..nonserialized.extra_channel_info.len() { let dim_shift = nonserialized.extra_channel_info[i].dim_shift(); self.ec_upsampling[i] <<= dim_shift; } } if self.encoding != Encoding::VarDCT || !nonserialized.xyb_encoded { self.x_qm_scale = 2; } } fn check(&self, nonserialized: &FrameHeaderNonserialized) -> Result<(), Error> { if self.upsampling > 1 && let Some((info, upsampling)) = nonserialized .extra_channel_info .iter() .zip(&self.ec_upsampling) .find(|(info, ec_upsampling)| { ((*ec_upsampling << info.dim_shift()) < self.upsampling) || (**ec_upsampling > 8) }) { return Err(Error::InvalidEcUpsampling( self.upsampling, info.dim_shift(), *upsampling, )); } let num_extra_channels = nonserialized.extra_channel_info.len(); let uses_alpha = |mode| matches!(mode, BlendingMode::Blend | BlendingMode::AlphaWeightedAdd); debug_assert_eq!(self.ec_blending_info.len(), num_extra_channels); if num_extra_channels > 0 && uses_alpha(self.blending_info.mode) && self.blending_info.alpha_channel as usize >= num_extra_channels { return Err(Error::InvalidBlendingAlphaChannel( self.blending_info.alpha_channel as usize, num_extra_channels, )); } for info in &self.ec_blending_info { if num_extra_channels > 0 && uses_alpha(info.mode) && info.alpha_channel as usize >= num_extra_channels { return Err(Error::InvalidBlendingAlphaChannel( info.alpha_channel as usize, num_extra_channels, )); } } if self.has_lf_frame() && self.lf_level >= 4 { return Err(Error::InvalidLfLevel(self.lf_level)); } if self.passes.num_ds >= self.passes.num_passes { return Err(Error::NumPassesTooLarge( self.passes.num_ds, self.passes.num_passes, )); } for w in self.passes.downsample.windows(2) { let [last_ds, ds] = w else { unreachable!() }; if ds >= last_ds { return Err(Error::PassesDownsampleNonDecreasing); } } for w in self.passes.last_pass.windows(2) { let [last_lp, lp] = w else { unreachable!() }; if lp <= last_lp { return Err(Error::PassesLastPassNonIncreasing); } } for &lp in self.passes.last_pass.iter() { if lp >= self.passes.num_passes { return Err(Error::PassesLastPassTooLarge); } } if !self.save_before_ct && !self.full_frame && self.frame_type == FrameType::ReferenceOnly { return Err(Error::NonPatchReferenceWithCrop); } if !self.is444() && ((self.flags & Flags::SKIP_ADAPTIVE_LF_SMOOTHING) == 0) && self.encoding == Encoding::VarDCT { return Err(Error::Non444ChromaSubsampling); } Ok(()) } } #[cfg(test)] mod test_frame_header { use super::super::permutation::Permutation; use super::super::toc::Toc; use super::*; use crate::util::test::read_headers_and_toc; use test_log::test; #[test] fn test_basic() { let (_, frame_header, toc) = read_headers_and_toc(include_bytes!("../../resources/test/basic.jxl")).unwrap(); assert_eq!(frame_header.frame_type, FrameType::RegularFrame); assert_eq!(frame_header.encoding, Encoding::VarDCT); assert_eq!(frame_header.flags, 0); assert_eq!(frame_header.upsampling, 1); assert_eq!(frame_header.x_qm_scale, 2); assert_eq!(frame_header.b_qm_scale, 2); assert!(!frame_header.have_crop); assert!(!frame_header.save_before_ct); assert_eq!(frame_header.name, String::from("")); assert_eq!(frame_header.restoration_filter.epf_iters, 1); assert_eq!( toc, Toc { permuted: false, permutation: Permutation::default(), entries: [53].to_vec(), } ) } #[test] fn test_extra_channel() { let frame_header = read_headers_and_toc(include_bytes!("../../resources/test/extra_channels.jxl")) .unwrap() .1; assert_eq!(frame_header.frame_type, FrameType::RegularFrame); assert_eq!(frame_header.encoding, Encoding::Modular); assert_eq!(frame_header.flags, 0); assert_eq!(frame_header.upsampling, 1); assert_eq!(frame_header.ec_upsampling, vec![1]); // libjxl x_qm_scale = 2, but condition is false (should be 3 according to the draft) // Doesn't actually matter since this is modular mode and the value doesn't get used. assert_eq!(frame_header.x_qm_scale, 3); assert_eq!(frame_header.b_qm_scale, 2); assert!(!frame_header.have_crop); assert!(!frame_header.save_before_ct); assert_eq!(frame_header.name, String::from("")); assert_eq!(frame_header.restoration_filter.epf_iters, 0); assert!(!frame_header.restoration_filter.gab); } #[test] fn test_invalid_blending_alpha_channel() { let (file_header, mut frame_header, _) = read_headers_and_toc(include_bytes!("../../resources/test/extra_channels.jxl")) .unwrap(); let nonserialized = file_header.frame_header_nonserialized(); frame_header.blending_info.mode = BlendingMode::Blend; frame_header.blending_info.alpha_channel = nonserialized.extra_channel_info.len() as u32; let err = frame_header.check(&nonserialized).unwrap_err(); assert!(matches!(err, Error::InvalidBlendingAlphaChannel(_, _))); } #[test] fn test_has_permutation() { let (_, frame_header, toc) = read_headers_and_toc(include_bytes!("../../resources/test/has_permutation.jxl")) .unwrap(); assert_eq!(frame_header.frame_type, FrameType::RegularFrame); assert_eq!(frame_header.encoding, Encoding::VarDCT); assert_eq!(frame_header.flags, 0); assert_eq!(frame_header.upsampling, 1); assert_eq!(frame_header.x_qm_scale, 3); assert_eq!(frame_header.b_qm_scale, 2); assert!(!frame_header.have_crop); assert!(!frame_header.save_before_ct); assert_eq!(frame_header.name, String::from("")); assert_eq!(frame_header.restoration_filter.epf_iters, 1); assert_eq!( toc, Toc { permuted: true, permutation: Permutation(std::borrow::Cow::Owned(vec![ 0u32, 1, 42, 48, 2, 3, 4, 5, 6, 7, 8, 9, 43, 10, 11, 12, 13, 14, 15, 16, 17, 44, 18, 19, 20, 21, 22, 23, 24, 25, 45, 26, 27, 28, 29, 30, 31, 32, 33, 46, 34, 35, 36, 37, 38, 39, 40, 41, 47, ])), entries: vec![ 155, 992, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 697, 5, 5, 5, 5, 5, 60, ], }, ) } }