// 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. use std::{ collections::{HashSet, VecDeque}, io::IoSliceMut, }; use crate::{ api::{ JxlColorProfile, JxlDecoderOptions, JxlOutputBuffer, JxlPixelFormat, inner::{ CodestreamParser, box_parser::CodestreamInput, codestream_parser::{ProcessMode, check_size_limit, validate_output_buffers}, process::SmallBuffer, }, }, bit_reader::BitReader, error::{Error, Result}, frame::{DecoderState, Frame, Section}, headers::{ FileHeader, encodings::UnconditionalCoder, frame_header::{Encoding, FrameHeader, FrameType}, toc::{IncrementalTocReader, Toc}, }, util::NewWithCapacity, }; struct SectionBuffer { len: usize, data: Vec, section: Section, } #[derive(Debug)] struct SectionState { lf_global_done: bool, remaining_lf: usize, hf_global_done: bool, completed_passes: Vec, lf_global_flush_len: usize, } impl SectionState { fn new(num_lf_groups: usize, num_groups: usize) -> Self { Self { lf_global_done: false, remaining_lf: num_lf_groups, hf_global_done: false, completed_passes: vec![0; num_groups], lf_global_flush_len: 0, } } } pub struct FrameInfo { frame_header: Option, toc_parser: Option, frame: Option, // Keeps track of whether pixels have been modified. pixels_dirty: bool, // Section information. sections: VecDeque, section_size: usize, ready_section_data: usize, section_state: SectionState, // Or only section if in single section special case. lf_global_section: Option, lf_sections: Vec, hf_global_section: Option, // indexed by group, then by pass. hf_sections: Vec>>, // group indices that *might* have new renderable data. candidate_hf_sections: HashSet, #[cfg(test)] pub use_simple_pipeline: bool, } impl FrameInfo { pub fn new() -> Self { Self { frame_header: None, toc_parser: None, frame: None, sections: VecDeque::new(), section_size: 0, ready_section_data: 0, section_state: SectionState::new(0, 0), lf_global_section: None, lf_sections: vec![], hf_global_section: None, hf_sections: vec![], candidate_hf_sections: HashSet::new(), pixels_dirty: false, #[cfg(test)] use_simple_pipeline: false, } } pub fn clear(&mut self, clear_frame: bool) { self.frame_header = None; self.toc_parser = None; self.ready_section_data = 0; if clear_frame { self.frame = None; } // Clear sections self.sections.clear(); self.section_state = SectionState::new(0, 0); self.lf_global_section = None; self.lf_sections.clear(); self.hf_global_section = None; self.hf_sections.clear(); self.candidate_hf_sections.clear(); } pub fn current_frame_header(&self) -> Option<&FrameHeader> { self.frame_header .as_ref() .or(self.frame.as_ref().map(|x| x.header())) } pub fn parse_frame_header( &mut self, is_preview: bool, decode_options: &JxlDecoderOptions, file_header: &FileHeader, br: &mut BitReader, bits: &mut usize, ) -> Result<()> { // For preview frames, use the preview dimensions instead of main image dimensions let nonserialized = if is_preview { file_header .preview_frame_header_nonserialized() .unwrap_or_else(|| file_header.frame_header_nonserialized()) } else { file_header.frame_header_nonserialized() }; let mut frame_header = FrameHeader::read_unconditional(&(), br, &nonserialized)?; frame_header.postprocess(&nonserialized); check_size_limit( decode_options.sample_limit, frame_header.size(), frame_header.num_extra_channels as usize, )?; self.frame_header = Some(frame_header); *bits = br.total_bits_read(); Ok(()) } pub fn parse_toc(&mut self, br: &mut BitReader, bits: &mut usize) -> Result { if self.toc_parser.is_none() { let num_toc_entries = self.frame_header.as_ref().unwrap().num_toc_entries(); self.toc_parser = Some(IncrementalTocReader::new(num_toc_entries as u32, br)?); } let toc_parser = self.toc_parser.as_mut().unwrap(); *bits = br.total_bits_read(); while !toc_parser.is_complete() { match toc_parser.read_step(br) { Ok(()) => *bits = br.total_bits_read(), Err(Error::OutOfBounds(c)) => { // Estimate >= 16 bits per remaining entry to read. return Err(Error::OutOfBounds( c + toc_parser.remaining_entries() as usize * 2, )); } Err(e) => return Err(e), } } br.jump_to_byte_boundary()?; *bits = br.total_bits_read(); Ok(self.toc_parser.take().unwrap().finalize()) } #[allow(clippy::too_many_arguments)] pub(super) fn make_frame( &mut self, cbuf: &mut SmallBuffer, toc: Toc, file_header: &FileHeader, decode_options: &JxlDecoderOptions, pixel_format: &JxlPixelFormat, output_profile: &JxlColorProfile, process_mode: ProcessMode, ) -> Result<()> { self.section_size = toc.entries.iter().map(|x| *x as usize).sum(); self.ready_section_data = 0; self.lf_global_section = None; self.lf_sections.clear(); self.hf_global_section = None; self.candidate_hf_sections.clear(); self.hf_sections.clear(); if !matches!(process_mode, ProcessMode::Skip(_)) { // If we have a previous frame, compute the decoder state from there. // Otherwise, compute a new decoder state. // We finalize the previous frame here to allow progressive rendering // to work properly if a flush is requested while we parse a frame // header. let decoder_state = self .frame .take() .map(|x| x.finalize()) .transpose()? .flatten() .unwrap_or_else(|| DecoderState::new(file_header.clone(), decode_options)); let mut frame = Frame::from_header_and_toc(self.frame_header.take().unwrap(), toc, decoder_state)?; let num_groups = frame.header().num_groups(); let num_passes = frame.header().passes.num_passes as usize; let mut hf_sections = Vec::new_with_capacity(num_groups)?; for _ in 0..num_groups { let mut row = Vec::new_with_capacity(num_passes)?; for _ in 0..num_passes { row.push(None); } hf_sections.push(row); } self.hf_sections = hf_sections; let mut sections: Vec<_> = frame .toc() .entries .iter() .map(|x| SectionBuffer { len: *x as usize, data: vec![], section: Section::LfGlobal, // will be fixed later }) .collect(); let order = if frame.toc().permuted { frame.toc().permutation.0.clone() } else { (0..sections.len() as u32).collect() }; if sections.len() > 1 { let base_sections = [Section::LfGlobal, Section::HfGlobal]; let lf_sections = (0..frame.header().num_lf_groups()).map(|x| Section::Lf { group: x }); let hf_sections = (0..frame.header().passes.num_passes).flat_map(|p| { (0..frame.header().num_groups()).map(move |g| Section::Hf { group: g, pass: p as usize, }) }); for section in base_sections .into_iter() .chain(lf_sections) .chain(hf_sections) { sections[order[frame.get_section_idx(section)] as usize].section = section; } } self.sections = sections.into_iter().collect(); // Move data from the pre-section buffer into the sections. for buf in self.sections.iter_mut() { if cbuf.is_empty() { break; } let mut data = Vec::new(); data.try_reserve_exact(buf.len)?; data.resize(buf.len, 0); buf.data = data; let n = cbuf.take(&mut [IoSliceMut::new(&mut buf.data)]); self.ready_section_data += n; } self.section_state = SectionState::new(frame.header().num_lf_groups(), frame.header().num_groups()); frame.prepare_render_pipeline(pixel_format, output_profile)?; self.frame = Some(frame); } else { let num = cbuf.len().min(self.section_size); cbuf.consume(num); self.ready_section_data += num; } Ok(()) } #[cfg(test)] pub fn frame(&mut self) -> Option<&mut Frame> { self.frame.as_mut() } pub fn dequeue_ready_sections(&mut self) { // Dequeue ready sections. while self .sections .front() .is_some_and(|s| s.len <= self.ready_section_data) { let s = self.sections.pop_front().unwrap(); self.ready_section_data -= s.len; match s.section { Section::LfGlobal => { self.lf_global_section = Some(s); } Section::HfGlobal => { self.hf_global_section = Some(s); } Section::Lf { .. } => { self.lf_sections.push(s); } Section::Hf { group, pass } => { self.hf_sections[group][pass] = Some(s); self.candidate_hf_sections.insert(group); } } } } pub fn skip_sections( &mut self, input: &mut CodestreamInput, buf: &mut SmallBuffer, ) -> Result<()> { let total_size = self.section_size; let need_skip = total_size - self.ready_section_data; let skipped = input.skip(need_skip)?; buf.mark_consumed(skipped as u64); self.ready_section_data += skipped; if self.ready_section_data < total_size { Err(Error::OutOfBounds(total_size - self.ready_section_data)) } else { self.sections.clear(); Ok(()) } } pub fn fill_sections( &mut self, input: &mut CodestreamInput, buf: &mut SmallBuffer, ) -> Result<()> { // TODO(veluca): consider re-using `section_buffers` between different iterations. let mut total = 0; loop { let available_codestream = input.available_bytes(); let mut available_codestream = match (available_codestream, total) { (Ok(a), _) => a.max(1), (Err(Error::OutOfBounds(_)), t) if t > 0 => return Ok(()), (Err(e), _) => return Err(e), }; let mut readable_section_data = self.ready_section_data + available_codestream; // Ensure enough section buffers are available for reading available data. for buf in self.sections.iter_mut() { if buf.data.is_empty() { buf.data.resize(buf.len, 0); } readable_section_data = readable_section_data.saturating_sub(buf.data.len()); if readable_section_data == 0 { break; } } // Prepare buffers to read into. let mut section_buffers = vec![]; let mut ready = self.ready_section_data; for buf in self.sections.iter_mut() { if buf.data.is_empty() && buf.len != 0 { break; } let len = buf.data.len(); if len > ready { let readable = (available_codestream + ready).min(len); section_buffers.push(IoSliceMut::new(&mut buf.data[ready..readable])); available_codestream = available_codestream.saturating_sub(readable - ready); if available_codestream == 0 { break; } } ready = ready.saturating_sub(len); } let num = input.read(&mut section_buffers[..])?; if num == 0 { break; } self.ready_section_data += num; total += num; buf.mark_consumed(num as u64); self.dequeue_ready_sections(); } Ok(()) } // Returns whether we modified the pixels. fn process_single_section( frame: &mut Frame, buf: &[u8], is_complete: bool, output_buffers: &mut Option<&mut [JxlOutputBuffer<'_>]>, output_profile: &JxlColorProfile, pixel_format: &JxlPixelFormat, do_flush: bool, ) -> Result { let mut br = BitReader::new(buf); frame.decode_lf_global(&mut br, !is_complete)?; frame.decode_lf_group(0, &mut br)?; frame.decode_hf_global(&mut br)?; frame.finalize_lf()?; frame.decode_and_render_hf_groups( output_buffers, pixel_format, vec![(0, vec![(0, br)])], do_flush, output_profile, ) } // Returns: // Ok(None) if the frame is complete // Ok(Some(len)) if we need `len` bytes to make progress // Err(_) if there was an error. pub fn process_sections( &mut self, output_buffers: &mut Option<&mut [JxlOutputBuffer<'_>]>, output_profile: &JxlColorProfile, pixel_format: &JxlPixelFormat, ) -> Result> { let data_for_next_section = self .sections .front() .map(|x| x.len - self.ready_section_data); let frame = self.frame.as_mut().unwrap(); let frame_header = frame.header(); if frame_header.num_groups() == 1 && frame_header.passes.num_passes == 1 { // Single-group special case. let Some(buf) = self.lf_global_section.take() else { return Ok(data_for_next_section); }; assert!(self.sections.is_empty()); self.pixels_dirty |= Self::process_single_section( frame, &buf.data, true, output_buffers, output_profile, pixel_format, false, )?; return Ok(None); } if let Some(buf) = self.lf_global_section.take() { frame.decode_lf_global(&mut BitReader::new(&buf.data), false)?; self.section_state.lf_global_done = true; } if !self.section_state.lf_global_done { return Ok(data_for_next_section); } for lf_section in self.lf_sections.drain(..) { let Section::Lf { group } = lf_section.section else { unreachable!() }; frame.decode_lf_group(group, &mut BitReader::new(&lf_section.data))?; self.section_state.remaining_lf -= 1; } if self.section_state.remaining_lf != 0 { return Ok(data_for_next_section); } if let Some(hf_global) = self.hf_global_section.take() { frame.decode_hf_global(&mut BitReader::new(&hf_global.data))?; frame.finalize_lf()?; self.section_state.hf_global_done = true; } if !self.section_state.hf_global_done { return Ok(data_for_next_section); } let mut group_readers = vec![]; let mut processed_groups = vec![]; let mut check_group = |g: usize| { let mut sections = vec![]; for (pass, grp) in self.hf_sections[g] .iter() .enumerate() .skip(self.section_state.completed_passes[g] as usize) { let Some(s) = &grp else { break; }; self.section_state.completed_passes[g] += 1; sections.push((pass, BitReader::new(&s.data))); } if !sections.is_empty() { group_readers.push((g, sections)); processed_groups.push(g); } }; if self.candidate_hf_sections.len() * 4 < self.hf_sections.len() { for g in self.candidate_hf_sections.drain() { check_group(g) } // Processing sections in order is more efficient because it lets us flush // the pipeline faster. group_readers.sort_by_key(|x| x.0); } else { for g in 0..self.hf_sections.len() { if self.candidate_hf_sections.contains(&g) { check_group(g); } } self.candidate_hf_sections.clear(); } self.pixels_dirty |= frame.decode_and_render_hf_groups( output_buffers, pixel_format, group_readers, false, output_profile, )?; for g in processed_groups.into_iter() { for i in 0..self.section_state.completed_passes[g] { self.hf_sections[g][i as usize] = None; } } Ok(data_for_next_section) } pub fn do_flush( &mut self, output_buffers: &mut [JxlOutputBuffer<'_>], output_profile: &JxlColorProfile, pixel_format: &JxlPixelFormat, ) -> Result<()> { let Some(frame) = self.frame.as_mut() else { return Ok(()); }; validate_output_buffers(output_buffers, Some(pixel_format))?; let frame_header = frame.header(); let has_partial_lf = self .sections .front() .is_some_and(|s| s.section == Section::LfGlobal) && 2 * self.ready_section_data > 3 * self.section_state.lf_global_flush_len && frame_header.encoding == Encoding::Modular && matches!( frame_header.frame_type, FrameType::RegularFrame | FrameType::LFFrame ); if has_partial_lf { self.section_state.lf_global_flush_len = self.ready_section_data; let section = &self.sections[0].data[..self.ready_section_data]; // These are partial renders, so we ignore any errors. if frame_header.num_groups() == 1 && frame_header.passes.num_passes == 1 { if let Ok(dirty) = Self::process_single_section( frame, section, false, &mut Some(output_buffers), output_profile, pixel_format, true, ) { self.pixels_dirty |= dirty; return Ok(()); } } else { let _ = frame.decode_lf_global(&mut BitReader::new(section), true); } } if frame.can_do_early_rendering() || self.section_state.hf_global_done { self.pixels_dirty |= frame.decode_and_render_hf_groups( &mut Some(output_buffers), pixel_format, vec![], true, output_profile, )?; } Ok(()) } } impl CodestreamParser { pub fn get_and_clear_pixels_dirty(&mut self) -> bool { let r = self.frame_info.pixels_dirty; self.frame_info.pixels_dirty = false; r } }