jxl 0.1.4

High performance Rust implementation of a JPEG XL decoder
Documentation
// 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::io::IoSliceMut;

use crate::{
    api::{
        Endianness, JxlBasicInfo, JxlBitDepth, JxlColorEncoding, JxlColorProfile, JxlColorType,
        JxlDataFormat, JxlDecoderOptions, JxlExtraChannel, JxlPixelFormat, JxlTransferFunction,
        inner::codestream_parser::SectionState,
    },
    bit_reader::BitReader,
    error::{Error, Result},
    frame::{DecoderState, Frame, Section},
    headers::{
        FileHeader, JxlHeader, color_encoding::ColorSpace, encodings::UnconditionalCoder,
        frame_header::FrameHeader, toc::IncrementalTocReader,
    },
    icc::IncrementalIccReader,
};

use super::{CodestreamParser, SectionBuffer};
use crate::api::ToneMapping;

impl CodestreamParser {
    #[cold]
    pub(super) fn process_non_section(&mut self, decode_options: &JxlDecoderOptions) -> Result<()> {
        if self.decoder_state.is_none() && self.file_header.is_none() {
            // We don't have a file header yet. Try parsing that.
            // TODO(veluca): make this incremental, as a file header might be multiple megabytes.
            let mut br = BitReader::new(&self.non_section_buf);
            br.skip_bits(self.non_section_bit_offset as usize)?;
            let file_header = FileHeader::read(&mut br)?;
            let data = &file_header.image_metadata;
            self.animation = data.animation.clone();
            self.basic_info = Some(JxlBasicInfo {
                size: if data.orientation.is_transposing() {
                    (
                        file_header.size.ysize() as usize,
                        file_header.size.xsize() as usize,
                    )
                } else {
                    (
                        file_header.size.xsize() as usize,
                        file_header.size.ysize() as usize,
                    )
                },
                bit_depth: if data.bit_depth.floating_point_sample() {
                    JxlBitDepth::Float {
                        bits_per_sample: data.bit_depth.bits_per_sample(),
                        exponent_bits_per_sample: data.bit_depth.exponent_bits_per_sample(),
                    }
                } else {
                    JxlBitDepth::Int {
                        bits_per_sample: data.bit_depth.bits_per_sample(),
                    }
                },
                orientation: data.orientation,
                extra_channels: data
                    .extra_channel_info
                    .iter()
                    .map(|info| JxlExtraChannel {
                        ec_type: info.ec_type,
                        alpha_associated: info.alpha_associated(),
                    })
                    .collect(),
                animation: data
                    .animation
                    .as_ref()
                    .map(|anim| crate::api::JxlAnimation {
                        tps_numerator: anim.tps_numerator,
                        tps_denominator: anim.tps_denominator,
                        num_loops: anim.num_loops,
                        have_timecodes: anim.have_timecodes,
                    }),
                uses_original_profile: !data.xyb_encoded,
                tone_mapping: ToneMapping {
                    intensity_target: data.tone_mapping.intensity_target,
                    min_nits: data.tone_mapping.min_nits,
                    relative_to_max_display: data.tone_mapping.relative_to_max_display,
                    linear_below: data.tone_mapping.linear_below,
                },
                preview_size: data
                    .preview
                    .as_ref()
                    .map(|p| (p.xsize() as usize, p.ysize() as usize)),
            });
            self.file_header = Some(file_header);
            let bits = br.total_bits_read();
            self.non_section_buf.consume(bits / 8);
            self.non_section_bit_offset = (bits % 8) as u8;
        }

        if self.decoder_state.is_none() && self.embedded_color_profile.is_none() {
            let file_header = self.file_header.as_ref().unwrap();
            // Parse (or extract from file header) the ICC profile.
            let mut br = BitReader::new(&self.non_section_buf);
            br.skip_bits(self.non_section_bit_offset as usize)?;
            let embedded_color_profile = if file_header.image_metadata.color_encoding.want_icc {
                if self.icc_parser.is_none() {
                    self.icc_parser = Some(IncrementalIccReader::new(&mut br)?);
                }
                let icc_parser = self.icc_parser.as_mut().unwrap();
                let mut bits = br.total_bits_read();
                for _ in 0..icc_parser.remaining() {
                    match icc_parser.read_one(&mut br) {
                        Ok(()) => bits = br.total_bits_read(),
                        Err(Error::OutOfBounds(c)) => {
                            self.non_section_buf.consume(bits / 8);
                            self.non_section_bit_offset = (bits % 8) as u8;
                            // Estimate >= one bit per remaining character to read.
                            return Err(Error::OutOfBounds(c + icc_parser.remaining() / 8));
                        }
                        Err(e) => return Err(e),
                    }
                }
                let icc_result = self.icc_parser.take().unwrap().finalize(&mut br);
                self.non_section_buf.consume(bits / 8);
                self.non_section_bit_offset = (bits % 8) as u8;
                JxlColorProfile::Icc(icc_result?)
            } else {
                JxlColorProfile::Simple(JxlColorEncoding::from_internal(
                    &file_header.image_metadata.color_encoding,
                )?)
            };
            let output_color_profile = if file_header.image_metadata.xyb_encoded {
                let nonlinear_output_color_profile = match &embedded_color_profile {
                    JxlColorProfile::Icc(_) => JxlColorEncoding::srgb(
                        file_header.image_metadata.color_encoding.color_space == ColorSpace::Gray,
                    ),
                    JxlColorProfile::Simple(encoding) => encoding.clone(),
                };
                JxlColorProfile::Simple(if decode_options.xyb_output_linear {
                    match nonlinear_output_color_profile {
                        JxlColorEncoding::RgbColorSpace {
                            white_point,
                            primaries,
                            transfer_function: _,
                            rendering_intent,
                        } => JxlColorEncoding::RgbColorSpace {
                            white_point,
                            primaries,
                            transfer_function: JxlTransferFunction::Linear,
                            rendering_intent,
                        },
                        JxlColorEncoding::GrayscaleColorSpace {
                            white_point,
                            transfer_function: _,
                            rendering_intent,
                        } => JxlColorEncoding::GrayscaleColorSpace {
                            white_point,
                            transfer_function: JxlTransferFunction::Linear,
                            rendering_intent,
                        },
                        JxlColorEncoding::XYB { .. } => unreachable!(),
                    }
                } else {
                    nonlinear_output_color_profile
                })
            } else {
                embedded_color_profile.clone()
            };
            self.embedded_color_profile = Some(embedded_color_profile);
            self.output_color_profile = Some(output_color_profile);
            self.pixel_format = Some(JxlPixelFormat {
                color_type: if file_header.image_metadata.color_encoding.color_space
                    == ColorSpace::Gray
                {
                    JxlColorType::Grayscale
                } else {
                    JxlColorType::Rgb
                },
                color_data_format: Some(JxlDataFormat::F32 {
                    endianness: Endianness::native(),
                }),
                extra_channel_format: vec![
                    Some(JxlDataFormat::F32 {
                        endianness: Endianness::native()
                    });
                    file_header.image_metadata.extra_channel_info.len()
                ],
            });

            let mut br = BitReader::new(&self.non_section_buf);
            br.skip_bits(self.non_section_bit_offset as usize)?;
            br.jump_to_byte_boundary()?;
            self.non_section_buf.consume(br.total_bits_read() / 8);

            // We now have image information.
            let mut decoder_state = DecoderState::new(self.file_header.take().unwrap());
            decoder_state.xyb_output_linear = decode_options.xyb_output_linear;
            decoder_state.render_spotcolors = decode_options.render_spot_colors;
            decoder_state.high_precision = decode_options.high_precision;
            self.decoder_state = Some(decoder_state);
            // Reset bit offset to 0 since we've consumed everything up to a byte boundary
            self.non_section_bit_offset = 0;
            return Ok(());
        }

        let decoder_state = self.decoder_state.as_mut().unwrap();

        if self.frame_header.is_none() {
            // We don't have a frame header yet. Try parsing that.
            // TODO(veluca): do we need to make this incremental?
            let mut br = BitReader::new(&self.non_section_buf);
            br.skip_bits(self.non_section_bit_offset as usize)?;

            // For preview frames, use the preview dimensions instead of main image dimensions
            let nonserialized = if !self.preview_done {
                decoder_state
                    .file_header
                    .preview_frame_header_nonserialized()
                    .unwrap_or_else(|| decoder_state.file_header.frame_header_nonserialized())
            } else {
                decoder_state.file_header.frame_header_nonserialized()
            };

            let mut frame_header = FrameHeader::read_unconditional(&(), &mut br, &nonserialized)?;
            frame_header.postprocess(&nonserialized);
            self.frame_header = Some(frame_header);
            let bits = br.total_bits_read();
            self.non_section_buf.consume(bits / 8);
            self.non_section_bit_offset = (bits % 8) as u8;
        }

        let toc = {
            let mut br = BitReader::new(&self.non_section_buf);
            br.skip_bits(self.non_section_bit_offset as usize)?;
            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, &mut br)?);
            }

            let toc_parser = self.toc_parser.as_mut().unwrap();
            let mut bits = br.total_bits_read();
            while !toc_parser.is_complete() {
                match toc_parser.read_step(&mut br) {
                    Ok(()) => bits = br.total_bits_read(),
                    Err(Error::OutOfBounds(c)) => {
                        self.non_section_buf.consume(bits / 8);
                        self.non_section_bit_offset = (bits % 8) as u8;
                        // 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();
            self.non_section_buf.consume(bits / 8);
            self.non_section_bit_offset = (bits % 8) as u8;
            self.toc_parser.take().unwrap().finalize()
        };

        // Save file_header before creating frame (for preview frame recovery)
        self.saved_file_header = self.decoder_state.as_ref().map(|ds| ds.file_header.clone());

        let frame = Frame::from_header_and_toc(
            self.frame_header.take().unwrap(),
            toc,
            self.decoder_state.take().unwrap(),
        )?;

        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();
        self.ready_section_data = 0;

        // Move data from the pre-section buffer into the sections.
        for buf in self.sections.iter_mut() {
            if self.non_section_buf.is_empty() {
                break;
            }
            buf.data = vec![0; buf.len];
            self.ready_section_data += self
                .non_section_buf
                .take(&mut [IoSliceMut::new(&mut buf.data)]);
        }

        self.section_state =
            SectionState::new(frame.header().num_lf_groups(), frame.header().num_groups());
        assert!(self.available_sections.is_empty());

        self.frame = Some(frame);

        Ok(())
    }
}