diff --git a/Cargo.lock b/Cargo.lock index 292c5306..cc3d5f8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,7 +202,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -269,7 +269,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -280,15 +280,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -296,9 +296,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "lazy_static" @@ -308,7 +308,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lepton_jpeg" -version = "0.4.0" +version = "0.4.1" dependencies = [ "bytemuck", "byteorder", @@ -331,9 +331,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "log" @@ -532,7 +532,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.89", + "syn 2.0.90", "unicode-ident", ] @@ -583,7 +583,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -626,9 +626,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -674,9 +674,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -846,5 +846,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index f4480f71..f276d755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lepton_jpeg" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = ["Kristof Roomp "] diff --git a/package/Lepton.Jpeg.Rust.nuspec b/package/Lepton.Jpeg.Rust.nuspec index 8c757662..af634c23 100644 --- a/package/Lepton.Jpeg.Rust.nuspec +++ b/package/Lepton.Jpeg.Rust.nuspec @@ -2,7 +2,7 @@ Lepton.Jpeg.Rust - 0.4.0 + 0.4.1 Lepton JPEG Compression Rust version binaries and libraries kristofr kristofr diff --git a/src/structs/bit_reader.rs b/src/jpeg/bit_reader.rs similarity index 100% rename from src/structs/bit_reader.rs rename to src/jpeg/bit_reader.rs diff --git a/src/structs/bit_writer.rs b/src/jpeg/bit_writer.rs similarity index 99% rename from src/structs/bit_writer.rs rename to src/jpeg/bit_writer.rs index 379c26e1..e080bc2e 100644 --- a/src/structs/bit_writer.rs +++ b/src/jpeg/bit_writer.rs @@ -142,11 +142,11 @@ impl BitWriter { #[cfg(test)] use std::io::Cursor; +#[cfg(test)] +use super::bit_reader::BitReader; use crate::helpers::has_ff; #[cfg(test)] use crate::helpers::u32_bit_length; -#[cfg(test)] -use crate::structs::bit_reader::BitReader; // write a test pattern with an escape and see if it matches #[test] diff --git a/src/structs/block_based_image.rs b/src/jpeg/block_based_image.rs similarity index 95% rename from src/structs/block_based_image.rs rename to src/jpeg/block_based_image.rs index eb79f233..eb3393ed 100644 --- a/src/structs/block_based_image.rs +++ b/src/jpeg/block_based_image.rs @@ -9,8 +9,8 @@ use log::info; use wide::{i16x8, CmpEq}; use crate::consts::ZIGZAG_TO_TRANSPOSED; -use crate::structs::block_context::BlockContext; -use crate::structs::jpeg_header::JPegHeader; + +use super::jpeg_header::JPegHeader; /// holds the 8x8 blocks for a given component. Since we do multithreaded encoding, /// the image may only hold a subset of the components (specified by dpos_offset), @@ -110,16 +110,6 @@ impl BlockBasedImage { ); } - // blocks above the first line are never dereferenced - pub fn off_y(&self, y: u32) -> BlockContext { - return BlockContext::new( - self.block_width * y, - if y > 0 { self.block_width * (y - 1) } else { 0 }, - if (y & 1) != 0 { self.block_width } else { 0 }, - if (y & 1) != 0 { 0 } else { self.block_width }, - ); - } - pub fn get_block_width(&self) -> u32 { self.block_width } diff --git a/src/structs/component_info.rs b/src/jpeg/component_info.rs similarity index 100% rename from src/structs/component_info.rs rename to src/jpeg/component_info.rs diff --git a/src/structs/jpeg_header.rs b/src/jpeg/jpeg_header.rs similarity index 90% rename from src/structs/jpeg_header.rs rename to src/jpeg/jpeg_header.rs index a8e8571e..c65f2eef 100644 --- a/src/structs/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -38,16 +38,96 @@ use std::num::NonZeroU32; use crate::consts::JPegType; use crate::enabled_features::EnabledFeatures; use crate::helpers::*; -use crate::jpeg_code; use crate::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; -use crate::structs::component_info::ComponentInfo; -use crate::structs::lepton_header::LeptonHeader; -use crate::structs::quantization_tables::QuantizationTables; -use crate::structs::truncate_components::TruncateComponents; -use crate::LeptonError; +use crate::{jpeg_code, LeptonError}; + +use super::component_info::ComponentInfo; +use super::truncate_components::TruncateComponents; + +/// Information required to partition the coding the JPEG huffman encoded stream of a scan +/// at an arbitrary location in the stream. +/// +/// Note that this only works for sequential JPEGs since progressive ones have multiple scans +/// that each process the entire image. + +#[derive(Debug, Default, Clone)] +pub struct RestartSegmentCodingInfo { + pub overhang_byte: u8, + pub num_overhang_bits: u8, + pub luma_y_start: u32, + pub luma_y_end: u32, + pub last_dc: [i16; 4], +} + +impl RestartSegmentCodingInfo { + pub fn new( + overhang_byte: u8, + num_overhang_bits: u8, + last_dc: [i16; 4], + mcu: u32, + jf: &JPegHeader, + ) -> Self { + let mcu_y = mcu / jf.mcuh; + let luma_mul = jf.cmp_info[0].bcv / jf.mcuv; + + Self { + overhang_byte, + num_overhang_bits, + last_dc, + luma_y_start: luma_mul * mcu_y, + luma_y_end: luma_mul * (mcu_y + 1), + } + } +} + +/// Global information required to reconstruct the JPEG exactly the way that it was, especially +/// regarding information about possible truncation and RST markers. +#[derive(Default, Clone, Debug)] +pub struct ReconstructionInfo { + /// the maximum component in a truncated progressive image. + /// + /// This is meant to be used for progressive images but is not yet implemented. + pub max_cmp: u32, + + /// the maximum band in a truncated progressive image + /// + /// This is meant to be used for progressive images but is not yet implemented. + pub max_bpos: u32, + + /// The maximum bit in a truncated progressive image. + /// + /// This is meant to be used for progressive images but is not yet implemented. + pub max_sah: u8, + + /// the maximum dpos in a truncated image + pub max_dpos: [u32; 4], + + /// if we encountered EOF before the expected end of the image + pub early_eof_encountered: bool, + + /// the mask for padding out the bitstream when we get to the end of a reset block + pub pad_bit: Option, + + /// A list containing one entry for each scan segment. Each entry contains the number of restart intervals + /// within the corresponding scan segment. + /// + /// TODO: We currently don't generate this value when we parse a JPEG (leaving rst_cnt_set as false), however when + /// we read a Lepton file we will use this to determine whether we should generate restart markers in order + /// to maintain backward compability for decoding Lepton files generated by the C++ version. + /// + /// This means that there might be some files that we could have encoded successfully that we don't, but since + /// we are required to reverify anyway, this is not a problem (except a minor efficiency issue) + pub rst_cnt: Vec, + + /// true if rst_cnt contains a valid set of counts + pub rst_cnt_set: bool, + + /// information about how to truncate the image if it was partially written + pub truncate_components: TruncateComponents, +} #[derive(Copy, Clone, Debug)] -pub struct HuffCodes { +pub(super) struct HuffCodes { pub c_val: [u16; 256], pub c_len: [u16; 256], pub c_len_plus_s: [u8; 256], @@ -155,7 +235,7 @@ impl HuffCodes { } #[derive(Copy, Clone, Debug)] -pub struct HuffTree { +pub(super) struct HuffTree { pub node: [[u16; 2]; 256], pub peek_code: [(u8, u8); 256], } @@ -267,6 +347,7 @@ impl HuffTree { } } +/// JPEG information parsed out of segments found before the image segment #[derive(Debug, Clone)] pub struct JPegHeader { /// quantization tables 4 x 64 @@ -279,7 +360,7 @@ pub struct JPegHeader { h_trees: [[HuffTree; 4]; 2], /// 1 if huffman table is set - pub ht_set: [[u8; 4]; 2], + ht_set: [[u8; 4]; 2], /// components pub cmp_info: [ComponentInfo; 4], @@ -332,37 +413,6 @@ pub struct JPegHeader { /// successive approximation bit pos low pub cs_sal: u8, } - -pub struct JPegEncodingInfo { - pub jpeg_header: JPegHeader, - pub truncate_components: TruncateComponents, - - /// A list containing one entry for each scan segment. Each entry contains the number of restart intervals - /// within the corresponding scan segment. - pub rst_cnt: Vec, - - /// the mask for padding out the bitstream when we get to the end of a reset block - pub pad_bit: Option, - - pub rst_cnt_set: bool, - - /// count of scans encountered so far - pub scnc: usize, -} - -impl JPegEncodingInfo { - pub fn new(lh: &LeptonHeader) -> Self { - JPegEncodingInfo { - jpeg_header: lh.jpeg_header.clone(), - truncate_components: lh.truncate_components.clone(), - rst_cnt: lh.rst_cnt.clone(), - pad_bit: lh.pad_bit, - rst_cnt_set: lh.rst_cnt_set, - scnc: lh.scnc, - } - } -} - enum ParseSegmentResult { Continue, EOI, @@ -404,26 +454,29 @@ impl Default for JPegHeader { impl JPegHeader { #[inline(always)] - pub fn get_huff_dc_codes(&self, cmp: usize) -> &HuffCodes { + pub(super) fn get_huff_dc_codes(&self, cmp: usize) -> &HuffCodes { &self.h_codes[0][usize::from(self.cmp_info[cmp].huff_dc)] } #[inline(always)] - pub fn get_huff_dc_tree(&self, cmp: usize) -> &HuffTree { + pub(super) fn get_huff_dc_tree(&self, cmp: usize) -> &HuffTree { &self.h_trees[0][usize::from(self.cmp_info[cmp].huff_dc)] } #[inline(always)] - pub fn get_huff_ac_codes(&self, cmp: usize) -> &HuffCodes { + pub(super) fn get_huff_ac_codes(&self, cmp: usize) -> &HuffCodes { &self.h_codes[1][usize::from(self.cmp_info[cmp].huff_ac)] } #[inline(always)] - pub fn get_huff_ac_tree(&self, cmp: usize) -> &HuffTree { + pub(super) fn get_huff_ac_tree(&self, cmp: usize) -> &HuffTree { &self.h_trees[1][usize::from(self.cmp_info[cmp].huff_ac)] } - /// Parses header for imageinfo + /// Parses JPEG segments and updates the appropriate header fields + /// until we hit either an SOS (image data) or EOI (end of image). + /// + /// Returns false if we hit EOI, true if we have an image to process. pub fn parse( &mut self, reader: &mut R, @@ -952,27 +1005,6 @@ impl JPegHeader { } return Ok(ParseSegmentResult::Continue); } - - /// constructs the quantization table based on the jpeg header - pub fn construct_quantization_tables(&self) -> Result> { - let mut quantization_tables = Vec::new(); - for i in 0..self.cmpc { - let qtables = QuantizationTables::new(self, i); - - // check to see if quantitization table was properly initialized - // (table contains divisors for edge coefficients so it never should have a zero) - for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 32, 40, 48, 56] { - if qtables.get_quantization_table()[i] == 0 { - return err_exit_code( - ExitCode::UnsupportedJpegWithZeroIdct0, - "Quantization table contains zero for edge which would cause a divide by zero", - ); - } - } - quantization_tables.push(qtables); - } - Ok(quantization_tables) - } } fn ensure_space(segment: &[u8], hpos: usize, amount: usize) -> Result<()> { @@ -985,7 +1017,7 @@ fn ensure_space(segment: &[u8], hpos: usize, amount: usize) -> Result<()> { /// constructs a huffman table for testing purposes from a given distribution #[cfg(test)] -pub fn generate_huff_table_from_distribution(freq: &[usize; 256]) -> HuffCodes { +pub(super) fn generate_huff_table_from_distribution(freq: &[usize; 256]) -> HuffCodes { use std::collections::{BinaryHeap, HashMap}; struct Node { diff --git a/src/structs/jpeg_position_state.rs b/src/jpeg/jpeg_position_state.rs similarity index 99% rename from src/structs/jpeg_position_state.rs rename to src/jpeg/jpeg_position_state.rs index 9e09a91e..0b9babdd 100644 --- a/src/structs/jpeg_position_state.rs +++ b/src/jpeg/jpeg_position_state.rs @@ -8,7 +8,7 @@ use crate::consts::{JPegDecodeStatus, JPegType}; use crate::lepton_error::{err_exit_code, AddContext, ExitCode}; use crate::{LeptonError, Result}; -use crate::structs::jpeg_header::{HuffCodes, JPegHeader}; +use super::jpeg_header::{HuffCodes, JPegHeader}; /// used to keep track of position while encoding or decoding a jpeg pub struct JpegPositionState { diff --git a/src/structs/jpeg_read.rs b/src/jpeg/jpeg_read.rs similarity index 86% rename from src/structs/jpeg_read.rs rename to src/jpeg/jpeg_read.rs index 817f0919..73e84cc1 100644 --- a/src/structs/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -37,44 +37,47 @@ use std::io::{BufRead, Seek}; use crate::consts::*; use crate::helpers::*; -use crate::lepton_error::Result; -use crate::lepton_error::{err_exit_code, AddContext, ExitCode}; -use crate::structs::bit_reader::BitReader; -use crate::structs::block_based_image::{AlignedBlock, BlockBasedImage}; -use crate::structs::jpeg_header::HuffTree; -use crate::structs::jpeg_position_state::JpegPositionState; -use crate::structs::lepton_header::LeptonHeader; -use crate::structs::thread_handoff::ThreadHandoff; - -pub fn read_scan( - lp: &mut LeptonHeader, +use crate::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; + +use super::bit_reader::BitReader; +use super::block_based_image::{AlignedBlock, BlockBasedImage}; +use super::jpeg_header::{HuffTree, JPegHeader, ReconstructionInfo, RestartSegmentCodingInfo}; +use super::jpeg_position_state::JpegPositionState; + +/// Reads the scan from the JPEG file, writes the image data to the image_data array and +/// partitions it into restart segments using the partition callback. +/// +/// This only works for sequential JPEGs or the first scan in a progressive image. +/// For subsequent scans, use the `read_progressive_scan`. +pub fn read_first_scan( + jf: &JPegHeader, reader: &mut R, - thread_handoff: &mut Vec, + partition: &mut FPARTITION, image_data: &mut [BlockBasedImage], + reconstruct_info: &mut ReconstructionInfo, ) -> Result<()> { let mut bit_reader = BitReader::new(reader); // init variables for decoding - let mut state = JpegPositionState::new(&lp.jpeg_header, 0); + let mut state = JpegPositionState::new(jf, 0); let mut do_handoff = true; // JPEG imagedata decoding routines let mut sta = JPegDecodeStatus::DecodeInProgress; while sta != JPegDecodeStatus::ScanCompleted { - let jf = &lp.jpeg_header; - // decoding for interleaved data state.reset_rstw(jf); // restart wait counter if jf.jpeg_type == JPegType::Sequential { sta = decode_baseline_rst( &mut state, - lp, - thread_handoff, + partition, &mut bit_reader, image_data, &mut do_handoff, + jf, + reconstruct_info, ) .context()?; } else if jf.cs_to == 0 && jf.cs_sah == 0 { @@ -86,16 +89,15 @@ pub fn read_scan( while sta == JPegDecodeStatus::DecodeInProgress { let current_block = image_data[state.get_cmp()].get_block_mut(state.get_dpos()); - // first time through, collect the handoffs although for progressive images the offsets - // won't mean much, but we do need to divide the scan into sections - + // collect the handoffs although for progressive images + // we still split the scan into sections, but we don't partition the actual JPEG + // writes since they have to be done on a single thread in a loop for a progressive file. + // + // TODO: get rid of this and just chop up the scan into sections in Lepton code if do_handoff { - crystallize_thread_handoff( - &state, - lp, - &mut bit_reader, - thread_handoff, - last_dc, + partition( + 0, + RestartSegmentCodingInfo::new(0, 0, [0; 4], state.get_mcu(), jf), ); do_handoff = false; @@ -114,7 +116,7 @@ pub fn read_scan( let old_mcu = state.get_mcu(); sta = state.next_mcu_pos(jf); - if state.get_mcu() % lp.jpeg_header.mcuh == 0 && old_mcu != state.get_mcu() { + if state.get_mcu() % jf.mcuh == 0 && old_mcu != state.get_mcu() { do_handoff = true; } } @@ -129,7 +131,7 @@ pub fn read_scan( // if we saw a pad bit at the end of the block, then remember whether they were 1s or 0s. This // will be used later on to reconstruct the padding bit_reader - .read_and_verify_fill_bits(&mut lp.pad_bit) + .read_and_verify_fill_bits(&mut reconstruct_info.pad_bit) .context()?; // verify that we got the right RST code here since the above should do 1 mcu. @@ -140,65 +142,32 @@ pub fn read_scan( sta = JPegDecodeStatus::DecodeInProgress; } } - - lp.scnc += 1; // increment scan counter Ok(()) } -/// stores handoff information in vector for the current position. This should -/// be enough information to independently restart encoding at this offset (at least for baseline images) -fn crystallize_thread_handoff( - state: &JpegPositionState, - lp: &LeptonHeader, - bit_reader: &mut BitReader, - thread_handoff: &mut Vec, - lastdc: [i16; 4], -) { - let mcu_y = state.get_mcu() / lp.jpeg_header.mcuh; - let luma_mul = lp.jpeg_header.cmp_info[0].bcv / lp.jpeg_header.mcuv; - - let (bits_already_read, byte_being_read) = bit_reader.overhang(); - - let pos = bit_reader.get_stream_position(); - - let retval = ThreadHandoff { - segment_offset_in_file: pos, - luma_y_start: luma_mul * mcu_y, - luma_y_end: luma_mul * (mcu_y + 1), - overhang_byte: byte_being_read, - num_overhang_bits: bits_already_read, - last_dc: lastdc, - segment_size: 0, // initialized later - }; - - thread_handoff.push(retval); -} - -// reads subsequent scans for progressive images +/// Reads a scan for progressive images where the image is encoded in multiple passes. +/// Between each scan are a bunch of header than need to be parsed containing information +/// like updated Huffman tables and quantization tables. pub fn read_progressive_scan( - lp: &mut LeptonHeader, + jf: &JPegHeader, reader: &mut R, image_data: &mut [BlockBasedImage], + reconstruct_info: &mut ReconstructionInfo, ) -> Result<()> { // track to see how far we got in progressive encoding in case of truncated images, however this // was never actually implemented in the original C++ code - lp.max_sah = max( - lp.max_sah, - max(lp.jpeg_header.cs_sal, lp.jpeg_header.cs_sah), - ); + reconstruct_info.max_sah = max(reconstruct_info.max_sah, max(jf.cs_sal, jf.cs_sah)); let mut bit_reader = BitReader::new(reader); // init variables for decoding - let mut state = JpegPositionState::new(&lp.jpeg_header, 0); + let mut state = JpegPositionState::new(jf, 0); // JPEG imagedata decoding routines let mut sta = JPegDecodeStatus::DecodeInProgress; while sta != JPegDecodeStatus::ScanCompleted { - let jf = &lp.jpeg_header; - // decoding for interleaved data - state.reset_rstw(jf); // restart wait counter + state.reset_rstw(&jf); // restart wait counter if jf.cs_to == 0 { if jf.cs_sah == 0 { @@ -355,7 +324,7 @@ pub fn read_progressive_scan( // if we saw a pad bit at the end of the block, then remember whether they were 1s or 0s. This // will be used later on to reconstruct the padding bit_reader - .read_and_verify_fill_bits(&mut lp.pad_bit) + .read_and_verify_fill_bits(&mut reconstruct_info.pad_bit) .context()?; // verify that we got the right RST code here since the above should do 1 mcu. @@ -367,43 +336,55 @@ pub fn read_progressive_scan( } } - lp.scnc += 1; // increment scan counter Ok(()) } /// reads an entire interval until the RST code -fn decode_baseline_rst( +fn decode_baseline_rst( state: &mut JpegPositionState, - lp: &mut LeptonHeader, - thread_handoff: &mut Vec, + partition: &mut FPARTITION, bit_reader: &mut BitReader, image_data: &mut [BlockBasedImage], do_handoff: &mut bool, + jpeg_header: &JPegHeader, + reconstruct_info: &mut ReconstructionInfo, ) -> Result { // should have both AC and DC components - lp.jpeg_header.verify_huffman_table(true, true).context()?; + jpeg_header.verify_huffman_table(true, true).context()?; let mut sta = JPegDecodeStatus::DecodeInProgress; let mut lastdc = [0i16; 4]; // (re)set last DCs for diff coding while sta == JPegDecodeStatus::DecodeInProgress { if *do_handoff { - crystallize_thread_handoff(state, lp, bit_reader, thread_handoff, lastdc); + let (bits_already_read, byte_being_read) = bit_reader.overhang(); + + partition( + bit_reader.get_stream_position(), + RestartSegmentCodingInfo::new( + byte_being_read, + bits_already_read, + lastdc, + state.get_mcu(), + &jpeg_header, + ), + ); *do_handoff = false; } if !bit_reader.is_eof() { - lp.max_dpos[state.get_cmp()] = cmp::max(state.get_dpos(), lp.max_dpos[state.get_cmp()]); // record the max block read + reconstruct_info.max_dpos[state.get_cmp()] = + cmp::max(state.get_dpos(), reconstruct_info.max_dpos[state.get_cmp()]); } // decode block (throws on error) let mut block = [0i16; 64]; let eob = decode_block_seq( bit_reader, - &lp.jpeg_header.get_huff_dc_tree(state.get_cmp()), - &lp.jpeg_header.get_huff_ac_tree(state.get_cmp()), + &jpeg_header.get_huff_dc_tree(state.get_cmp()), + &jpeg_header.get_huff_ac_tree(state.get_cmp()), &mut block, )?; @@ -425,15 +406,15 @@ fn decode_baseline_rst( // see if here is a good position to do a handoff (has to be aligned between MCU rows since we can't split any finer) let old_mcu = state.get_mcu(); - sta = state.next_mcu_pos(&lp.jpeg_header); + sta = state.next_mcu_pos(&jpeg_header); - if state.get_mcu() % lp.jpeg_header.mcuh == 0 && old_mcu != state.get_mcu() { + if state.get_mcu() % jpeg_header.mcuh == 0 && old_mcu != state.get_mcu() { *do_handoff = true; } if bit_reader.is_eof() { sta = JPegDecodeStatus::ScanCompleted; - lp.early_eof_encountered = true; + reconstruct_info.early_eof_encountered = true; } } @@ -444,7 +425,7 @@ fn decode_baseline_rst( /// sequential block decoding routine /// #[inline(never)] -pub fn decode_block_seq( +pub(super) fn decode_block_seq( bit_reader: &mut BitReader, dctree: &HuffTree, actree: &HuffTree, diff --git a/src/structs/jpeg_write.rs b/src/jpeg/jpeg_write.rs similarity index 91% rename from src/structs/jpeg_write.rs rename to src/jpeg/jpeg_write.rs index d82b7814..60209d3b 100644 --- a/src/structs/jpeg_write.rs +++ b/src/jpeg/jpeg_write.rs @@ -38,36 +38,42 @@ use wide::{i16x16, CmpEq}; use crate::consts::{JPegDecodeStatus, JPegType}; use crate::helpers::u16_bit_length; use crate::lepton_error::{err_exit_code, AddContext, ExitCode}; -use crate::structs::bit_writer::BitWriter; -use crate::structs::block_based_image::{AlignedBlock, BlockBasedImage}; -use crate::structs::jpeg_header::{HuffCodes, JPegEncodingInfo}; -use crate::structs::jpeg_position_state::JpegPositionState; -use crate::structs::row_spec::RowSpec; + use crate::{jpeg_code, Result}; -/// write a range of rows corresponding to the thread_handoff structure into the writer. +use super::bit_writer::BitWriter; +use super::block_based_image::{AlignedBlock, BlockBasedImage}; +use super::jpeg_header::{HuffCodes, JPegHeader, ReconstructionInfo, RestartSegmentCodingInfo}; +use super::jpeg_position_state::JpegPositionState; +use super::row_spec::RowSpec; + +/// write a range of rows corresponding to the restart_info structure. +/// Returns the encoded data as a buffer. +/// /// Only works with baseline non-progressive images. pub fn jpeg_write_baseline_row_range( encoded_length: usize, - overhang_byte: u8, - num_overhang_bits: u8, - luma_y_start: u32, - luma_y_end: u32, - mut last_dc: [i16; 4], + restart_info: &RestartSegmentCodingInfo, image_data: &[BlockBasedImage], - jenc: &JPegEncodingInfo, + jpeg_header: &JPegHeader, + rinfo: &ReconstructionInfo, ) -> Result> { - let max_coded_heights = jenc.truncate_components.get_max_coded_heights(); + let max_coded_heights: Vec = rinfo.truncate_components.get_max_coded_heights(); let mut huffw = BitWriter::new(encoded_length); - huffw.reset_from_overhang_byte_and_num_bits(overhang_byte, u32::from(num_overhang_bits)); + huffw.reset_from_overhang_byte_and_num_bits( + restart_info.overhang_byte, + u32::from(restart_info.num_overhang_bits), + ); + + let mut last_dc = restart_info.last_dc; let mut decode_index = 0; loop { let cur_row = RowSpec::get_row_spec_from_index( decode_index, image_data, - jenc.truncate_components.mcu_count_vertical, + rinfo.truncate_components.mcu_count_vertical, &max_coded_heights, ); @@ -81,21 +87,23 @@ pub fn jpeg_write_baseline_row_range( continue; } - if cur_row.min_row_luma_y < luma_y_start { + if cur_row.min_row_luma_y < restart_info.luma_y_start { continue; } - if cur_row.next_row_luma_y > luma_y_end { + if cur_row.next_row_luma_y > restart_info.luma_y_end { break; // we're done here } if cur_row.last_row_to_complete_mcu { recode_one_mcu_row( &mut huffw, - cur_row.mcu_row_index * jenc.jpeg_header.mcuh.get(), + cur_row.mcu_row_index * jpeg_header.mcuh.get(), &mut last_dc, image_data, - jenc, + jpeg_header, + rinfo, + 0, ) .context()?; } @@ -104,13 +112,15 @@ pub fn jpeg_write_baseline_row_range( Ok(huffw.detach_buffer()) } -// writes an entire scan vs only a range of rows as above. -// supports progressive encoding whereas the row range version does not +/// writes an entire scan vs only a range of rows as above. +/// supports progressive encoding whereas the row range version does not pub fn jpeg_write_entire_scan( image_data: &[BlockBasedImage], - jenc: &JPegEncodingInfo, + jpeg_header: &JPegHeader, + rinfo: &ReconstructionInfo, + current_scan_index: usize, ) -> Result> { - let max_coded_heights = jenc.truncate_components.get_max_coded_heights(); + let max_coded_heights = rinfo.truncate_components.get_max_coded_heights(); let mut last_dc = [0i16; 4]; @@ -121,7 +131,7 @@ pub fn jpeg_write_entire_scan( let cur_row = RowSpec::get_row_spec_from_index( decode_index, image_data, - jenc.truncate_components.mcu_count_vertical, + jpeg_header.mcuv.get(), &max_coded_heights, ); @@ -138,10 +148,12 @@ pub fn jpeg_write_entire_scan( if cur_row.last_row_to_complete_mcu { let r = recode_one_mcu_row( &mut huffw, - cur_row.mcu_row_index * jenc.jpeg_header.mcuh.get(), + cur_row.mcu_row_index * jpeg_header.mcuh.get(), &mut last_dc, image_data, - jenc, + jpeg_header, + rinfo, + current_scan_index, ) .context()?; @@ -160,10 +172,10 @@ fn recode_one_mcu_row( mcu: u32, lastdc: &mut [i16], framebuffer: &[BlockBasedImage], - jenc: &JPegEncodingInfo, + jf: &JPegHeader, + rinfo: &ReconstructionInfo, + current_scan_index: usize, ) -> Result { - let jf = &jenc.jpeg_header; - let mut state = JpegPositionState::new(jf, mcu); let mut cumulative_reset_markers = state.get_cumulative_reset_markers(jf); @@ -298,7 +310,7 @@ fn recode_one_mcu_row( } // pad huffman writer - huffw.pad(jenc.pad_bit.unwrap_or(0)); + huffw.pad(rinfo.pad_bit.unwrap_or(0)); assert!( huffw.has_no_remainder(), @@ -313,9 +325,9 @@ fn recode_one_mcu_row( // status 1 means restart if jf.rsti > 0 { - if jenc.rst_cnt.len() == 0 - || (!jenc.rst_cnt_set) - || cumulative_reset_markers < jenc.rst_cnt[jenc.scnc] + if rinfo.rst_cnt.len() == 0 + || (!rinfo.rst_cnt_set) + || cumulative_reset_markers < rinfo.rst_cnt[current_scan_index] { let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8; @@ -627,9 +639,9 @@ fn encode_eobrun_bits(s: u8, v: u16) -> u16 { fn round_trip_block(block: &AlignedBlock, expected: &[u8]) { use std::io::Cursor; - use crate::structs::bit_reader::BitReader; - use crate::structs::jpeg_header::{generate_huff_table_from_distribution, HuffTree}; - use crate::structs::jpeg_read::decode_block_seq; + use super::bit_reader::BitReader; + use super::jpeg_header::{generate_huff_table_from_distribution, HuffTree}; + use super::jpeg_read::decode_block_seq; let mut bitwriter = BitWriter::new(1024); diff --git a/src/jpeg/mod.rs b/src/jpeg/mod.rs new file mode 100644 index 00000000..59668add --- /dev/null +++ b/src/jpeg/mod.rs @@ -0,0 +1,20 @@ +//! Module for reading and recreation of JPEGs without the loss of any information. +//! +//! This means that it should be possible to reconstruct bit-by-bit an exactly identical +//! JPEG file from the input. +//! +//! Note that we never actually decode the JPEG into pixels, since the DCT is lossy, so +//! processing needs to be done at the DCT coefficient level and keep the coefficients in +//! the BlockBasedImage identical. + +mod bit_reader; +mod bit_writer; +mod component_info; +mod jpeg_position_state; + +pub mod block_based_image; +pub mod jpeg_header; +pub mod jpeg_read; +pub mod jpeg_write; +pub mod row_spec; +pub mod truncate_components; diff --git a/src/structs/row_spec.rs b/src/jpeg/row_spec.rs similarity index 98% rename from src/structs/row_spec.rs rename to src/jpeg/row_spec.rs index 367a6e2b..49d461b3 100644 --- a/src/structs/row_spec.rs +++ b/src/jpeg/row_spec.rs @@ -5,7 +5,8 @@ *--------------------------------------------------------------------------------------------*/ use crate::consts::COLOR_CHANNEL_NUM_BLOCK_TYPES; -use crate::structs::block_based_image::BlockBasedImage; + +use super::block_based_image::BlockBasedImage; pub struct RowSpec { pub min_row_luma_y: u32, diff --git a/src/structs/truncate_components.rs b/src/jpeg/truncate_components.rs similarity index 97% rename from src/structs/truncate_components.rs rename to src/jpeg/truncate_components.rs index 513aa390..677ff702 100644 --- a/src/structs/truncate_components.rs +++ b/src/jpeg/truncate_components.rs @@ -6,8 +6,8 @@ use std::cmp; -use crate::structs::component_info::*; -use crate::structs::jpeg_header::JPegHeader; +use super::component_info::ComponentInfo; +use super::jpeg_header::JPegHeader; #[derive(Debug, Clone)] struct TrucateComponentsInfo { diff --git a/src/lib.rs b/src/lib.rs index 087ac4f1..bdbceeda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod consts; mod helpers; +pub mod jpeg; mod jpeg_code; pub mod metrics; mod structs; diff --git a/src/structs/block_context.rs b/src/structs/block_context.rs index c2c2ba8c..8b9a0b74 100644 --- a/src/structs/block_context.rs +++ b/src/structs/block_context.rs @@ -4,13 +4,12 @@ * This software incorporates material from third parties. See NOTICE.txt for details. *--------------------------------------------------------------------------------------------*/ -use crate::structs::block_based_image::{AlignedBlock, BlockBasedImage, EMPTY_BLOCK}; +use crate::jpeg::block_based_image::{AlignedBlock, BlockBasedImage, EMPTY_BLOCK}; use crate::structs::neighbor_summary::{NeighborSummary, NEIGHBOR_DATA_EMPTY}; use crate::structs::probability_tables::ProbabilityTables; pub struct BlockContext { + block_width: u32, cur_block_index: u32, - above_block_index: u32, - cur_neighbor_summary_index: u32, above_neighbor_summary_index: u32, } @@ -23,6 +22,25 @@ pub struct NeighborData<'a> { } impl BlockContext { + /// Create a new BlockContext for the first line of the image at a given y-coordinate. + pub fn off_y(y: u32, image_data: &BlockBasedImage) -> BlockContext { + let block_width = image_data.get_block_width(); + + let cur_block_index = block_width * y; + + // blocks above the first line are never dereferenced + let cur_neighbor_summary_index = if (y & 1) != 0 { block_width } else { 0 }; + + let above_neighbor_summary_index = if (y & 1) != 0 { 0 } else { block_width }; + + BlockContext { + cur_block_index, + block_width, + cur_neighbor_summary_index, + above_neighbor_summary_index, + } + } + // for debugging #[allow(dead_code)] pub fn get_here_index(&self) -> u32 { @@ -30,30 +48,15 @@ impl BlockContext { } // as each new line BlockContext is set by `off_y`, no edge cases with dereferencing - // out of bounds indices is possilbe, therefore no special treatment is needed + // out of bounds indices is possible, therefore no special treatment is needed pub fn next(&mut self) -> u32 { self.cur_block_index += 1; - self.above_block_index += 1; self.cur_neighbor_summary_index += 1; self.above_neighbor_summary_index += 1; self.cur_block_index } - pub fn new( - cur_block_index: u32, - above_block_index: u32, - cur_neighbor_summary_index: u32, - above_neighbor_summary_index: u32, - ) -> Self { - return BlockContext { - cur_block_index, - above_block_index, - cur_neighbor_summary_index, - above_neighbor_summary_index, - }; - } - pub fn here<'a>(&self, image_data: &'a BlockBasedImage) -> &'a AlignedBlock { let retval = image_data.get_block(self.cur_block_index); return retval; @@ -67,12 +70,12 @@ impl BlockContext { ) -> NeighborData<'a> { NeighborData::<'a> { above_left: if ALL_PRESENT { - image_data.get_block(self.above_block_index - 1) + image_data.get_block(self.cur_block_index - self.block_width - 1) } else { &EMPTY_BLOCK }, above: if ALL_PRESENT || pt.is_above_present() { - image_data.get_block(self.above_block_index) + image_data.get_block(self.cur_block_index - self.block_width) } else { &EMPTY_BLOCK }, diff --git a/src/structs/idct.rs b/src/structs/idct.rs index e98a516c..556d2c63 100644 --- a/src/structs/idct.rs +++ b/src/structs/idct.rs @@ -7,7 +7,7 @@ use bytemuck::cast; use wide::{i16x8, i32x8}; -use crate::structs::block_based_image::AlignedBlock; +use crate::jpeg::block_based_image::AlignedBlock; const _W1: i32 = 2841; // 2048*sqrt(2)*cos(1*pi/16) const _W2: i32 = 2676; // 2048*sqrt(2)*cos(2*pi/16) diff --git a/src/structs/lepton_decoder.rs b/src/structs/lepton_decoder.rs index 792993fe..90523387 100644 --- a/src/structs/lepton_decoder.rs +++ b/src/structs/lepton_decoder.rs @@ -14,16 +14,16 @@ use wide::i32x8; use crate::consts::UNZIGZAG_49_TR; use crate::enabled_features::EnabledFeatures; use crate::helpers::u16_bit_length; +use crate::jpeg::block_based_image::{AlignedBlock, BlockBasedImage}; +use crate::jpeg::row_spec::RowSpec; +use crate::jpeg::truncate_components::*; use crate::lepton_error::{err_exit_code, AddContext, ExitCode}; use crate::metrics::Metrics; -use crate::structs::block_based_image::{AlignedBlock, BlockBasedImage}; use crate::structs::block_context::{BlockContext, NeighborData}; use crate::structs::model::{Model, ModelPerColor}; use crate::structs::neighbor_summary::NeighborSummary; use crate::structs::probability_tables::ProbabilityTables; use crate::structs::quantization_tables::QuantizationTables; -use crate::structs::row_spec::RowSpec; -use crate::structs::truncate_components::*; use crate::structs::vpx_bool_reader::VPXBoolReader; use crate::Result; @@ -135,7 +135,7 @@ fn decode_row_wrapper( component_size_in_blocks: u32, features: &EnabledFeatures, ) -> Result<()> { - let mut block_context = image_data.off_y(curr_y); + let mut block_context = BlockContext::off_y(curr_y, image_data); let block_width = image_data.get_block_width(); diff --git a/src/structs/lepton_encoder.rs b/src/structs/lepton_encoder.rs index 82225ec5..3350cd8a 100644 --- a/src/structs/lepton_encoder.rs +++ b/src/structs/lepton_encoder.rs @@ -14,16 +14,16 @@ use wide::i32x8; use crate::consts::UNZIGZAG_49_TR; use crate::enabled_features::EnabledFeatures; use crate::helpers::*; +use crate::jpeg::block_based_image::{AlignedBlock, BlockBasedImage}; +use crate::jpeg::row_spec::RowSpec; +use crate::jpeg::truncate_components::*; use crate::lepton_error::{err_exit_code, AddContext, ExitCode}; use crate::metrics::Metrics; -use crate::structs::block_based_image::{AlignedBlock, BlockBasedImage}; use crate::structs::block_context::{BlockContext, NeighborData}; use crate::structs::model::{Model, ModelPerColor}; use crate::structs::neighbor_summary::NeighborSummary; use crate::structs::probability_tables::ProbabilityTables; use crate::structs::quantization_tables::QuantizationTables; -use crate::structs::row_spec::RowSpec; -use crate::structs::truncate_components::*; use crate::structs::vpx_bool_writer::VPXBoolWriter; use crate::Result; @@ -159,7 +159,7 @@ fn process_row( component_size_in_block: u32, features: &EnabledFeatures, ) -> Result<()> { - let mut block_context = image_data.off_y(curr_y); + let mut block_context = BlockContext::off_y(curr_y, image_data); let block_width = image_data.get_block_width(); for jpeg_x in 0..block_width { @@ -810,7 +810,7 @@ fn roundtrip_read_write_coefficients( // use the Sip hasher directly since that's guaranteed not to change implementation vs the default hasher use siphasher::sip::SipHasher13; - use crate::structs::block_based_image::EMPTY_BLOCK; + use crate::jpeg::block_based_image::EMPTY_BLOCK; use crate::structs::lepton_decoder::read_coefficient_block; use crate::structs::neighbor_summary::NEIGHBOR_DATA_EMPTY; use crate::structs::vpx_bool_reader::VPXBoolReader; diff --git a/src/structs/lepton_file_reader.rs b/src/structs/lepton_file_reader.rs index 12d28245..9fc5d212 100644 --- a/src/structs/lepton_file_reader.rs +++ b/src/structs/lepton_file_reader.rs @@ -15,12 +15,12 @@ use log::warn; use crate::consts::*; use crate::enabled_features::EnabledFeatures; +use crate::jpeg::block_based_image::BlockBasedImage; +use crate::jpeg::jpeg_header::{JPegHeader, ReconstructionInfo, RestartSegmentCodingInfo}; +use crate::jpeg::jpeg_write::{jpeg_write_baseline_row_range, jpeg_write_entire_scan}; use crate::jpeg_code; use crate::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; use crate::metrics::{CpuTimeMeasure, Metrics}; -use crate::structs::block_based_image::BlockBasedImage; -use crate::structs::jpeg_header::JPegEncodingInfo; -use crate::structs::jpeg_write::{jpeg_write_baseline_row_range, jpeg_write_entire_scan}; use crate::structs::lepton_decoder::lepton_decode_row_range; use crate::structs::lepton_header::{LeptonHeader, FIXED_HEADER_SIZE}; use crate::structs::multiplexer::{MultiplexReader, MultiplexReaderState}; @@ -83,7 +83,7 @@ pub fn decode_lepton_file_image( &lh, &enabled_features, 4, - |_thread_handoff, image_data, _lh| { + |_thread_handoff, image_data, _, _| { // just return the image data directly to be merged together return Ok(image_data); }, @@ -380,10 +380,12 @@ impl LeptonFileReader { let mut results = Vec::new(); results.push(header); + let mut scnc = 0; loop { // progressive JPEG consists of scans followed by headers - let scan = jpeg_write_entire_scan(&merged[..], &JPegEncodingInfo::new(lh)).context()?; + let scan = + jpeg_write_entire_scan(&merged[..], &lh.jpeg_header, &lh.rinfo, scnc).context()?; results.push(scan); // read the next headers (DHT, etc) while mirroring it back to the writer @@ -397,7 +399,7 @@ impl LeptonFileReader { } // advance to next scan - lh.scnc += 1; + scnc += 1; } Ok(DecoderState::AppendTrailer(results)) @@ -416,7 +418,7 @@ impl LeptonFileReader { lh, enabled_features, 4, /* retain the last 4 bytes for the very end, since that is the file size, and shouldn't be parsed */ - |_thread_handoff, image_data, _lh| { + |_thread_handoff, image_data, _, _| { // just return the image data directly to be merged together return Ok(image_data); }, @@ -429,16 +431,21 @@ impl LeptonFileReader { &lh, &enabled_features, 4, /*retain 4 bytes for the end for the file size that is appended */ - |thread_handoff, image_data, jenc| { + |thread_handoff, image_data, jpeg_header, rinfo| { + let restart_info = RestartSegmentCodingInfo { + overhang_byte: thread_handoff.overhang_byte, + num_overhang_bits: thread_handoff.num_overhang_bits, + luma_y_start: thread_handoff.luma_y_start, + luma_y_end: thread_handoff.luma_y_end, + last_dc: thread_handoff.last_dc, + }; + let mut result_buffer = jpeg_write_baseline_row_range( thread_handoff.segment_size as usize, - thread_handoff.overhang_byte, - thread_handoff.num_overhang_bits, - thread_handoff.luma_y_start, - thread_handoff.luma_y_end, - thread_handoff.last_dc, + &restart_info, &image_data, - jenc, + &jpeg_header, + &rinfo, ) .context()?; @@ -504,24 +511,27 @@ impl LeptonFileReader { process: fn( thread_handoff: &ThreadHandoff, image_data: Vec, - jenc: &JPegEncodingInfo, + jpeg_header: &JPegHeader, + rinfo: &ReconstructionInfo, ) -> Result

, ) -> Result> { - let qt = lh.jpeg_header.construct_quantization_tables()?; + let qt = QuantizationTables::construct_quantization_tables(&lh.jpeg_header)?; let features = features.clone(); - let jenc = JPegEncodingInfo::new(lh); - let thread_handoff = lh.thread_handoff.clone(); + let jpeg_header = lh.jpeg_header.clone(); + let rinfo = lh.rinfo.clone(); + let multiplex_reader_state = MultiplexReaderState::new( thread_handoff.len(), retention_bytes, features.max_threads as usize, move |thread_id, reader| -> Result<(Metrics, P)> { Self::run_lepton_decoder_processor( - &jenc, + &jpeg_header, + &rinfo, &thread_handoff[thread_id], thread_id == thread_handoff.len() - 1, &qt, @@ -537,25 +547,31 @@ impl LeptonFileReader { /// the logic of a decoder thread. Takes a range of rows fn run_lepton_decoder_processor

( - jenc: &JPegEncodingInfo, + jpeg_header: &JPegHeader, + rinfo: &ReconstructionInfo, thread_handoff: &ThreadHandoff, is_last_thread: bool, qt: &[QuantizationTables], reader: &mut MultiplexReader, features: &EnabledFeatures, - process: fn(&ThreadHandoff, Vec, &JPegEncodingInfo) -> Result

, + process: fn( + &ThreadHandoff, + Vec, + &JPegHeader, + &ReconstructionInfo, + ) -> Result

, ) -> Result<(Metrics, P)> { let cpu_time = CpuTimeMeasure::new(); let mut image_data = Vec::new(); - for i in 0..jenc.jpeg_header.cmpc { + for i in 0..jpeg_header.cmpc { image_data.push(BlockBasedImage::new( - &jenc.jpeg_header, + &jpeg_header, i, thread_handoff.luma_y_start, if is_last_thread { // if this is the last thread, then the image should extend all the way to the bottom - jenc.jpeg_header.cmp_info[0].bcv + jpeg_header.cmp_info[0].bcv } else { thread_handoff.luma_y_end }, @@ -567,7 +583,7 @@ impl LeptonFileReader { metrics.merge_from( lepton_decode_row_range( &qt, - &jenc.truncate_components, + &rinfo.truncate_components, &mut image_data, reader, thread_handoff.luma_y_start, @@ -579,7 +595,7 @@ impl LeptonFileReader { .context()?, ); - let process_result = process(thread_handoff, image_data, &jenc)?; + let process_result = process(thread_handoff, image_data, jpeg_header, rinfo)?; metrics.record_cpu_worker_time(cpu_time.elapsed()); diff --git a/src/structs/lepton_file_writer.rs b/src/structs/lepton_file_writer.rs index 06b15698..377cf088 100644 --- a/src/structs/lepton_file_writer.rs +++ b/src/structs/lepton_file_writer.rs @@ -14,18 +14,19 @@ use log::info; use crate::consts::*; use crate::enabled_features::EnabledFeatures; +use crate::jpeg::block_based_image::BlockBasedImage; +use crate::jpeg::jpeg_header::JPegHeader; +use crate::jpeg::jpeg_read::{read_first_scan, read_progressive_scan}; +use crate::jpeg::truncate_components::TruncateComponents; use crate::jpeg_code; use crate::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; use crate::metrics::{CpuTimeMeasure, Metrics}; -use crate::structs::block_based_image::BlockBasedImage; -use crate::structs::jpeg_header::JPegHeader; -use crate::structs::jpeg_read::{read_progressive_scan, read_scan}; use crate::structs::lepton_encoder::lepton_encode_row_range; use crate::structs::lepton_file_reader::decode_lepton_file; use crate::structs::lepton_header::LeptonHeader; use crate::structs::multiplexer::multiplex_write; +use crate::structs::quantization_tables::QuantizationTables; use crate::structs::thread_handoff::ThreadHandoff; -use crate::structs::truncate_components::TruncateComponents; /// reads a jpeg and writes it out as a lepton file pub fn encode_lepton_wrapper( @@ -39,7 +40,7 @@ pub fn encode_lepton_wrapper( let metrics = run_lepton_encoder_threads( &lp.jpeg_header, - &lp.truncate_components, + &lp.rinfo.truncate_components, writer, &lp.thread_handoff[..], image_data, @@ -149,7 +150,7 @@ pub fn read_jpeg( .context(); } - lp.truncate_components.init(&lp.jpeg_header); + lp.rinfo.truncate_components.init(&lp.jpeg_header); let mut image_data = Vec::::new(); for i in 0..lp.jpeg_header.cmpc { // constructor takes height in proportion to the component[0] @@ -163,8 +164,26 @@ pub fn read_jpeg( let mut thread_handoff = Vec::::new(); let start_scan: u32 = reader.stream_position()?.try_into().unwrap(); - read_scan(&mut lp, reader, &mut thread_handoff, &mut image_data[..]).context()?; - lp.scnc += 1; + read_first_scan( + &lp.jpeg_header, + reader, + &mut |segment_offset_in_file, restart_info| { + let retval = ThreadHandoff { + segment_offset_in_file: segment_offset_in_file, + luma_y_start: restart_info.luma_y_start, + luma_y_end: restart_info.luma_y_end, + overhang_byte: restart_info.overhang_byte, + num_overhang_bits: restart_info.num_overhang_bits, + last_dc: restart_info.last_dc, + segment_size: 0, // initialized later + }; + + thread_handoff.push(retval); + }, + &mut image_data[..], + &mut lp.rinfo, + ) + .context()?; let mut end_scan = reader.stream_position()?.try_into().unwrap(); @@ -192,9 +211,10 @@ pub fn read_jpeg( } if lp.jpeg_header.jpeg_type == JPegType::Sequential { - if lp.early_eof_encountered { - lp.truncate_components - .set_truncation_bounds(&lp.jpeg_header, lp.max_dpos); + if lp.rinfo.early_eof_encountered { + lp.rinfo + .truncate_components + .set_truncation_bounds(&lp.jpeg_header, lp.rinfo.max_dpos); // If we got an early EOF, then seek backwards and capture the last two bytes and store them as garbage. // This is necessary since the decoder will assume that zero garbage always means a properly terminated JPEG @@ -216,7 +236,7 @@ pub fn read_jpeg( } else { assert!(lp.jpeg_header.jpeg_type == JPegType::Progressive); - if lp.early_eof_encountered { + if lp.rinfo.early_eof_encountered { return err_exit_code( ExitCode::UnsupportedJpeg, "truncation is only supported for baseline images", @@ -228,10 +248,10 @@ pub fn read_jpeg( while prepare_to_decode_next_scan(&mut lp, reader, enabled_features).context()? { callback(&lp.jpeg_header); - read_progressive_scan(&mut lp, reader, &mut image_data[..]).context()?; - lp.scnc += 1; + read_progressive_scan(&lp.jpeg_header, reader, &mut image_data[..], &mut lp.rinfo) + .context()?; - if lp.early_eof_encountered { + if lp.rinfo.early_eof_encountered { return err_exit_code( ExitCode::UnsupportedJpeg, "truncation is only supported for baseline images", @@ -315,7 +335,7 @@ fn run_lepton_encoder_threads( ); // Prepare quantization tables - let quantization_tables = jpeg_header.construct_quantization_tables()?; + let quantization_tables = QuantizationTables::construct_quantization_tables(jpeg_header)?; let colldata = colldata.clone(); let thread_handoffs = thread_handoffs.to_vec(); @@ -451,16 +471,16 @@ fn prepare_to_decode_next_scan( return Ok(false); } - lp.max_bpos = cmp::max(lp.max_bpos, u32::from(lp.jpeg_header.cs_to)); + lp.rinfo.max_bpos = cmp::max(lp.rinfo.max_bpos, u32::from(lp.jpeg_header.cs_to)); // FIXME: not sure why only first bit of csSah is examined but 4 bits of it are stored - lp.max_sah = cmp::max( - lp.max_sah, + lp.rinfo.max_sah = cmp::max( + lp.rinfo.max_sah, cmp::max(lp.jpeg_header.cs_sal, lp.jpeg_header.cs_sah), ); for i in 0..lp.jpeg_header.cs_cmpc { - lp.max_cmp = cmp::max(lp.max_cmp, lp.jpeg_header.cs_cmp[i] as u32); + lp.rinfo.max_cmp = cmp::max(lp.rinfo.max_cmp, lp.jpeg_header.cs_cmp[i] as u32); } return Ok(true); diff --git a/src/structs/lepton_header.rs b/src/structs/lepton_header.rs index aae6c280..9e421579 100644 --- a/src/structs/lepton_header.rs +++ b/src/structs/lepton_header.rs @@ -8,10 +8,9 @@ use flate2::Compression; use crate::consts::*; use crate::helpers::buffer_prefix_matches_marker; +use crate::jpeg::jpeg_header::{JPegHeader, ReconstructionInfo}; use crate::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; -use crate::structs::jpeg_header::JPegHeader; use crate::structs::thread_handoff::ThreadHandoff; -use crate::structs::truncate_components::TruncateComponents; use crate::EnabledFeatures; pub const FIXED_HEADER_SIZE: usize = 28; @@ -32,39 +31,12 @@ pub struct LeptonHeader { pub jpeg_header: JPegHeader, - /// information about how to truncate the image if it was partially written - pub truncate_components: TruncateComponents, - pub rst_err: Vec, - /// A list containing one entry for each scan segment. Each entry contains the number of restart intervals - /// within the corresponding scan segment. - pub rst_cnt: Vec, - - /// the mask for padding out the bitstream when we get to the end of a reset block - pub pad_bit: Option, - - pub rst_cnt_set: bool, - /// garbage data (default value - empty segment - means no garbage data) pub garbage_data: Vec, - /// count of scans encountered so far - pub scnc: usize, - - pub early_eof_encountered: bool, - - /// the maximum dpos in a truncated image - pub max_dpos: [u32; 4], - - /// the maximum component in a truncated image - pub max_cmp: u32, - - /// the maximum band in a truncated image - pub max_bpos: u32, - - /// the maximum bit in a truncated image - pub max_sah: u8, + pub rinfo: ReconstructionInfo, pub jpeg_file_size: u32, @@ -177,22 +149,23 @@ impl LeptonHeader { self.raw_jpeg_header_read_index = header_data_cursor.position() as usize; } - self.truncate_components.init(&self.jpeg_header); + self.rinfo.truncate_components.init(&self.jpeg_header); - if self.early_eof_encountered { - self.truncate_components - .set_truncation_bounds(&self.jpeg_header, self.max_dpos); + if self.rinfo.early_eof_encountered { + self.rinfo + .truncate_components + .set_truncation_bounds(&self.jpeg_header, self.rinfo.max_dpos); } let num_threads = self.thread_handoff.len(); // luma_y_end of the last thread is not serialized/deserialized, fill it here self.thread_handoff[num_threads - 1].luma_y_end = - self.truncate_components.get_block_height(0); + self.rinfo.truncate_components.get_block_height(0); // if the last segment was too big to fit with the garbage data taken into account, shorten it // (a bit of broken logic in the encoder, but can't change it without breaking the file format) - if self.early_eof_encountered { + if self.rinfo.early_eof_encountered { let mut max_last_segment_size = self.jpeg_file_size - u32::try_from(self.garbage_data.len())? - u32::try_from(self.raw_jpeg_header_read_index)? @@ -205,6 +178,8 @@ impl LeptonHeader { let last = &mut self.thread_handoff[num_threads - 1]; + let max_last_segment_size = max_last_segment_size; + if last.segment_size > max_last_segment_size { // re-adjust the last segment size last.segment_size = max_last_segment_size; @@ -273,17 +248,19 @@ impl LeptonHeader { } if buffer_prefix_matches_marker(current_lepton_marker, LEPTON_HEADER_PAD_MARKER) { - self.pad_bit = Some(header_reader.read_u8()?); + self.rinfo.pad_bit = Some(header_reader.read_u8()?); } else if buffer_prefix_matches_marker( current_lepton_marker, LEPTON_HEADER_JPG_RESTARTS_MARKER, ) { // CRS marker - self.rst_cnt_set = true; + self.rinfo.rst_cnt_set = true; let rst_count = header_reader.read_u32::()?; for _i in 0..rst_count { - self.rst_cnt.push(header_reader.read_u32::()?); + self.rinfo + .rst_cnt + .push(header_reader.read_u32::()?); } } else if buffer_prefix_matches_marker( current_lepton_marker, @@ -325,14 +302,14 @@ impl LeptonHeader { current_lepton_marker, LEPTON_HEADER_EARLY_EOF_MARKER, ) { - self.max_cmp = header_reader.read_u32::()?; - self.max_bpos = header_reader.read_u32::()?; - self.max_sah = u8::try_from(header_reader.read_u32::()?)?; - self.max_dpos[0] = header_reader.read_u32::()?; - self.max_dpos[1] = header_reader.read_u32::()?; - self.max_dpos[2] = header_reader.read_u32::()?; - self.max_dpos[3] = header_reader.read_u32::()?; - self.early_eof_encountered = true; + self.rinfo.max_cmp = header_reader.read_u32::()?; + self.rinfo.max_bpos = header_reader.read_u32::()?; + self.rinfo.max_sah = u8::try_from(header_reader.read_u32::()?)?; + self.rinfo.max_dpos[0] = header_reader.read_u32::()?; + self.rinfo.max_dpos[1] = header_reader.read_u32::()?; + self.rinfo.max_dpos[2] = header_reader.read_u32::()?; + self.rinfo.max_dpos[3] = header_reader.read_u32::()?; + self.rinfo.early_eof_encountered = true; } else { return err_exit_code(ExitCode::BadLeptonFile, "unknown data found"); } @@ -443,7 +420,7 @@ impl LeptonHeader { mrw.write_all(&LEPTON_HEADER_PAD_MARKER)?; // data: this.padBit - mrw.write_u8(self.pad_bit.unwrap_or(0))?; + mrw.write_u8(self.rinfo.pad_bit.unwrap_or(0))?; Ok(()) } @@ -459,14 +436,14 @@ impl LeptonHeader { } fn write_lepton_jpeg_restarts_if_needed(&self, mrw: &mut W) -> Result<()> { - if self.rst_cnt.len() > 0 { + if self.rinfo.rst_cnt.len() > 0 { // marker: CRS mrw.write_all(&LEPTON_HEADER_JPG_RESTARTS_MARKER)?; - mrw.write_u32::(self.rst_cnt.len() as u32)?; + mrw.write_u32::(self.rinfo.rst_cnt.len() as u32)?; - for i in 0..self.rst_cnt.len() { - mrw.write_u32::(self.rst_cnt[i])?; + for i in 0..self.rinfo.rst_cnt.len() { + mrw.write_u32::(self.rinfo.rst_cnt[i])?; } } @@ -491,17 +468,17 @@ impl LeptonHeader { &self, mrw: &mut W, ) -> Result<()> { - if self.early_eof_encountered { + if self.rinfo.early_eof_encountered { // EEE marker mrw.write_all(&LEPTON_HEADER_EARLY_EOF_MARKER)?; - mrw.write_u32::(self.max_cmp)?; - mrw.write_u32::(self.max_bpos)?; - mrw.write_u32::(u32::from(self.max_sah))?; - mrw.write_u32::(self.max_dpos[0])?; - mrw.write_u32::(self.max_dpos[1])?; - mrw.write_u32::(self.max_dpos[2])?; - mrw.write_u32::(self.max_dpos[3])?; + mrw.write_u32::(self.rinfo.max_cmp)?; + mrw.write_u32::(self.rinfo.max_bpos)?; + mrw.write_u32::(u32::from(self.rinfo.max_sah))?; + mrw.write_u32::(self.rinfo.max_dpos[0])?; + mrw.write_u32::(self.rinfo.max_dpos[1])?; + mrw.write_u32::(self.rinfo.max_dpos[2])?; + mrw.write_u32::(self.rinfo.max_dpos[3])?; } Ok(()) diff --git a/src/structs/mod.rs b/src/structs/mod.rs index 4bef598f..e6ef2bf0 100644 --- a/src/structs/mod.rs +++ b/src/structs/mod.rs @@ -9,17 +9,9 @@ #![forbid(unsafe_code)] #![forbid(trivial_numeric_casts)] -mod bit_reader; -mod bit_writer; -pub mod block_based_image; mod block_context; mod branch; -mod component_info; mod idct; -mod jpeg_header; -mod jpeg_position_state; -mod jpeg_read; -mod jpeg_write; mod lepton_decoder; mod lepton_encoder; pub mod lepton_file_reader; @@ -31,13 +23,11 @@ mod neighbor_summary; mod partial_buffer; mod probability_tables; mod quantization_tables; -mod row_spec; mod simple_hash; #[cfg(not(feature = "use_rayon"))] pub mod simple_threadpool; mod thread_handoff; -mod truncate_components; mod vpx_bool_reader; mod vpx_bool_writer; diff --git a/src/structs/probability_tables.rs b/src/structs/probability_tables.rs index d12830c8..30403aef 100644 --- a/src/structs/probability_tables.rs +++ b/src/structs/probability_tables.rs @@ -9,7 +9,7 @@ use wide::{i16x8, i32x8, u16x8}; use crate::consts::*; use crate::enabled_features; -use crate::structs::block_based_image::AlignedBlock; +use crate::jpeg::block_based_image::AlignedBlock; use crate::structs::block_context::NeighborData; use crate::structs::idct::*; use crate::structs::model::*; diff --git a/src/structs/quantization_tables.rs b/src/structs/quantization_tables.rs index 32844cbd..f597068a 100644 --- a/src/structs/quantization_tables.rs +++ b/src/structs/quantization_tables.rs @@ -6,7 +6,9 @@ use crate::consts::*; use crate::helpers::*; -use crate::structs::jpeg_header::JPegHeader; +use crate::jpeg::jpeg_header::JPegHeader; +use crate::lepton_error::err_exit_code; +use crate::{ExitCode, Result}; pub struct QuantizationTables { quantization_table: [u16; 64], @@ -61,6 +63,29 @@ impl QuantizationTables { retval } + /// constructs the quantization table based on the jpeg header + pub fn construct_quantization_tables( + jpeg_header: &JPegHeader, + ) -> Result> { + let mut quantization_tables = Vec::new(); + for i in 0..jpeg_header.cmpc { + let qtables = QuantizationTables::new(jpeg_header, i); + + // check to see if quantitization table was properly initialized + // (table contains divisors for edge coefficients so it never should have a zero) + for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 32, 40, 48, 56] { + if qtables.get_quantization_table()[i] == 0 { + return err_exit_code( + ExitCode::UnsupportedJpegWithZeroIdct0, + "Quantization table contains zero for edge which would cause a divide by zero", + ); + } + } + quantization_tables.push(qtables); + } + Ok(quantization_tables) + } + pub fn get_quantization_table(&self) -> &[u16; 64] { &self.quantization_table }