From d89a65a769f11e837248f88647ed7d3cfa5b4964 Mon Sep 17 00:00:00 2001 From: Kristof Date: Sun, 1 Dec 2024 21:36:33 +0100 Subject: [PATCH 01/14] standardize on u32 for coordinates and lengths --- src/enabled_features.rs | 12 +++--- src/main.rs | 4 +- src/structs/bit_reader.rs | 4 +- src/structs/block_based_image.rs | 38 +++++++++--------- src/structs/block_context.rs | 20 +++++----- src/structs/component_info.rs | 40 +++++++++---------- src/structs/jpeg_header.rs | 62 +++++++++++++++++------------- src/structs/jpeg_position_state.rs | 28 +++++++------- src/structs/jpeg_write.rs | 10 ++--- src/structs/lepton_decoder.rs | 8 ++-- src/structs/lepton_encoder.rs | 8 ++-- src/structs/lepton_file_reader.rs | 2 +- src/structs/lepton_file_writer.rs | 17 ++++---- src/structs/lepton_header.rs | 48 ++++++++++++----------- src/structs/row_spec.rs | 22 +++++------ src/structs/thread_handoff.rs | 14 +++---- src/structs/truncate_components.rs | 24 ++++++------ 17 files changed, 187 insertions(+), 174 deletions(-) diff --git a/src/enabled_features.rs b/src/enabled_features.rs index fb0e3b9e..81135b51 100644 --- a/src/enabled_features.rs +++ b/src/enabled_features.rs @@ -8,10 +8,10 @@ pub struct EnabledFeatures { pub reject_dqts_with_zeros: bool, /// maximum jpeg width - pub max_jpeg_width: i32, + pub max_jpeg_width: u32, // maximum jpeg height - pub max_jpeg_height: i32, + pub max_jpeg_height: u32, /// Sadly C++ version has a bug where it uses 16 bit math in the SIMD path and 32 bit math in the scalar path pub use_16bit_dc_estimate: bool, @@ -53,8 +53,8 @@ impl EnabledFeatures { Self { progressive: true, reject_dqts_with_zeros: false, - max_jpeg_height: i32::MAX, - max_jpeg_width: i32::MAX, + max_jpeg_height: u32::MAX, + max_jpeg_width: u32::MAX, use_16bit_dc_estimate: false, use_16bit_adv_predict: false, accept_invalid_dht: true, @@ -70,8 +70,8 @@ impl EnabledFeatures { Self { progressive: true, reject_dqts_with_zeros: false, - max_jpeg_height: i32::MAX, - max_jpeg_width: i32::MAX, + max_jpeg_height: u32::MAX, + max_jpeg_width: u32::MAX, use_16bit_dc_estimate: true, use_16bit_adv_predict: true, accept_invalid_dht: true, diff --git a/src/main.rs b/src/main.rs index 1128a27a..140461fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,14 +113,14 @@ Options: override_if( &mut pargs, "--max-width", - parse_i32, + parse_u32, &mut enabled_features.max_jpeg_width, )?; override_if( &mut pargs, "--max-height", - parse_i32, + parse_u32, &mut enabled_features.max_jpeg_height, )?; diff --git a/src/structs/bit_reader.rs b/src/structs/bit_reader.rs index b221e76d..e2f1eae0 100644 --- a/src/structs/bit_reader.rs +++ b/src/structs/bit_reader.rs @@ -23,10 +23,10 @@ pub struct BitReader { } impl BitReader { - pub fn get_stream_position(&mut self) -> i32 { + pub fn get_stream_position(&mut self) -> u32 { self.undo_read_ahead(); - let pos: i32 = (self.inner.stream_position().unwrap() - self.start_offset) + let pos: u32 = (self.inner.stream_position().unwrap() - self.start_offset) .try_into() .unwrap(); diff --git a/src/structs/block_based_image.rs b/src/structs/block_based_image.rs index 0e908cc2..8a4982ee 100644 --- a/src/structs/block_based_image.rs +++ b/src/structs/block_based_image.rs @@ -16,11 +16,11 @@ use crate::structs::jpeg_header::JPegHeader; /// the image may only hold a subset of the components (specified by dpos_offset), /// but they can be merged pub struct BlockBasedImage { - block_width: i32, + block_width: u32, - original_height: i32, + original_height: u32, - dpos_offset: i32, + dpos_offset: u32, image: Vec, } @@ -32,22 +32,22 @@ impl BlockBasedImage { pub fn new( jpeg_header: &JPegHeader, component: usize, - luma_y_start: i32, - luma_y_end: i32, + luma_y_start: u32, + luma_y_end: u32, ) -> Self { let block_width = jpeg_header.cmp_info[component].bch; let original_height = jpeg_header.cmp_info[component].bcv; let max_size = block_width * original_height; let image_capcity = usize::try_from( - (i64::from(max_size) * i64::from(luma_y_end - luma_y_start) - + i64::from(jpeg_header.cmp_info[0].bcv - 1 /* round up */)) - / i64::from(jpeg_header.cmp_info[0].bcv), + (u64::from(max_size) * u64::from(luma_y_end - luma_y_start) + + u64::from(jpeg_header.cmp_info[0].bcv - 1 /* round up */)) + / u64::from(jpeg_header.cmp_info[0].bcv), ) .unwrap(); - let dpos_offset = i32::try_from( - i64::from(max_size) * i64::from(luma_y_start) / i64::from(jpeg_header.cmp_info[0].bcv), + let dpos_offset = u32::try_from( + u64::from(max_size) * u64::from(luma_y_start) / u64::from(jpeg_header.cmp_info[0].bcv), ) .unwrap(); @@ -70,7 +70,7 @@ impl BlockBasedImage { for v in images { assert!( - v[index].dpos_offset == contents.len() as i32, + v[index].dpos_offset == contents.len() as u32, "previous content should match new content" ); @@ -111,20 +111,20 @@ impl BlockBasedImage { } // blocks above the first line are never dereferenced - pub fn off_y(&self, y: i32) -> BlockContext { + pub fn off_y(&self, y: u32) -> BlockContext { return BlockContext::new( self.block_width * y, - self.block_width * (y - 1), + 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) -> i32 { + pub fn get_block_width(&self) -> u32 { self.block_width } - pub fn get_original_height(&self) -> i32 { + pub fn get_original_height(&self) -> u32 { self.original_height } @@ -133,7 +133,7 @@ impl BlockBasedImage { #[inline(always)] pub fn fill_up_to_dpos( &mut self, - dpos: i32, + dpos: u32, block_to_write: Option, ) -> &mut AlignedBlock { // ensure that dpos_offset got set to the right value when we start writing @@ -169,11 +169,11 @@ impl BlockBasedImage { return &mut self.image[relative_offset as usize]; } - pub fn set_block_data(&mut self, dpos: i32, block_data: AlignedBlock) { + pub fn set_block_data(&mut self, dpos: u32, block_data: AlignedBlock) { self.fill_up_to_dpos(dpos, Some(block_data)); } - pub fn get_block(&self, dpos: i32) -> &AlignedBlock { + pub fn get_block(&self, dpos: u32) -> &AlignedBlock { if (dpos - self.dpos_offset) as usize >= self.image.len() { return &EMPTY; } else { @@ -191,7 +191,7 @@ impl BlockBasedImage { } #[inline(always)] - pub fn get_block_mut(&mut self, dpos: i32) -> &mut AlignedBlock { + pub fn get_block_mut(&mut self, dpos: u32) -> &mut AlignedBlock { self.fill_up_to_dpos(dpos, None) } } diff --git a/src/structs/block_context.rs b/src/structs/block_context.rs index 5a83dbb6..c2c2ba8c 100644 --- a/src/structs/block_context.rs +++ b/src/structs/block_context.rs @@ -8,11 +8,11 @@ use crate::structs::block_based_image::{AlignedBlock, BlockBasedImage, EMPTY_BLO use crate::structs::neighbor_summary::{NeighborSummary, NEIGHBOR_DATA_EMPTY}; use crate::structs::probability_tables::ProbabilityTables; pub struct BlockContext { - cur_block_index: i32, - above_block_index: i32, + cur_block_index: u32, + above_block_index: u32, - cur_neighbor_summary_index: i32, - above_neighbor_summary_index: i32, + cur_neighbor_summary_index: u32, + above_neighbor_summary_index: u32, } pub struct NeighborData<'a> { pub above: &'a AlignedBlock, @@ -25,13 +25,13 @@ pub struct NeighborData<'a> { impl BlockContext { // for debugging #[allow(dead_code)] - pub fn get_here_index(&self) -> i32 { + pub fn get_here_index(&self) -> u32 { self.cur_block_index } // 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 - pub fn next(&mut self) -> i32 { + pub fn next(&mut self) -> u32 { self.cur_block_index += 1; self.above_block_index += 1; self.cur_neighbor_summary_index += 1; @@ -41,10 +41,10 @@ impl BlockContext { } pub fn new( - cur_block_index: i32, - above_block_index: i32, - cur_neighbor_summary_index: i32, - above_neighbor_summary_index: i32, + cur_block_index: u32, + above_block_index: u32, + cur_neighbor_summary_index: u32, + above_neighbor_summary_index: u32, ) -> Self { return BlockContext { cur_block_index, diff --git a/src/structs/component_info.rs b/src/structs/component_info.rs index 7c6aba89..ba104cf4 100644 --- a/src/structs/component_info.rs +++ b/src/structs/component_info.rs @@ -16,34 +16,34 @@ pub struct ComponentInfo { pub huff_ac: u8, /// sample factor vertical - pub sfv: i32, + pub sfv: u32, /// sample factor horizontal - pub sfh: i32, + pub sfh: u32, /// blocks in mcu - pub mbs: i32, + pub mbs: u32, /// block count vertical (interleaved) - pub bcv: i32, + pub bcv: u32, /// block count horizontal (interleaved) - pub bch: i32, + pub bch: u32, /// block count (all) (interleaved) - pub bc: i32, + pub bc: u32, /// block count vertical (non interleaved) - pub ncv: i32, + pub ncv: u32, /// block count horizontal (non interleaved) - pub nch: i32, + pub nch: u32, /// block count (all) (non interleaved) - pub nc: i32, + pub nc: u32, /// statistical identity - pub sid: i32, + pub sid: u32, /// jpeg internal id pub jid: u8, @@ -53,16 +53,16 @@ impl Default for ComponentInfo { fn default() -> ComponentInfo { return ComponentInfo { q_table_index: 0xff, - sfv: -1, - sfh: -1, - mbs: -1, - bcv: -1, - bch: -1, - bc: -1, - ncv: -1, - nch: -1, - nc: -1, - sid: -1, + sfv: u32::MAX, + sfh: u32::MAX, + mbs: u32::MAX, + bcv: u32::MAX, + bch: u32::MAX, + bc: u32::MAX, + ncv: u32::MAX, + nch: u32::MAX, + nc: u32::MAX, + sid: u32::MAX, jid: 0xff, huff_dc: 0xff, huff_ac: 0xff, diff --git a/src/structs/jpeg_header.rs b/src/structs/jpeg_header.rs index 1be2d733..2c4542f8 100644 --- a/src/structs/jpeg_header.rs +++ b/src/structs/jpeg_header.rs @@ -33,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ use std::io::Read; +use std::num::NonZeroU32; use crate::consts::JPegType; use crate::enabled_features::EnabledFeatures; @@ -125,10 +126,10 @@ impl HuffCodes { for i in 0..256 { let s = i & 0xf; self.c_len_plus_s[i] = (self.c_len[i] + (s as u16)) as u8; - self.c_val_shift_s[i] = (self.c_val[i] as u32) << s; + self.c_val_shift_s[i] = u32::from(self.c_val[i]) << s; // calculate the value for negative coefficients, which compensates for the sign bit - self.c_val_shift_s[i + 256] = ((self.c_val[i] as u32) << s) | ((1u32 << s) - 1); + self.c_val_shift_s[i + 256] = (u32::from(self.c_val[i]) << s) | ((1u32 << s) - 1); } // find out eobrun (runs of all zero blocks) max value. This is used encoding/decoding progressive files. @@ -273,17 +274,17 @@ pub struct JPegHeader { pub ht_set: [[u8; 4]; 2], // 1 if huffman table is set pub cmp_info: [ComponentInfo; 4], // components pub cmpc: usize, // component count - pub img_width: i32, // width of image - pub img_height: i32, // height of image + pub img_width: u32, // width of image + pub img_height: u32, // height of image pub jpeg_type: JPegType, - pub sfhm: i32, // max horizontal sample factor - pub sfvm: i32, // max verical sample factor - pub mcuv: i32, // mcus per line - pub mcuh: i32, // mcus per collumn - pub mcuc: i32, // count of mcus + pub sfhm: u32, // max horizontal sample factor + pub sfvm: u32, // max verical sample factor + pub mcuv: NonZeroU32, // mcus per line + pub mcuh: NonZeroU32, // mcus per collumn + pub mcuc: u32, // count of mcus - pub rsti: i32, // restart interval + pub rsti: u32, // restart interval pub cs_cmpc: usize, // component count in current scan pub cs_cmp: [usize; 4], // component numbers in current scan @@ -300,7 +301,7 @@ pub struct JPegEncodingInfo { /// 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, + 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, @@ -349,8 +350,8 @@ impl Default for JPegHeader { jpeg_type: JPegType::Unknown, sfhm: 0, sfvm: 0, - mcuv: 0, - mcuh: 0, + mcuv: NonZeroU32::MIN, + mcuh: NonZeroU32::MIN, mcuc: 0, rsti: 0, cs_cmpc: 0, @@ -364,18 +365,22 @@ impl Default for JPegHeader { } impl JPegHeader { + #[inline(always)] pub 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 { &self.h_trees[0][usize::from(self.cmp_info[cmp].huff_dc)] } + #[inline(always)] pub 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 { &self.h_trees[1][usize::from(self.cmp_info[cmp].huff_ac)] } @@ -434,30 +439,35 @@ impl JPegHeader { } } - self.mcuv = (1.0 * self.img_height as f64 / (8.0 * self.sfhm as f64)).ceil() as i32; - self.mcuh = (1.0 * self.img_width as f64 / (8.0 * self.sfvm as f64)).ceil() as i32; - self.mcuc = self.mcuv * self.mcuh; + self.mcuv = NonZeroU32::new( + (1.0 * self.img_height as f64 / (8.0 * self.sfhm as f64)).ceil() as u32, + ) + .unwrap(); + self.mcuh = + NonZeroU32::new((1.0 * self.img_width as f64 / (8.0 * self.sfvm as f64)).ceil() as u32) + .unwrap(); + self.mcuc = self.mcuv.get() * self.mcuh.get(); for cmp in 0..self.cmpc { self.cmp_info[cmp].mbs = self.cmp_info[cmp].sfv * self.cmp_info[cmp].sfh; - self.cmp_info[cmp].bcv = self.mcuv * self.cmp_info[cmp].sfh; - self.cmp_info[cmp].bch = self.mcuh * self.cmp_info[cmp].sfv; + self.cmp_info[cmp].bcv = self.mcuv.get() * self.cmp_info[cmp].sfh; + self.cmp_info[cmp].bch = self.mcuh.get() * self.cmp_info[cmp].sfv; self.cmp_info[cmp].bc = self.cmp_info[cmp].bcv * self.cmp_info[cmp].bch; self.cmp_info[cmp].ncv = (1.0 * self.img_height as f64 * (self.cmp_info[cmp].sfh as f64 / (8.0 * self.sfhm as f64))) - .ceil() as i32; + .ceil() as u32; self.cmp_info[cmp].nch = (1.0 * self.img_width as f64 * (self.cmp_info[cmp].sfv as f64 / (8.0 * self.sfvm as f64))) - .ceil() as i32; + .ceil() as u32; self.cmp_info[cmp].nc = self.cmp_info[cmp].ncv * self.cmp_info[cmp].nch; } // decide components' statistical ids if self.cmpc <= 3 { for cmp in 0..self.cmpc { - self.cmp_info[cmp].sid = cmp as i32; + self.cmp_info[cmp].sid = cmp as u32; } } else { for cmp in 0..self.cmpc { @@ -642,7 +652,7 @@ impl JPegHeader { { // DRI segment // define restart interval ensure_space(segment,hpos, 2).context()?; - self.rsti = b_short(segment[hpos], segment[hpos + 1]) as i32; + self.rsti = u32::from(b_short(segment[hpos], segment[hpos + 1])); } jpeg_code::SOS => // SOS segment @@ -741,8 +751,8 @@ impl JPegHeader { } // image size, height & component count - self.img_height = i32::from(b_short(segment[hpos + 1], segment[hpos + 2])); - self.img_width = i32::from(b_short(segment[hpos + 3], segment[hpos + 4])); + self.img_height = u32::from(b_short(segment[hpos + 1], segment[hpos + 2])); + self.img_width = u32::from(b_short(segment[hpos + 3], segment[hpos + 4])); if self.img_height == 0 || self.img_width == 0 { @@ -769,8 +779,8 @@ impl JPegHeader { ensure_space(segment,hpos, 3).context()?; self.cmp_info[cmp].jid = segment[hpos]; - self.cmp_info[cmp].sfv = lbits(segment[hpos + 1], 4) as i32; - self.cmp_info[cmp].sfh = rbits(segment[hpos + 1], 4) as i32; + self.cmp_info[cmp].sfv = u32::from(lbits(segment[hpos + 1], 4)); + self.cmp_info[cmp].sfh = u32::from(rbits(segment[hpos + 1], 4)); if self.cmp_info[cmp].sfv > 2 || self.cmp_info[cmp].sfh > 2 { diff --git a/src/structs/jpeg_position_state.rs b/src/structs/jpeg_position_state.rs index 283aaee4..9e09a91e 100644 --- a/src/structs/jpeg_position_state.rs +++ b/src/structs/jpeg_position_state.rs @@ -16,19 +16,19 @@ pub struct JpegPositionState { cmp: usize, /// current minimum coded unit (a fraction of dpos) - mcu: i32, + mcu: u32, /// index of component csc: usize, /// offset within mcu - sub: i32, + sub: u32, /// current block position in image for this component - dpos: i32, + dpos: u32, /// number of blocks left until reset interval - rstw: i32, + rstw: u32, /// tracks long zero byte runs in progressive images pub eobrun: u16, @@ -40,7 +40,7 @@ pub struct JpegPositionState { } impl JpegPositionState { - pub fn new(jf: &JPegHeader, mcu: i32) -> Self { + pub fn new(jf: &JPegHeader, mcu: u32) -> Self { let cmp = jf.cs_cmp[0]; let mcumul = jf.cmp_info[cmp].sfv * jf.cmp_info[cmp].sfh; @@ -61,17 +61,17 @@ impl JpegPositionState { return state; } - pub fn get_mcu(&self) -> i32 { + pub fn get_mcu(&self) -> u32 { self.mcu } - pub fn get_dpos(&self) -> i32 { + pub fn get_dpos(&self) -> u32 { self.dpos } pub fn get_cmp(&self) -> usize { self.cmp } - pub fn get_cumulative_reset_markers(&self, jf: &JPegHeader) -> i32 { + pub fn get_cumulative_reset_markers(&self, jf: &JPegHeader) -> u32 { if self.rstw != 0 { self.get_mcu() / jf.rsti } else { @@ -129,7 +129,7 @@ impl JpegPositionState { } let mut sta = JPegDecodeStatus::DecodeInProgress; // status - let local_mcuh = jf.mcuh; + let local_mcuh = jf.mcuh.get(); let mut local_mcu = self.mcu; let mut local_cmp = self.cmp; @@ -202,18 +202,18 @@ impl JpegPositionState { // compare rst wait counter if needed if jf.rsti > 0 { - if i32::from(self.eobrun) > self.rstw { + if u32::from(self.eobrun) > self.rstw { return err_exit_code( ExitCode::UnsupportedJpeg, "skip_eobrun: eob run extends passed end of reset interval", ) .context(); } else { - self.rstw -= i32::from(self.eobrun); + self.rstw -= u32::from(self.eobrun); } } - fn checked_add(a: i32, b: i32) -> Result { + fn checked_add(a: u32, b: u32) -> Result { a.checked_add(b) .ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, "integer overflow")) } @@ -224,7 +224,7 @@ impl JpegPositionState { if cmp_info.bch != cmp_info.nch { self.dpos = checked_add( self.dpos, - (((self.dpos % cmp_info.bch) + i32::from(self.eobrun)) / cmp_info.nch) + (((self.dpos % cmp_info.bch) + u32::from(self.eobrun)) / cmp_info.nch) * (cmp_info.bch - cmp_info.nch), ) .context()?; @@ -237,7 +237,7 @@ impl JpegPositionState { } // skip blocks - self.dpos = checked_add(self.dpos, i32::from(self.eobrun)).context()?; + self.dpos = checked_add(self.dpos, u32::from(self.eobrun)).context()?; // reset eobrun self.eobrun = 0; diff --git a/src/structs/jpeg_write.rs b/src/structs/jpeg_write.rs index e3f23522..d82b7814 100644 --- a/src/structs/jpeg_write.rs +++ b/src/structs/jpeg_write.rs @@ -51,8 +51,8 @@ pub fn jpeg_write_baseline_row_range( encoded_length: usize, overhang_byte: u8, num_overhang_bits: u8, - luma_y_start: i32, - luma_y_end: i32, + luma_y_start: u32, + luma_y_end: u32, mut last_dc: [i16; 4], image_data: &[BlockBasedImage], jenc: &JPegEncodingInfo, @@ -92,7 +92,7 @@ pub fn jpeg_write_baseline_row_range( if cur_row.last_row_to_complete_mcu { recode_one_mcu_row( &mut huffw, - cur_row.mcu_row_index * jenc.jpeg_header.mcuh, + cur_row.mcu_row_index * jenc.jpeg_header.mcuh.get(), &mut last_dc, image_data, jenc, @@ -138,7 +138,7 @@ 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, + cur_row.mcu_row_index * jenc.jpeg_header.mcuh.get(), &mut last_dc, image_data, jenc, @@ -157,7 +157,7 @@ pub fn jpeg_write_entire_scan( #[inline(never)] fn recode_one_mcu_row( huffw: &mut BitWriter, - mcu: i32, + mcu: u32, lastdc: &mut [i16], framebuffer: &[BlockBasedImage], jenc: &JPegEncodingInfo, diff --git a/src/structs/lepton_decoder.rs b/src/structs/lepton_decoder.rs index ddf23f1a..79fa6b9c 100644 --- a/src/structs/lepton_decoder.rs +++ b/src/structs/lepton_decoder.rs @@ -35,8 +35,8 @@ pub fn lepton_decode_row_range( trunc: &TruncateComponents, image_data: &mut [BlockBasedImage], reader: &mut R, - min_y: i32, - max_y: i32, + min_y: u32, + max_y: u32, is_last_thread: bool, full_file_compression: bool, features: &EnabledFeatures, @@ -131,8 +131,8 @@ fn decode_row_wrapper( image_data: &mut BlockBasedImage, qt: &QuantizationTables, neighbor_summary_cache: &mut [NeighborSummary], - curr_y: i32, - component_size_in_blocks: i32, + curr_y: u32, + component_size_in_blocks: u32, features: &EnabledFeatures, ) -> Result<()> { let mut block_context = image_data.off_y(curr_y); diff --git a/src/structs/lepton_encoder.rs b/src/structs/lepton_encoder.rs index 82b316d1..82225ec5 100644 --- a/src/structs/lepton_encoder.rs +++ b/src/structs/lepton_encoder.rs @@ -34,8 +34,8 @@ pub fn lepton_encode_row_range( writer: &mut W, _thread_id: i32, colldata: &TruncateComponents, - min_y: i32, - max_y: i32, + min_y: u32, + max_y: u32, is_last_thread: bool, full_file_compression: bool, features: &EnabledFeatures, @@ -155,8 +155,8 @@ fn process_row( image_data: &BlockBasedImage, qt: &QuantizationTables, neighbor_summary_cache: &mut [NeighborSummary], - curr_y: i32, - component_size_in_block: i32, + curr_y: u32, + component_size_in_block: u32, features: &EnabledFeatures, ) -> Result<()> { let mut block_context = image_data.off_y(curr_y); diff --git a/src/structs/lepton_file_reader.rs b/src/structs/lepton_file_reader.rs index 52572738..5f048534 100644 --- a/src/structs/lepton_file_reader.rs +++ b/src/structs/lepton_file_reader.rs @@ -329,7 +329,7 @@ impl LeptonFileReader { let mut markers = Vec::new(); let cumulative_reset_markers = if lh.jpeg_header.rsti != 0 { - ((lh.jpeg_header.mcuh * lh.jpeg_header.mcuv) - 1) / lh.jpeg_header.rsti + (lh.jpeg_header.mcuc - 1) / lh.jpeg_header.rsti } else { 0 } as u8; diff --git a/src/structs/lepton_file_writer.rs b/src/structs/lepton_file_writer.rs index 8499c51f..2e37154d 100644 --- a/src/structs/lepton_file_writer.rs +++ b/src/structs/lepton_file_writer.rs @@ -162,11 +162,11 @@ pub fn read_jpeg( } let mut thread_handoff = Vec::::new(); - let start_scan = reader.stream_position()? as i32; + 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; - let mut end_scan = reader.stream_position()? as i32; + let mut end_scan = reader.stream_position()?.try_into().unwrap(); // need at least two bytes of scan data if start_scan + 2 > end_scan || thread_handoff.len() == 0 { @@ -207,7 +207,8 @@ pub fn read_jpeg( // and then fix up the broken file later in the decoder. The following logic will create a valid file // that the C++ and CS version will still decode properly without the fixup logic. let len = thread_handoff.len(); - thread_handoff[len - 1].segment_size -= 2; + thread_handoff[len - 1].segment_size = + thread_handoff[len - 1].segment_size.saturating_sub(2); } // rest of data is garbage data if it is a sequential jpeg (including EOI marker) @@ -239,7 +240,7 @@ pub fn read_jpeg( } } - end_scan = reader.stream_position()? as i32; + end_scan = reader.stream_position()? as u32; // since prepare_to_decode_next_scan consumes the EOI, // we need to add it to the beginning of the garbage data (if there is any) @@ -252,7 +253,7 @@ pub fn read_jpeg( } } - set_segment_size_in_row_thread_handoffs(&mut thread_handoff[..], end_scan as i32); + set_segment_size_in_row_thread_handoffs(&mut thread_handoff[..], end_scan); let merged_handoffs = split_row_handoffs_to_threads(&thread_handoff[..], enabled_features.max_threads as usize); lp.thread_handoff = merged_handoffs; @@ -450,7 +451,7 @@ fn prepare_to_decode_next_scan( return Ok(false); } - lp.max_bpos = cmp::max(lp.max_bpos, lp.jpeg_header.cs_to as i32); + lp.max_bpos = cmp::max(lp.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( @@ -459,7 +460,7 @@ fn prepare_to_decode_next_scan( ); for i in 0..lp.jpeg_header.cs_cmpc { - lp.max_cmp = cmp::max(lp.max_cmp, lp.jpeg_header.cs_cmp[i] as i32); + lp.max_cmp = cmp::max(lp.max_cmp, lp.jpeg_header.cs_cmp[i] as u32); } return Ok(true); @@ -467,7 +468,7 @@ fn prepare_to_decode_next_scan( fn set_segment_size_in_row_thread_handoffs( thread_handoffs: &mut [ThreadHandoff], - entropy_data_end_offset_in_file: i32, + entropy_data_end_offset_in_file: u32, ) { if thread_handoffs.len() != 0 { for i in 0..thread_handoffs.len() - 1 { diff --git a/src/structs/lepton_header.rs b/src/structs/lepton_header.rs index 4edf85d6..759051b2 100644 --- a/src/structs/lepton_header.rs +++ b/src/structs/lepton_header.rs @@ -39,7 +39,7 @@ pub struct LeptonHeader { /// 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, + 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, @@ -55,13 +55,13 @@ pub struct LeptonHeader { pub early_eof_encountered: bool, /// the maximum dpos in a truncated image - pub max_dpos: [i32; 4], + pub max_dpos: [u32; 4], /// the maximum component in a truncated image - pub max_cmp: i32, + pub max_cmp: u32, /// the maximum band in a truncated image - pub max_bpos: i32, + pub max_bpos: u32, /// the maximum bit in a truncated image pub max_sah: u8, @@ -193,10 +193,10 @@ impl LeptonHeader { // 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 { - let mut max_last_segment_size = i32::try_from(self.jpeg_file_size)? - - i32::try_from(self.garbage_data.len())? - - i32::try_from(self.raw_jpeg_header_read_index)? - - SOI.len() as i32; + 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)? + - u32::try_from(SOI.len())?; // subtract the segment sizes of all the previous segments (except for the last) for i in 0..num_threads - 1 { @@ -205,6 +205,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; @@ -283,7 +285,7 @@ impl LeptonHeader { let rst_count = header_reader.read_u32::()?; for _i in 0..rst_count { - self.rst_cnt.push(header_reader.read_i32::()?); + self.rst_cnt.push(header_reader.read_u32::()?); } } else if buffer_prefix_matches_marker( current_lepton_marker, @@ -325,13 +327,13 @@ impl LeptonHeader { current_lepton_marker, LEPTON_HEADER_EARLY_EOF_MARKER, ) { - self.max_cmp = header_reader.read_i32::()?; - self.max_bpos = header_reader.read_i32::()?; - self.max_sah = u8::try_from(header_reader.read_i32::()?)?; - self.max_dpos[0] = header_reader.read_i32::()?; - self.max_dpos[1] = header_reader.read_i32::()?; - self.max_dpos[2] = header_reader.read_i32::()?; - self.max_dpos[3] = header_reader.read_i32::()?; + 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; } else { return err_exit_code(ExitCode::BadLeptonFile, "unknown data found"); @@ -495,13 +497,13 @@ impl LeptonHeader { // EEE marker mrw.write_all(&LEPTON_HEADER_EARLY_EOF_MARKER)?; - mrw.write_i32::(self.max_cmp)?; - mrw.write_i32::(self.max_bpos)?; - mrw.write_i32::(i32::from(self.max_sah))?; - mrw.write_i32::(self.max_dpos[0])?; - mrw.write_i32::(self.max_dpos[1])?; - mrw.write_i32::(self.max_dpos[2])?; - mrw.write_i32::(self.max_dpos[3])?; + 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])?; } Ok(()) diff --git a/src/structs/row_spec.rs b/src/structs/row_spec.rs index d0262850..281ff48c 100644 --- a/src/structs/row_spec.rs +++ b/src/structs/row_spec.rs @@ -8,12 +8,12 @@ use crate::consts::COLOR_CHANNEL_NUM_BLOCK_TYPES; use crate::structs::block_based_image::BlockBasedImage; pub struct RowSpec { - pub min_row_luma_y: i32, - pub next_row_luma_y: i32, - pub luma_y: i32, + pub min_row_luma_y: u32, + pub next_row_luma_y: u32, + pub luma_y: u32, pub component: usize, - pub curr_y: i32, - pub mcu_row_index: i32, + pub curr_y: u32, + pub mcu_row_index: u32, pub last_row_to_complete_mcu: bool, pub skip: bool, pub done: bool, @@ -23,7 +23,7 @@ impl RowSpec { pub fn get_row_spec_from_index( decode_index: u32, image_data: &[BlockBasedImage], - mcuv: i32, // number of mcus + mcuv: u32, // number of mcus max_coded_heights: &[u32], ) -> RowSpec { assert!( @@ -44,14 +44,14 @@ impl RowSpec { } let mcu_row = decode_index / mcu_multiple; - let min_row_luma_y = (mcu_row * component_multiple[0]) as i32; + let min_row_luma_y = mcu_row * component_multiple[0]; let mut retval = RowSpec { skip: false, done: false, - mcu_row_index: mcu_row as i32, + mcu_row_index: mcu_row, component: num_cmp, min_row_luma_y, - next_row_luma_y: min_row_luma_y + component_multiple[0] as i32, + next_row_luma_y: min_row_luma_y + component_multiple[0], luma_y: min_row_luma_y, curr_y: 0, last_row_to_complete_mcu: false, @@ -63,11 +63,11 @@ impl RowSpec { loop { if place_within_scan < component_multiple[i] { retval.component = i; - retval.curr_y = ((mcu_row * component_multiple[i]) + place_within_scan) as i32; + retval.curr_y = (mcu_row * component_multiple[i]) + place_within_scan; retval.last_row_to_complete_mcu = (place_within_scan + 1 == component_multiple[i]) && (i == 0); - if retval.curr_y >= max_coded_heights[i] as i32 { + if retval.curr_y >= max_coded_heights[i] { retval.skip = true; retval.done = true; // assume true, but if we find something that needs coding, set false for j in 0..num_cmp - 1 { diff --git a/src/structs/thread_handoff.rs b/src/structs/thread_handoff.rs index 67247f02..0d65d08d 100644 --- a/src/structs/thread_handoff.rs +++ b/src/structs/thread_handoff.rs @@ -12,10 +12,10 @@ use crate::consts::COLOR_CHANNEL_NUM_BLOCK_TYPES; #[derive(Debug, Clone, PartialEq)] pub struct ThreadHandoff { - pub luma_y_start: i32, - pub luma_y_end: i32, - pub segment_offset_in_file: i32, - pub segment_size: i32, + pub luma_y_start: u32, + pub luma_y_end: u32, + pub segment_offset_in_file: u32, + pub segment_size: u32, pub overhang_byte: u8, pub num_overhang_bits: u8, pub last_dc: [i16; 4], @@ -27,10 +27,10 @@ impl ThreadHandoff { for _i in 0..num_threads { let mut th = ThreadHandoff { - luma_y_start: data.read_u16::()? as i32, + luma_y_start: data.read_u16::()? as u32, luma_y_end: 0, // filled in later segment_offset_in_file: 0, // not serialized - segment_size: data.read_i32::()?, + segment_size: data.read_u32::()?, overhang_byte: data.read_u8()?, num_overhang_bits: data.read_u8()?, last_dc: [0; 4], @@ -92,7 +92,7 @@ impl ThreadHandoff { overhang_byte: from.overhang_byte, num_overhang_bits: from.num_overhang_bits, luma_y_end: to.luma_y_end, - segment_size: ThreadHandoff::get_combine_thread_range_segment_size(from, to) as i32, + segment_size: ThreadHandoff::get_combine_thread_range_segment_size(from, to) as u32, last_dc: from.last_dc, }; diff --git a/src/structs/truncate_components.rs b/src/structs/truncate_components.rs index c872333b..04cf18a8 100644 --- a/src/structs/truncate_components.rs +++ b/src/structs/truncate_components.rs @@ -11,9 +11,9 @@ use crate::structs::jpeg_header::JPegHeader; #[derive(Debug, Clone)] struct TrucateComponentsInfo { - trunc_bcv: i32, // the number of vertical components in this (truncated) image + trunc_bcv: u32, // the number of vertical components in this (truncated) image - trunc_bc: i32, + trunc_bc: u32, } #[derive(Debug, Clone)] @@ -22,9 +22,9 @@ pub struct TruncateComponents { pub components_count: usize, - pub mcu_count_horizontal: i32, + pub mcu_count_horizontal: u32, - pub mcu_count_vertical: i32, + pub mcu_count_vertical: u32, } impl Default for TruncateComponents { @@ -40,8 +40,8 @@ impl Default for TruncateComponents { impl TruncateComponents { pub fn init(&mut self, jpeg_header: &JPegHeader) { - self.mcu_count_horizontal = jpeg_header.mcuh; - self.mcu_count_vertical = jpeg_header.mcuv; + self.mcu_count_horizontal = jpeg_header.mcuh.get(); + self.mcu_count_vertical = jpeg_header.mcuv.get(); self.components_count = jpeg_header.cmpc; for i in 0..jpeg_header.cmpc { @@ -61,7 +61,7 @@ impl TruncateComponents { return retval; } - pub fn set_truncation_bounds(&mut self, jpeg_header: &JPegHeader, max_d_pos: [i32; 4]) { + pub fn set_truncation_bounds(&mut self, jpeg_header: &JPegHeader, max_d_pos: [u32; 4]) { for i in 0..self.components_count { TruncateComponents::set_block_count_d_pos( &mut self.trunc_info[i], @@ -72,15 +72,15 @@ impl TruncateComponents { } } - pub fn get_block_height(&self, cmp: usize) -> i32 { + pub fn get_block_height(&self, cmp: usize) -> u32 { return self.trunc_info[cmp].trunc_bcv; } fn set_block_count_d_pos( ti: &mut TrucateComponentsInfo, ci: &ComponentInfo, - trunc_bc: i32, - mcu_count_vertical: i32, + trunc_bc: u32, + mcu_count_vertical: u32, ) { assert!( ci.bcv == (ci.bc / ci.bch) + (if ci.bc % ci.bch != 0 { 1 } else { 0 }), @@ -105,12 +105,12 @@ impl TruncateComponents { ti.trunc_bc = trunc_bc; } - fn get_min_vertical_extcmp_multiple(cmp_info: &ComponentInfo, mcu_count_vertical: i32) -> i32 { + fn get_min_vertical_extcmp_multiple(cmp_info: &ComponentInfo, mcu_count_vertical: u32) -> u32 { let luma_height = cmp_info.bcv; return luma_height / mcu_count_vertical; } - pub fn get_component_sizes_in_blocks(&self) -> Vec { + pub fn get_component_sizes_in_blocks(&self) -> Vec { let mut retval = Vec::new(); for i in 0..self.components_count { retval.push(self.trunc_info[i].trunc_bc); From 7aa04dca8b942d1eedc8b7cb4373b0bad6945376 Mon Sep 17 00:00:00 2001 From: Kristof Date: Mon, 2 Dec 2024 10:50:27 +0100 Subject: [PATCH 02/14] clean up trival casts --- src/structs/bit_reader.rs | 4 +- src/structs/bit_writer.rs | 4 +- src/structs/block_based_image.rs | 4 +- src/structs/branch.rs | 2 +- src/structs/jpeg_header.rs | 91 +++++++++++++++++++++--------- src/structs/jpeg_read.rs | 4 +- src/structs/lepton_decoder.rs | 2 +- src/structs/lepton_file_reader.rs | 4 +- src/structs/lepton_file_writer.rs | 2 +- src/structs/lepton_header.rs | 4 +- src/structs/mod.rs | 1 + src/structs/quantization_tables.rs | 2 +- src/structs/row_spec.rs | 4 +- src/structs/simple_hash.rs | 4 +- src/structs/truncate_components.rs | 2 +- 15 files changed, 85 insertions(+), 49 deletions(-) diff --git a/src/structs/bit_reader.rs b/src/structs/bit_reader.rs index e2f1eae0..08dbb637 100644 --- a/src/structs/bit_reader.rs +++ b/src/structs/bit_reader.rs @@ -299,7 +299,7 @@ use std::io::Cursor; // test reading a simple bit pattern with an escaped 0xff inside it. #[test] fn read_simple() { - let arr = [0x12 as u8, 0x34, 0x45, 0x67, 0x89, 0xff, 00, 0xee]; + let arr = [0x12u8, 0x34, 0x45, 0x67, 0x89, 0xff, 00, 0xee]; let mut b = BitReader::new(Cursor::new(&arr)); @@ -338,7 +338,7 @@ fn read_simple() { // what happens when a file has 0xff as the last character (assume that it is an escaped 0xff) #[test] fn read_truncate_ff() { - let arr = [0x12 as u8, 0xff]; + let arr = [0x12u8, 0xff]; let mut b = BitReader::new(Cursor::new(&arr)); diff --git a/src/structs/bit_writer.rs b/src/structs/bit_writer.rs index 57cefc4c..379c26e1 100644 --- a/src/structs/bit_writer.rs +++ b/src/structs/bit_writer.rs @@ -151,7 +151,7 @@ use crate::structs::bit_reader::BitReader; // write a test pattern with an escape and see if it matches #[test] fn write_simple() { - let arr = [0x12 as u8, 0x34, 0x45, 0x67, 0x89, 0xff, 00, 0xee]; + let arr = [0x12, 0x34, 0x45, 0x67, 0x89, 0xff, 00, 0xee]; let mut b = BitWriter::new(1024); @@ -178,7 +178,7 @@ fn roundtrip_bits() { { let mut b = BitWriter::new(1024); for i in 1..2048 { - b.write(i, u32_bit_length(i as u32) as u32); + b.write(i, u32_bit_length(i) as u32); } b.pad(0xff); diff --git a/src/structs/block_based_image.rs b/src/structs/block_based_image.rs index 8a4982ee..eb79f233 100644 --- a/src/structs/block_based_image.rs +++ b/src/structs/block_based_image.rs @@ -149,7 +149,7 @@ impl BlockBasedImage { if relative_offset < self.image.len() { // rewrite already written block if let Some(b) = block_to_write { - self.image[relative_offset as usize] = b; + self.image[relative_offset] = b; } } else { // need to extend the image length and add any necessary @@ -166,7 +166,7 @@ impl BlockBasedImage { self.image.push(block_to_write.unwrap_or_default()); } - return &mut self.image[relative_offset as usize]; + return &mut self.image[relative_offset]; } pub fn set_block_data(&mut self, dpos: u32, block_data: AlignedBlock) { diff --git a/src/structs/branch.rs b/src/structs/branch.rs index 965c9e4e..03de87ce 100644 --- a/src/structs/branch.rs +++ b/src/structs/branch.rs @@ -231,7 +231,7 @@ fn test_all_probabilities() { continue; } - let mut new_f = Branch { counts: i as u16 }; + let mut new_f = Branch { counts: i }; for _k in 0..10 { old_f.record_obs_and_update(false); diff --git a/src/structs/jpeg_header.rs b/src/structs/jpeg_header.rs index 2c4542f8..a8e8571e 100644 --- a/src/structs/jpeg_header.rs +++ b/src/structs/jpeg_header.rs @@ -44,6 +44,7 @@ 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; #[derive(Copy, Clone, Debug)] pub struct HuffCodes { @@ -268,31 +269,68 @@ impl HuffTree { #[derive(Debug, Clone)] pub struct JPegHeader { - pub q_tables: [[u16; 64]; 4], // quantization tables 4 x 64 - h_codes: [[HuffCodes; 4]; 2], // huffman codes (access via get_huff_xx_codes) - h_trees: [[HuffTree; 4]; 2], // huffman decoding trees (access via get_huff_xx_tree) - pub ht_set: [[u8; 4]; 2], // 1 if huffman table is set - pub cmp_info: [ComponentInfo; 4], // components - pub cmpc: usize, // component count - pub img_width: u32, // width of image - pub img_height: u32, // height of image + /// quantization tables 4 x 64 + pub q_tables: [[u16; 64]; 4], + + /// huffman codes (access via get_huff_xx_codes) + h_codes: [[HuffCodes; 4]; 2], + + /// huffman decoding trees (access via get_huff_xx_tree) + h_trees: [[HuffTree; 4]; 2], + + /// 1 if huffman table is set + pub ht_set: [[u8; 4]; 2], + + /// components + pub cmp_info: [ComponentInfo; 4], + + /// component count + pub cmpc: usize, + + /// width of image + pub img_width: u32, + + /// height of image + pub img_height: u32, pub jpeg_type: JPegType, - pub sfhm: u32, // max horizontal sample factor - pub sfvm: u32, // max verical sample factor - pub mcuv: NonZeroU32, // mcus per line - pub mcuh: NonZeroU32, // mcus per collumn - pub mcuc: u32, // count of mcus - pub rsti: u32, // restart interval - pub cs_cmpc: usize, // component count in current scan - pub cs_cmp: [usize; 4], // component numbers in current scan + /// max horizontal sample factor + pub sfhm: u32, + + /// max verical sample factor + pub sfvm: u32, + + // mcus per line + pub mcuv: NonZeroU32, + + /// mcus per column + pub mcuh: NonZeroU32, + + /// count of mcus + pub mcuc: u32, + + /// restart interval + pub rsti: u32, + + /// component count in current scan + pub cs_cmpc: usize, + + /// component numbers in current scan + pub cs_cmp: [usize; 4], // variables: info about current scan - pub cs_from: u8, // begin - band of current scan ( inclusive ) - pub cs_to: u8, // end - band of current scan ( inclusive ) - pub cs_sah: u8, // successive approximation bit pos high - pub cs_sal: u8, // successive approximation bit pos low + /// begin - band of current scan ( inclusive ) + pub cs_from: u8, + + /// end - band of current scan ( inclusive ) + pub cs_to: u8, + + /// successive approximation bit pos high + pub cs_sah: u8, + + /// successive approximation bit pos low + pub cs_sal: u8, } pub struct JPegEncodingInfo { @@ -442,10 +480,12 @@ impl JPegHeader { self.mcuv = NonZeroU32::new( (1.0 * self.img_height as f64 / (8.0 * self.sfhm as f64)).ceil() as u32, ) - .unwrap(); + .ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, "mcuv is zero"))?; + self.mcuh = NonZeroU32::new((1.0 * self.img_width as f64 / (8.0 * self.sfvm as f64)).ceil() as u32) - .unwrap(); + .ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, "mcuh is zero"))?; + self.mcuc = self.mcuv.get() * self.mcuh.get(); for cmp in 0..self.cmpc { @@ -1004,12 +1044,7 @@ pub fn generate_huff_table_from_distribution(freq: &[usize; 256]) -> HuffCodes { pq.pop().unwrap() } - fn generate_codes( - root: &Node, - codes: &mut std::collections::HashMap, - prefix: u16, - length: u8, - ) { + fn generate_codes(root: &Node, codes: &mut HashMap, prefix: u16, length: u8) { if let Some(symbol) = root.symbol { codes.insert(symbol, (prefix, length)); } else { diff --git a/src/structs/jpeg_read.rs b/src/structs/jpeg_read.rs index 5908997b..817f0919 100644 --- a/src/structs/jpeg_read.rs +++ b/src/structs/jpeg_read.rs @@ -616,7 +616,7 @@ fn decode_ac_prg_fs( } else { // decode eobrun let s = l; - let n = bit_reader.read(u32::from(s))? as u16; + let n = bit_reader.read(u32::from(s))?; state.eobrun = decode_eobrun_bits(s, n); state.eobrun -= 1; // decrement eobrun ( for this one ) @@ -694,7 +694,7 @@ fn decode_ac_prg_sa( // decode eobrun eob = bpos; let s = l; - let n = bit_reader.read(u32::from(s))? as u16; + let n = bit_reader.read(u32::from(s))?; state.eobrun = decode_eobrun_bits(s, n); // since we hit EOB, the rest can be done with the zero block decoder diff --git a/src/structs/lepton_decoder.rs b/src/structs/lepton_decoder.rs index 79fa6b9c..792993fe 100644 --- a/src/structs/lepton_decoder.rs +++ b/src/structs/lepton_decoder.rs @@ -458,7 +458,7 @@ fn decode_one_edge( if coef != 0 { num_non_zeros_edge -= 1; here_mut.set_coefficient(coord_tr, coef); - raster[coord_tr as usize] = + raster[coord_tr] = i32::from(coef) * i32::from(qt.get_quantization_table_transposed()[coord_tr]); } diff --git a/src/structs/lepton_file_reader.rs b/src/structs/lepton_file_reader.rs index 5f048534..12d28245 100644 --- a/src/structs/lepton_file_reader.rs +++ b/src/structs/lepton_file_reader.rs @@ -334,8 +334,8 @@ impl LeptonFileReader { 0 } as u8; - for i in 0..lh.rst_err[0] as u8 { - let rst = (jpeg_code::RST0 + ((cumulative_reset_markers + i) & 7)) as u8; + for i in 0..lh.rst_err[0] { + let rst = jpeg_code::RST0 + ((cumulative_reset_markers + i) & 7); markers.push(0xFF); markers.push(rst); } diff --git a/src/structs/lepton_file_writer.rs b/src/structs/lepton_file_writer.rs index 2e37154d..06b15698 100644 --- a/src/structs/lepton_file_writer.rs +++ b/src/structs/lepton_file_writer.rs @@ -380,7 +380,7 @@ fn split_row_handoffs_to_threads( info!("Number of threads: {0}", num_threads); - let mut selected_splits = Vec::with_capacity(num_threads as usize); + let mut selected_splits = Vec::with_capacity(num_threads); if num_threads == 1 { // Single thread execution - no split, run on the whole range diff --git a/src/structs/lepton_header.rs b/src/structs/lepton_header.rs index 759051b2..ced853bb 100644 --- a/src/structs/lepton_header.rs +++ b/src/structs/lepton_header.rs @@ -262,7 +262,7 @@ impl LeptonHeader { // beginning here: recovery information (needed for exact JPEG recovery) // read further recovery information if any loop { - let mut current_lepton_marker = [0 as u8; 3]; + let mut current_lepton_marker = [0u8; 3]; match header_reader.read_exact(&mut current_lepton_marker) { Ok(_) => {} Err(e) => { @@ -468,7 +468,7 @@ impl LeptonHeader { mrw.write_u32::(self.rst_cnt.len() as u32)?; for i in 0..self.rst_cnt.len() { - mrw.write_u32::(self.rst_cnt[i] as u32)?; + mrw.write_u32::(self.rst_cnt[i])?; } } diff --git a/src/structs/mod.rs b/src/structs/mod.rs index fc423e3a..4bef598f 100644 --- a/src/structs/mod.rs +++ b/src/structs/mod.rs @@ -7,6 +7,7 @@ // Don't allow any unsafe code by default. Since this code has to potentially deal with // badly/maliciously formatted images, we want this extra level of safety. #![forbid(unsafe_code)] +#![forbid(trivial_numeric_casts)] mod bit_reader; mod bit_writer; diff --git a/src/structs/quantization_tables.rs b/src/structs/quantization_tables.rs index 8c2e1a41..32844cbd 100644 --- a/src/structs/quantization_tables.rs +++ b/src/structs/quantization_tables.rs @@ -51,7 +51,7 @@ impl QuantizationTables { freq_max /= retval.quantization_table[coord]; } - let max_len = u16_bit_length(freq_max) as u8; + let max_len = u16_bit_length(freq_max); if max_len > RESIDUAL_NOISE_FLOOR as u8 { retval.min_noise_threshold[i] = max_len - RESIDUAL_NOISE_FLOOR as u8; } diff --git a/src/structs/row_spec.rs b/src/structs/row_spec.rs index 281ff48c..367a6e2b 100644 --- a/src/structs/row_spec.rs +++ b/src/structs/row_spec.rs @@ -38,8 +38,8 @@ impl RowSpec { let mut mcu_multiple = 0; for i in 0..num_cmp { - heights.push(image_data[i].get_original_height() as u32); - component_multiple.push(heights[i] / mcuv as u32); + heights.push(image_data[i].get_original_height()); + component_multiple.push(heights[i] / mcuv); mcu_multiple += component_multiple[i]; } diff --git a/src/structs/simple_hash.rs b/src/structs/simple_hash.rs index 821cd178..f36f6848 100644 --- a/src/structs/simple_hash.rs +++ b/src/structs/simple_hash.rs @@ -31,7 +31,7 @@ impl SimpleHashProvider for u32 { impl SimpleHashProvider for u64 { fn get_u64(&self) -> u64 { - return *self as u64; + return *self; } } @@ -41,7 +41,7 @@ impl SimpleHash { } pub fn hash(&mut self, v: T) { - self.hash = (Wrapping(self.hash as u64) * Wrapping(13u64) + Wrapping(v.get_u64())).0; + self.hash = (Wrapping(self.hash) * Wrapping(13u64) + Wrapping(v.get_u64())).0; } pub fn get(&self) -> u32 { diff --git a/src/structs/truncate_components.rs b/src/structs/truncate_components.rs index 04cf18a8..513aa390 100644 --- a/src/structs/truncate_components.rs +++ b/src/structs/truncate_components.rs @@ -56,7 +56,7 @@ impl TruncateComponents { let mut retval = Vec::::new(); for i in 0..self.components_count { - retval.push(self.trunc_info[i].trunc_bcv as u32); + retval.push(self.trunc_info[i].trunc_bcv); } return retval; } From 60329371650f072c59a9242fcf21584be3bf346c Mon Sep 17 00:00:00 2001 From: Kristof Date: Tue, 3 Dec 2024 09:34:00 +0100 Subject: [PATCH 03/14] refactor jpeg reader/writer to be independent of Lepton encoder --- src/{structs => jpeg}/bit_reader.rs | 0 src/{structs => jpeg}/bit_writer.rs | 4 +- src/{structs => jpeg}/block_based_image.rs | 14 +- src/{structs => jpeg}/component_info.rs | 0 src/{structs => jpeg}/jpeg_header.rs | 120 ++++++++++------- src/{structs => jpeg}/jpeg_position_state.rs | 2 +- src/{structs => jpeg}/jpeg_read.rs | 134 ++++++++----------- src/{structs => jpeg}/jpeg_write.rs | 43 +++--- src/jpeg/mod.rs | 11 ++ src/{structs => jpeg}/row_spec.rs | 3 +- src/{structs => jpeg}/truncate_components.rs | 3 +- src/lib.rs | 1 + src/structs/block_context.rs | 24 +++- src/structs/idct.rs | 2 +- src/structs/lepton_decoder.rs | 8 +- src/structs/lepton_encoder.rs | 10 +- src/structs/lepton_file_reader.rs | 38 ++++-- src/structs/lepton_file_writer.rs | 58 +++++--- src/structs/lepton_header.rs | 79 +++++------ src/structs/mod.rs | 10 -- src/structs/probability_tables.rs | 2 +- src/structs/quantization_tables.rs | 28 +++- 22 files changed, 331 insertions(+), 263 deletions(-) rename src/{structs => jpeg}/bit_reader.rs (100%) rename src/{structs => jpeg}/bit_writer.rs (99%) rename src/{structs => jpeg}/block_based_image.rs (95%) rename src/{structs => jpeg}/component_info.rs (100%) rename src/{structs => jpeg}/jpeg_header.rs (95%) rename src/{structs => jpeg}/jpeg_position_state.rs (99%) rename src/{structs => jpeg}/jpeg_read.rs (87%) rename src/{structs => jpeg}/jpeg_write.rs (95%) create mode 100644 src/jpeg/mod.rs rename src/{structs => jpeg}/row_spec.rs (98%) rename src/{structs => jpeg}/truncate_components.rs (97%) 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 95% rename from src/structs/jpeg_header.rs rename to src/jpeg/jpeg_header.rs index a8e8571e..8ee92ee6 100644 --- a/src/structs/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -40,12 +40,77 @@ 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 super::component_info::ComponentInfo; +use super::truncate_components::TruncateComponents; + +/// information required to restart coding the huffman encoded stream at an arbitrary +/// location in the stream. +/// +/// This is used to partition the JPEG into multiple segments +/// that can be used to decode it in multiple threads. +/// +/// Each segment has its own unique restart info +#[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 band in a truncated image + pub max_bpos: u32, + + /// 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, + + /// count of scans encountered so far + pub scnc: usize, + + /// the maximum bit in a truncated image + pub max_sah: u8, + + /// 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, + + pub rst_cnt_set: bool, +} + #[derive(Copy, Clone, Debug)] pub struct HuffCodes { pub c_val: [u16; 256], @@ -336,31 +401,7 @@ pub struct JPegHeader { 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, - } - } + pub rinfo: ReconstructionInfo, } enum ParseSegmentResult { @@ -952,27 +993,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<()> { 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 87% rename from src/structs/jpeg_read.rs rename to src/jpeg/jpeg_read.rs index 817f0919..7b92c0e1 100644 --- a/src/structs/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -39,42 +39,41 @@ 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 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; + +pub fn read_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 { @@ -90,12 +89,17 @@ pub fn read_scan( // won't mean much, but we do need to divide the scan into sections if do_handoff { - crystallize_thread_handoff( - &state, - lp, - &mut bit_reader, - thread_handoff, - last_dc, + let (num_overhang_bits, overhang_byte) = bit_reader.overhang(); + + partition( + bit_reader.get_stream_position(), + RestartSegmentCodingInfo::new( + overhang_byte, + num_overhang_bits, + last_dc, + state.get_mcu(), + jf, + ), ); do_handoff = false; @@ -114,7 +118,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 +133,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. @@ -141,64 +145,31 @@ pub fn read_scan( } } - lp.scnc += 1; // increment scan counter + reconstruct_info.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 pub fn read_progressive_scan( - lp: &mut LeptonHeader, + jf: &mut 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 +326,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 +338,56 @@ pub fn read_progressive_scan( } } - lp.scnc += 1; // increment scan counter + reconstruct_info.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 +409,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; } } diff --git a/src/structs/jpeg_write.rs b/src/jpeg/jpeg_write.rs similarity index 95% rename from src/structs/jpeg_write.rs rename to src/jpeg/jpeg_write.rs index d82b7814..53b06c4d 100644 --- a/src/structs/jpeg_write.rs +++ b/src/jpeg/jpeg_write.rs @@ -38,29 +38,32 @@ 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}; +use super::bit_writer::BitWriter; +use super::block_based_image::{AlignedBlock, BlockBasedImage}; +use super::jpeg_header::{HuffCodes, JPegEncodingInfo, RestartSegmentCodingInfo}; +use super::jpeg_position_state::JpegPositionState; +use super::row_spec::RowSpec; + /// write a range of rows corresponding to the thread_handoff structure into the writer. /// 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, ) -> Result> { let max_coded_heights = jenc.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 { @@ -81,11 +84,11 @@ 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 } @@ -298,7 +301,7 @@ fn recode_one_mcu_row( } // pad huffman writer - huffw.pad(jenc.pad_bit.unwrap_or(0)); + huffw.pad(jenc.rinfo.pad_bit.unwrap_or(0)); assert!( huffw.has_no_remainder(), @@ -313,9 +316,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 jenc.rinfo.rst_cnt.len() == 0 + || (!jenc.rinfo.rst_cnt_set) + || cumulative_reset_markers < jenc.rinfo.rst_cnt[jenc.rinfo.scnc] { let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8; @@ -627,9 +630,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..f46a6a2a --- /dev/null +++ b/src/jpeg/mod.rs @@ -0,0 +1,11 @@ +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..a2ed807b 100644 --- a/src/structs/truncate_components.rs +++ b/src/jpeg/truncate_components.rs @@ -6,8 +6,7 @@ use std::cmp; -use crate::structs::component_info::*; -use crate::structs::jpeg_header::JPegHeader; +use super::{component_info::ComponentInfo, jpeg_header::JPegHeader}; #[derive(Debug, Clone)] struct TrucateComponentsInfo { diff --git a/src/lib.rs b/src/lib.rs index 087ac4f1..1f4f88bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod consts; mod helpers; +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..cc92c462 100644 --- a/src/structs/block_context.rs +++ b/src/structs/block_context.rs @@ -4,7 +4,7 @@ * 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 { @@ -23,6 +23,28 @@ pub struct NeighborData<'a> { } impl BlockContext { + // blocks above the first line are never dereferenced + pub fn off_y(y: u32, image_data: &BlockBasedImage) -> BlockContext { + return BlockContext::new( + image_data.get_block_width() * y, + if y > 0 { + image_data.get_block_width() * (y - 1) + } else { + 0 + }, + if (y & 1) != 0 { + image_data.get_block_width() + } else { + 0 + }, + if (y & 1) != 0 { + 0 + } else { + image_data.get_block_width() + }, + ); + } + // for debugging #[allow(dead_code)] pub fn get_here_index(&self) -> u32 { 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..3d18174a 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::{JPegEncodingInfo, 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}; @@ -382,8 +382,14 @@ impl LeptonFileReader { results.push(header); loop { + let jenc = JPegEncodingInfo { + jpeg_header: lh.jpeg_header.clone(), + truncate_components: lh.truncate_components.clone(), + rinfo: lh.rinfo.clone(), + }; + // 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[..], &jenc).context()?; results.push(scan); // read the next headers (DHT, etc) while mirroring it back to the writer @@ -397,7 +403,7 @@ impl LeptonFileReader { } // advance to next scan - lh.scnc += 1; + lh.rinfo.scnc += 1; } Ok(DecoderState::AppendTrailer(results)) @@ -430,13 +436,17 @@ impl LeptonFileReader { &enabled_features, 4, /*retain 4 bytes for the end for the file size that is appended */ |thread_handoff, image_data, jenc| { + 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, ) @@ -507,11 +517,15 @@ impl LeptonFileReader { jenc: &JPegEncodingInfo, ) -> 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 jenc = JPegEncodingInfo { + jpeg_header: lh.jpeg_header.clone(), + truncate_components: lh.truncate_components.clone(), + rinfo: lh.rinfo.clone(), + }; let thread_handoff = lh.thread_handoff.clone(); diff --git a/src/structs/lepton_file_writer.rs b/src/structs/lepton_file_writer.rs index 06b15698..20f4b037 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_progressive_scan, read_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( @@ -163,8 +164,27 @@ 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_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()?; + lp.rinfo.scnc += 1; let mut end_scan = reader.stream_position()?.try_into().unwrap(); @@ -192,9 +212,9 @@ pub fn read_jpeg( } if lp.jpeg_header.jpeg_type == JPegType::Sequential { - if lp.early_eof_encountered { + if lp.rinfo.early_eof_encountered { lp.truncate_components - .set_truncation_bounds(&lp.jpeg_header, lp.max_dpos); + .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,16 @@ 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( + &mut lp.jpeg_header, + reader, + &mut image_data[..], + &mut lp.rinfo, + ) + .context()?; + lp.rinfo.scnc += 1; - 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 +341,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,11 +477,11 @@ 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), ); diff --git a/src/structs/lepton_header.rs b/src/structs/lepton_header.rs index ced853bb..ba056170 100644 --- a/src/structs/lepton_header.rs +++ b/src/structs/lepton_header.rs @@ -8,10 +8,10 @@ use flate2::Compression; use crate::consts::*; use crate::helpers::buffer_prefix_matches_marker; +use crate::jpeg::jpeg_header::{JPegHeader, ReconstructionInfo}; +use crate::jpeg::truncate_components::TruncateComponents; 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; @@ -37,34 +37,13 @@ pub struct LeptonHeader { 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, @@ -179,9 +158,9 @@ impl LeptonHeader { self.truncate_components.init(&self.jpeg_header); - if self.early_eof_encountered { + if self.rinfo.early_eof_encountered { self.truncate_components - .set_truncation_bounds(&self.jpeg_header, self.max_dpos); + .set_truncation_bounds(&self.jpeg_header, self.rinfo.max_dpos); } let num_threads = self.thread_handoff.len(); @@ -192,7 +171,7 @@ impl LeptonHeader { // 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)? @@ -275,17 +254,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, @@ -328,13 +309,13 @@ impl LeptonHeader { 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_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"); } @@ -445,7 +426,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(()) } @@ -461,14 +442,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])?; } } @@ -493,17 +474,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_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..cc1b30c5 100644 --- a/src/structs/quantization_tables.rs +++ b/src/structs/quantization_tables.rs @@ -6,7 +6,10 @@ 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; +use crate::Result; pub struct QuantizationTables { quantization_table: [u16; 64], @@ -61,6 +64,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 } From d90dea2d9a809bab8f7a803235fa366193e73f70 Mon Sep 17 00:00:00 2001 From: Kristof Date: Tue, 3 Dec 2024 10:21:56 +0100 Subject: [PATCH 04/14] work --- src/jpeg/jpeg_header.rs | 22 +++--- src/jpeg/jpeg_read.rs | 2 +- src/lib.rs | 2 +- src/structs/truncate_components.rs | 119 +++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 src/structs/truncate_components.rs diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index 8ee92ee6..42318271 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -112,7 +112,7 @@ pub struct ReconstructionInfo { } #[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], @@ -220,7 +220,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], } @@ -332,6 +332,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 @@ -344,7 +345,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], @@ -445,26 +446,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, @@ -1005,7 +1009,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/jpeg/jpeg_read.rs b/src/jpeg/jpeg_read.rs index 7b92c0e1..fac6d524 100644 --- a/src/jpeg/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -428,7 +428,7 @@ fn decode_baseline_rst #[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/lib.rs b/src/lib.rs index 1f4f88bf..bdbceeda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ mod consts; mod helpers; -mod jpeg; +pub mod jpeg; mod jpeg_code; pub mod metrics; mod structs; diff --git a/src/structs/truncate_components.rs b/src/structs/truncate_components.rs new file mode 100644 index 00000000..a2ed807b --- /dev/null +++ b/src/structs/truncate_components.rs @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. + * This software incorporates material from third parties. See NOTICE.txt for details. + *--------------------------------------------------------------------------------------------*/ + +use std::cmp; + +use super::{component_info::ComponentInfo, jpeg_header::JPegHeader}; + +#[derive(Debug, Clone)] +struct TrucateComponentsInfo { + trunc_bcv: u32, // the number of vertical components in this (truncated) image + + trunc_bc: u32, +} + +#[derive(Debug, Clone)] +pub struct TruncateComponents { + trunc_info: Vec, + + pub components_count: usize, + + pub mcu_count_horizontal: u32, + + pub mcu_count_vertical: u32, +} + +impl Default for TruncateComponents { + fn default() -> Self { + return TruncateComponents { + trunc_info: Vec::new(), + components_count: 0, + mcu_count_horizontal: 0, + mcu_count_vertical: 0, + }; + } +} + +impl TruncateComponents { + pub fn init(&mut self, jpeg_header: &JPegHeader) { + self.mcu_count_horizontal = jpeg_header.mcuh.get(); + self.mcu_count_vertical = jpeg_header.mcuv.get(); + self.components_count = jpeg_header.cmpc; + + for i in 0..jpeg_header.cmpc { + self.trunc_info.push(TrucateComponentsInfo { + trunc_bcv: jpeg_header.cmp_info[i].bcv, + trunc_bc: jpeg_header.cmp_info[i].bc, + }); + } + } + + pub fn get_max_coded_heights(&self) -> Vec { + let mut retval = Vec::::new(); + + for i in 0..self.components_count { + retval.push(self.trunc_info[i].trunc_bcv); + } + return retval; + } + + pub fn set_truncation_bounds(&mut self, jpeg_header: &JPegHeader, max_d_pos: [u32; 4]) { + for i in 0..self.components_count { + TruncateComponents::set_block_count_d_pos( + &mut self.trunc_info[i], + &jpeg_header.cmp_info[i], + max_d_pos[i] + 1, + self.mcu_count_vertical, + ); + } + } + + pub fn get_block_height(&self, cmp: usize) -> u32 { + return self.trunc_info[cmp].trunc_bcv; + } + + fn set_block_count_d_pos( + ti: &mut TrucateComponentsInfo, + ci: &ComponentInfo, + trunc_bc: u32, + mcu_count_vertical: u32, + ) { + assert!( + ci.bcv == (ci.bc / ci.bch) + (if ci.bc % ci.bch != 0 { 1 } else { 0 }), + "SetBlockCountDpos" + ); + + let mut vertical_scan_lines = cmp::min( + (trunc_bc / ci.bch) + (if trunc_bc % ci.bch != 0 { 1 } else { 0 }), + ci.bcv, + ); + let ratio = TruncateComponents::get_min_vertical_extcmp_multiple(&ci, mcu_count_vertical); + + while vertical_scan_lines % ratio != 0 && vertical_scan_lines + 1 <= ci.bcv { + vertical_scan_lines += 1; + } + + assert!( + vertical_scan_lines <= ci.bcv, + "verticalScanLines <= ci.Info.bcv" + ); + ti.trunc_bcv = vertical_scan_lines; + ti.trunc_bc = trunc_bc; + } + + fn get_min_vertical_extcmp_multiple(cmp_info: &ComponentInfo, mcu_count_vertical: u32) -> u32 { + let luma_height = cmp_info.bcv; + return luma_height / mcu_count_vertical; + } + + pub fn get_component_sizes_in_blocks(&self) -> Vec { + let mut retval = Vec::new(); + for i in 0..self.components_count { + retval.push(self.trunc_info[i].trunc_bc); + } + return retval; + } +} From c452ebd4f4ef429a51890610696ef025e766982d Mon Sep 17 00:00:00 2001 From: Kristof Date: Tue, 3 Dec 2024 10:37:05 +0100 Subject: [PATCH 05/14] removed Jpegencoding info not needed anymore --- src/jpeg/jpeg_header.rs | 10 +-- src/jpeg/jpeg_write.rs | 39 +++++----- src/structs/lepton_file_reader.rs | 54 ++++++------- src/structs/lepton_file_writer.rs | 7 +- src/structs/lepton_header.rs | 11 +-- src/structs/truncate_components.rs | 119 ----------------------------- 6 files changed, 59 insertions(+), 181 deletions(-) delete mode 100644 src/structs/truncate_components.rs diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index 42318271..e935e1af 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -109,6 +109,9 @@ pub struct ReconstructionInfo { pub rst_cnt: Vec, 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)] @@ -398,13 +401,6 @@ pub struct JPegHeader { /// successive approximation bit pos low pub cs_sal: u8, } - -pub struct JPegEncodingInfo { - pub jpeg_header: JPegHeader, - pub truncate_components: TruncateComponents, - pub rinfo: ReconstructionInfo, -} - enum ParseSegmentResult { Continue, EOI, diff --git a/src/jpeg/jpeg_write.rs b/src/jpeg/jpeg_write.rs index 53b06c4d..223ea5c9 100644 --- a/src/jpeg/jpeg_write.rs +++ b/src/jpeg/jpeg_write.rs @@ -43,7 +43,7 @@ use crate::{jpeg_code, Result}; use super::bit_writer::BitWriter; use super::block_based_image::{AlignedBlock, BlockBasedImage}; -use super::jpeg_header::{HuffCodes, JPegEncodingInfo, RestartSegmentCodingInfo}; +use super::jpeg_header::{HuffCodes, JPegHeader, ReconstructionInfo, RestartSegmentCodingInfo}; use super::jpeg_position_state::JpegPositionState; use super::row_spec::RowSpec; @@ -53,9 +53,10 @@ pub fn jpeg_write_baseline_row_range( encoded_length: usize, 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( @@ -70,7 +71,7 @@ pub fn jpeg_write_baseline_row_range( 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, ); @@ -95,10 +96,11 @@ pub fn jpeg_write_baseline_row_range( 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, ) .context()?; } @@ -111,9 +113,10 @@ pub fn jpeg_write_baseline_row_range( // 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, ) -> 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]; @@ -124,7 +127,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, ); @@ -141,10 +144,11 @@ 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, ) .context()?; @@ -163,10 +167,9 @@ fn recode_one_mcu_row( mcu: u32, lastdc: &mut [i16], framebuffer: &[BlockBasedImage], - jenc: &JPegEncodingInfo, + jf: &JPegHeader, + rinfo: &ReconstructionInfo, ) -> Result { - let jf = &jenc.jpeg_header; - let mut state = JpegPositionState::new(jf, mcu); let mut cumulative_reset_markers = state.get_cumulative_reset_markers(jf); @@ -301,7 +304,7 @@ fn recode_one_mcu_row( } // pad huffman writer - huffw.pad(jenc.rinfo.pad_bit.unwrap_or(0)); + huffw.pad(rinfo.pad_bit.unwrap_or(0)); assert!( huffw.has_no_remainder(), @@ -316,9 +319,9 @@ fn recode_one_mcu_row( // status 1 means restart if jf.rsti > 0 { - if jenc.rinfo.rst_cnt.len() == 0 - || (!jenc.rinfo.rst_cnt_set) - || cumulative_reset_markers < jenc.rinfo.rst_cnt[jenc.rinfo.scnc] + if rinfo.rst_cnt.len() == 0 + || (!rinfo.rst_cnt_set) + || cumulative_reset_markers < rinfo.rst_cnt[rinfo.scnc] { let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8; diff --git a/src/structs/lepton_file_reader.rs b/src/structs/lepton_file_reader.rs index 3d18174a..20b6cf2f 100644 --- a/src/structs/lepton_file_reader.rs +++ b/src/structs/lepton_file_reader.rs @@ -16,7 +16,7 @@ use log::warn; use crate::consts::*; use crate::enabled_features::EnabledFeatures; use crate::jpeg::block_based_image::BlockBasedImage; -use crate::jpeg::jpeg_header::{JPegEncodingInfo, RestartSegmentCodingInfo}; +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}; @@ -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); }, @@ -382,14 +382,8 @@ impl LeptonFileReader { results.push(header); loop { - let jenc = JPegEncodingInfo { - jpeg_header: lh.jpeg_header.clone(), - truncate_components: lh.truncate_components.clone(), - rinfo: lh.rinfo.clone(), - }; - // progressive JPEG consists of scans followed by headers - let scan = jpeg_write_entire_scan(&merged[..], &jenc).context()?; + let scan = jpeg_write_entire_scan(&merged[..], &lh.jpeg_header, &lh.rinfo).context()?; results.push(scan); // read the next headers (DHT, etc) while mirroring it back to the writer @@ -422,7 +416,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); }, @@ -435,7 +429,7 @@ 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, @@ -448,7 +442,8 @@ impl LeptonFileReader { thread_handoff.segment_size as usize, &restart_info, &image_data, - jenc, + &jpeg_header, + &rinfo, ) .context()?; @@ -514,28 +509,27 @@ impl LeptonFileReader { process: fn( thread_handoff: &ThreadHandoff, image_data: Vec, - jenc: &JPegEncodingInfo, + jpeg_header: &JPegHeader, + rinfo: &ReconstructionInfo, ) -> Result

, ) -> Result> { let qt = QuantizationTables::construct_quantization_tables(&lh.jpeg_header)?; let features = features.clone(); - let jenc = JPegEncodingInfo { - jpeg_header: lh.jpeg_header.clone(), - truncate_components: lh.truncate_components.clone(), - rinfo: lh.rinfo.clone(), - }; - 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, @@ -551,25 +545,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 }, @@ -581,7 +581,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, @@ -593,7 +593,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 20f4b037..79cbbdb1 100644 --- a/src/structs/lepton_file_writer.rs +++ b/src/structs/lepton_file_writer.rs @@ -40,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, @@ -150,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] @@ -213,7 +213,8 @@ pub fn read_jpeg( if lp.jpeg_header.jpeg_type == JPegType::Sequential { if lp.rinfo.early_eof_encountered { - lp.truncate_components + 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. diff --git a/src/structs/lepton_header.rs b/src/structs/lepton_header.rs index ba056170..556c2c85 100644 --- a/src/structs/lepton_header.rs +++ b/src/structs/lepton_header.rs @@ -9,7 +9,6 @@ use flate2::Compression; use crate::consts::*; use crate::helpers::buffer_prefix_matches_marker; use crate::jpeg::jpeg_header::{JPegHeader, ReconstructionInfo}; -use crate::jpeg::truncate_components::TruncateComponents; use crate::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; use crate::structs::thread_handoff::ThreadHandoff; use crate::EnabledFeatures; @@ -32,9 +31,6 @@ 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, /// garbage data (default value - empty segment - means no garbage data) @@ -156,10 +152,11 @@ 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.rinfo.early_eof_encountered { - self.truncate_components + self.rinfo + .truncate_components .set_truncation_bounds(&self.jpeg_header, self.rinfo.max_dpos); } @@ -167,7 +164,7 @@ impl LeptonHeader { // 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) diff --git a/src/structs/truncate_components.rs b/src/structs/truncate_components.rs deleted file mode 100644 index a2ed807b..00000000 --- a/src/structs/truncate_components.rs +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information. - * This software incorporates material from third parties. See NOTICE.txt for details. - *--------------------------------------------------------------------------------------------*/ - -use std::cmp; - -use super::{component_info::ComponentInfo, jpeg_header::JPegHeader}; - -#[derive(Debug, Clone)] -struct TrucateComponentsInfo { - trunc_bcv: u32, // the number of vertical components in this (truncated) image - - trunc_bc: u32, -} - -#[derive(Debug, Clone)] -pub struct TruncateComponents { - trunc_info: Vec, - - pub components_count: usize, - - pub mcu_count_horizontal: u32, - - pub mcu_count_vertical: u32, -} - -impl Default for TruncateComponents { - fn default() -> Self { - return TruncateComponents { - trunc_info: Vec::new(), - components_count: 0, - mcu_count_horizontal: 0, - mcu_count_vertical: 0, - }; - } -} - -impl TruncateComponents { - pub fn init(&mut self, jpeg_header: &JPegHeader) { - self.mcu_count_horizontal = jpeg_header.mcuh.get(); - self.mcu_count_vertical = jpeg_header.mcuv.get(); - self.components_count = jpeg_header.cmpc; - - for i in 0..jpeg_header.cmpc { - self.trunc_info.push(TrucateComponentsInfo { - trunc_bcv: jpeg_header.cmp_info[i].bcv, - trunc_bc: jpeg_header.cmp_info[i].bc, - }); - } - } - - pub fn get_max_coded_heights(&self) -> Vec { - let mut retval = Vec::::new(); - - for i in 0..self.components_count { - retval.push(self.trunc_info[i].trunc_bcv); - } - return retval; - } - - pub fn set_truncation_bounds(&mut self, jpeg_header: &JPegHeader, max_d_pos: [u32; 4]) { - for i in 0..self.components_count { - TruncateComponents::set_block_count_d_pos( - &mut self.trunc_info[i], - &jpeg_header.cmp_info[i], - max_d_pos[i] + 1, - self.mcu_count_vertical, - ); - } - } - - pub fn get_block_height(&self, cmp: usize) -> u32 { - return self.trunc_info[cmp].trunc_bcv; - } - - fn set_block_count_d_pos( - ti: &mut TrucateComponentsInfo, - ci: &ComponentInfo, - trunc_bc: u32, - mcu_count_vertical: u32, - ) { - assert!( - ci.bcv == (ci.bc / ci.bch) + (if ci.bc % ci.bch != 0 { 1 } else { 0 }), - "SetBlockCountDpos" - ); - - let mut vertical_scan_lines = cmp::min( - (trunc_bc / ci.bch) + (if trunc_bc % ci.bch != 0 { 1 } else { 0 }), - ci.bcv, - ); - let ratio = TruncateComponents::get_min_vertical_extcmp_multiple(&ci, mcu_count_vertical); - - while vertical_scan_lines % ratio != 0 && vertical_scan_lines + 1 <= ci.bcv { - vertical_scan_lines += 1; - } - - assert!( - vertical_scan_lines <= ci.bcv, - "verticalScanLines <= ci.Info.bcv" - ); - ti.trunc_bcv = vertical_scan_lines; - ti.trunc_bc = trunc_bc; - } - - fn get_min_vertical_extcmp_multiple(cmp_info: &ComponentInfo, mcu_count_vertical: u32) -> u32 { - let luma_height = cmp_info.bcv; - return luma_height / mcu_count_vertical; - } - - pub fn get_component_sizes_in_blocks(&self) -> Vec { - let mut retval = Vec::new(); - for i in 0..self.components_count { - retval.push(self.trunc_info[i].trunc_bc); - } - return retval; - } -} From fd780784c426b27f10c0e0c800f468e8be693f3f Mon Sep 17 00:00:00 2001 From: Kristof Date: Tue, 3 Dec 2024 10:50:53 +0100 Subject: [PATCH 06/14] fixed comments --- src/jpeg/jpeg_header.rs | 11 +++++------ src/jpeg/jpeg_read.rs | 10 ++++++++-- src/jpeg/jpeg_write.rs | 8 +++++--- src/jpeg/mod.rs | 4 ++++ src/structs/lepton_file_writer.rs | 9 ++------- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index e935e1af..32e19980 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -45,13 +45,12 @@ use crate::LeptonError; use super::component_info::ComponentInfo; use super::truncate_components::TruncateComponents; -/// information required to restart coding the huffman encoded stream at an arbitrary -/// location in the stream. +/// Information required to partition the coding the JPEG huffman encoded stream of a scan +/// at an arbitrary location in the stream. /// -/// This is used to partition the JPEG into multiple segments -/// that can be used to decode it in multiple threads. -/// -/// Each segment has its own unique restart info +/// 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, diff --git a/src/jpeg/jpeg_read.rs b/src/jpeg/jpeg_read.rs index fac6d524..398737c4 100644 --- a/src/jpeg/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -45,6 +45,10 @@ 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. For progressive JPEGs, use `read_progressive_scan`. pub fn read_scan( jf: &JPegHeader, reader: &mut R, @@ -149,9 +153,11 @@ pub fn read_scan( - jf: &mut JPegHeader, + jf: &JPegHeader, reader: &mut R, image_data: &mut [BlockBasedImage], reconstruct_info: &mut ReconstructionInfo, diff --git a/src/jpeg/jpeg_write.rs b/src/jpeg/jpeg_write.rs index 223ea5c9..cb059064 100644 --- a/src/jpeg/jpeg_write.rs +++ b/src/jpeg/jpeg_write.rs @@ -47,7 +47,9 @@ use super::jpeg_header::{HuffCodes, JPegHeader, ReconstructionInfo, RestartSegme use super::jpeg_position_state::JpegPositionState; use super::row_spec::RowSpec; -/// write a range of rows corresponding to the thread_handoff structure into the writer. +/// 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, @@ -109,8 +111,8 @@ 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], jpeg_header: &JPegHeader, diff --git a/src/jpeg/mod.rs b/src/jpeg/mod.rs index f46a6a2a..499313a8 100644 --- a/src/jpeg/mod.rs +++ b/src/jpeg/mod.rs @@ -1,3 +1,7 @@ +/// 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 using this code. mod bit_reader; mod bit_writer; mod component_info; diff --git a/src/structs/lepton_file_writer.rs b/src/structs/lepton_file_writer.rs index 79cbbdb1..dfe41156 100644 --- a/src/structs/lepton_file_writer.rs +++ b/src/structs/lepton_file_writer.rs @@ -249,13 +249,8 @@ 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.jpeg_header, - reader, - &mut image_data[..], - &mut lp.rinfo, - ) - .context()?; + read_progressive_scan(&lp.jpeg_header, reader, &mut image_data[..], &mut lp.rinfo) + .context()?; lp.rinfo.scnc += 1; if lp.rinfo.early_eof_encountered { From 5b6949c108514e92a730fa8365e32a6627caab23 Mon Sep 17 00:00:00 2001 From: Kristof Date: Tue, 3 Dec 2024 10:54:10 +0100 Subject: [PATCH 07/14] module docs --- src/jpeg/mod.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/jpeg/mod.rs b/src/jpeg/mod.rs index 499313a8..59668add 100644 --- a/src/jpeg/mod.rs +++ b/src/jpeg/mod.rs @@ -1,7 +1,12 @@ -/// 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 using this code. +//! 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; From 4671aac9ec9964e80582c02ff5cee950040187d7 Mon Sep 17 00:00:00 2001 From: Kristof Date: Wed, 4 Dec 2024 16:22:45 +0100 Subject: [PATCH 08/14] fix formatting --- src/jpeg/jpeg_header.rs | 3 +-- src/jpeg/jpeg_read.rs | 3 +-- src/jpeg/truncate_components.rs | 3 ++- src/structs/quantization_tables.rs | 3 +-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index 32e19980..fd32b129 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -38,9 +38,8 @@ 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::LeptonError; +use crate::{jpeg_code, LeptonError}; use super::component_info::ComponentInfo; use super::truncate_components::TruncateComponents; diff --git a/src/jpeg/jpeg_read.rs b/src/jpeg/jpeg_read.rs index 398737c4..56aae3fa 100644 --- a/src/jpeg/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -37,8 +37,7 @@ 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::lepton_error::{err_exit_code, AddContext, ExitCode, Result}; use super::bit_reader::BitReader; use super::block_based_image::{AlignedBlock, BlockBasedImage}; diff --git a/src/jpeg/truncate_components.rs b/src/jpeg/truncate_components.rs index a2ed807b..677ff702 100644 --- a/src/jpeg/truncate_components.rs +++ b/src/jpeg/truncate_components.rs @@ -6,7 +6,8 @@ use std::cmp; -use super::{component_info::ComponentInfo, jpeg_header::JPegHeader}; +use super::component_info::ComponentInfo; +use super::jpeg_header::JPegHeader; #[derive(Debug, Clone)] struct TrucateComponentsInfo { diff --git a/src/structs/quantization_tables.rs b/src/structs/quantization_tables.rs index cc1b30c5..f597068a 100644 --- a/src/structs/quantization_tables.rs +++ b/src/structs/quantization_tables.rs @@ -8,8 +8,7 @@ use crate::consts::*; use crate::helpers::*; use crate::jpeg::jpeg_header::JPegHeader; use crate::lepton_error::err_exit_code; -use crate::ExitCode; -use crate::Result; +use crate::{ExitCode, Result}; pub struct QuantizationTables { quantization_table: [u16; 64], From acfe298d0eae89cd877f8d246580c23b5fa7b32e Mon Sep 17 00:00:00 2001 From: Kristof Date: Wed, 4 Dec 2024 16:51:58 +0100 Subject: [PATCH 09/14] clarified progressive handling --- src/jpeg/jpeg_header.rs | 2 +- src/jpeg/jpeg_read.rs | 25 ++++++++++--------------- src/structs/lepton_file_writer.rs | 4 ++-- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index fd32b129..75958aaa 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -99,7 +99,7 @@ pub struct ReconstructionInfo { /// count of scans encountered so far pub scnc: usize, - /// the maximum bit in a truncated image + /// the maximum bit in a truncated image. This is not implemented or used for progressive images. pub max_sah: u8, /// A list containing one entry for each scan segment. Each entry contains the number of restart intervals diff --git a/src/jpeg/jpeg_read.rs b/src/jpeg/jpeg_read.rs index 56aae3fa..9880ebd1 100644 --- a/src/jpeg/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -47,8 +47,9 @@ 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. For progressive JPEGs, use `read_progressive_scan`. -pub fn read_scan( +/// 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, partition: &mut FPARTITION, @@ -88,21 +89,15 @@ pub fn read_scan( let mut thread_handoff = Vec::::new(); let start_scan: u32 = reader.stream_position()?.try_into().unwrap(); - read_scan( + read_first_scan( &lp.jpeg_header, reader, &mut |segment_offset_in_file, restart_info| { From ce16257c41118c99d375400ccf860071cfbc294a Mon Sep 17 00:00:00 2001 From: Kristof Date: Wed, 4 Dec 2024 16:53:47 +0100 Subject: [PATCH 10/14] update version --- Cargo.lock | 40 ++++++++++++++++----------------- Cargo.toml | 2 +- package/Lepton.Jpeg.Rust.nuspec | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) 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 From b242ed12760b71ff00ce3bbce0d9a9df2b313122 Mon Sep 17 00:00:00 2001 From: Kristof Date: Wed, 4 Dec 2024 22:46:43 +0100 Subject: [PATCH 11/14] clean up blockcontext --- src/structs/block_context.rs | 63 +++++++++++++++++------------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/structs/block_context.rs b/src/structs/block_context.rs index cc92c462..3b72989f 100644 --- a/src/structs/block_context.rs +++ b/src/structs/block_context.rs @@ -23,26 +23,35 @@ pub struct NeighborData<'a> { } impl BlockContext { - // blocks above the first line are never dereferenced + /// 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 { - return BlockContext::new( - image_data.get_block_width() * y, - if y > 0 { - image_data.get_block_width() * (y - 1) - } else { - 0 - }, - if (y & 1) != 0 { - image_data.get_block_width() - } else { - 0 - }, - if (y & 1) != 0 { - 0 - } else { - image_data.get_block_width() - }, - ); + let cur_block_index = image_data.get_block_width() * y; + + // blocks above the first line are never dereferenced + let above_block_index = if y > 0 { + image_data.get_block_width() * (y - 1) + } else { + 0 + }; + + let cur_neighbor_summary_index = if (y & 1) != 0 { + image_data.get_block_width() + } else { + 0 + }; + + let above_neighbor_summary_index = if (y & 1) != 0 { + 0 + } else { + image_data.get_block_width() + }; + + BlockContext { + cur_block_index, + above_block_index, + cur_neighbor_summary_index, + above_neighbor_summary_index, + } } // for debugging @@ -52,7 +61,7 @@ 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; @@ -62,20 +71,6 @@ impl BlockContext { 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; From be4309f62d8294eae9b1e14b1f9c4677a8a45535 Mon Sep 17 00:00:00 2001 From: Kristof Date: Wed, 4 Dec 2024 22:50:50 +0100 Subject: [PATCH 12/14] clean up blockcontext a bit more --- src/structs/block_context.rs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/structs/block_context.rs b/src/structs/block_context.rs index 3b72989f..8b9a0b74 100644 --- a/src/structs/block_context.rs +++ b/src/structs/block_context.rs @@ -8,9 +8,8 @@ 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, } @@ -25,30 +24,18 @@ 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 cur_block_index = image_data.get_block_width() * y; + let block_width = image_data.get_block_width(); - // blocks above the first line are never dereferenced - let above_block_index = if y > 0 { - image_data.get_block_width() * (y - 1) - } else { - 0 - }; + let cur_block_index = block_width * y; - let cur_neighbor_summary_index = if (y & 1) != 0 { - image_data.get_block_width() - } else { - 0 - }; + // 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 { - image_data.get_block_width() - }; + let above_neighbor_summary_index = if (y & 1) != 0 { 0 } else { block_width }; BlockContext { cur_block_index, - above_block_index, + block_width, cur_neighbor_summary_index, above_neighbor_summary_index, } @@ -64,7 +51,6 @@ impl BlockContext { // 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; @@ -84,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 }, From b01e56434957efd57e055106f89db14029335bbc Mon Sep 17 00:00:00 2001 From: Kristof Date: Fri, 6 Dec 2024 17:59:09 +0100 Subject: [PATCH 13/14] simplify current scan index to only what is used --- src/jpeg/jpeg_header.rs | 20 +++++++++++++------- src/jpeg/jpeg_read.rs | 3 --- src/jpeg/jpeg_write.rs | 6 +++++- src/structs/lepton_file_reader.rs | 6 ++++-- src/structs/lepton_file_writer.rs | 4 +--- src/structs/lepton_header.rs | 7 ++----- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index 75958aaa..53a9b033 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -84,9 +84,21 @@ impl RestartSegmentCodingInfo { /// regarding information about possible truncation and RST markers. #[derive(Default, Clone, Debug)] pub struct ReconstructionInfo { - /// the maximum band in a truncated image + /// 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], @@ -96,12 +108,6 @@ pub struct ReconstructionInfo { /// the mask for padding out the bitstream when we get to the end of a reset block pub pad_bit: Option, - /// count of scans encountered so far - pub scnc: usize, - - /// the maximum bit in a truncated image. This is not implemented or used for progressive images. - pub max_sah: u8, - /// 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, diff --git a/src/jpeg/jpeg_read.rs b/src/jpeg/jpeg_read.rs index 9880ebd1..73e84cc1 100644 --- a/src/jpeg/jpeg_read.rs +++ b/src/jpeg/jpeg_read.rs @@ -142,8 +142,6 @@ pub fn read_first_scan( } } - reconstruct_info.scnc += 1; // increment scan counter Ok(()) } diff --git a/src/jpeg/jpeg_write.rs b/src/jpeg/jpeg_write.rs index cb059064..60209d3b 100644 --- a/src/jpeg/jpeg_write.rs +++ b/src/jpeg/jpeg_write.rs @@ -103,6 +103,7 @@ pub fn jpeg_write_baseline_row_range( image_data, jpeg_header, rinfo, + 0, ) .context()?; } @@ -117,6 +118,7 @@ pub fn jpeg_write_entire_scan( image_data: &[BlockBasedImage], jpeg_header: &JPegHeader, rinfo: &ReconstructionInfo, + current_scan_index: usize, ) -> Result> { let max_coded_heights = rinfo.truncate_components.get_max_coded_heights(); @@ -151,6 +153,7 @@ pub fn jpeg_write_entire_scan( image_data, jpeg_header, rinfo, + current_scan_index, ) .context()?; @@ -171,6 +174,7 @@ fn recode_one_mcu_row( framebuffer: &[BlockBasedImage], jf: &JPegHeader, rinfo: &ReconstructionInfo, + current_scan_index: usize, ) -> Result { let mut state = JpegPositionState::new(jf, mcu); @@ -323,7 +327,7 @@ fn recode_one_mcu_row( if jf.rsti > 0 { if rinfo.rst_cnt.len() == 0 || (!rinfo.rst_cnt_set) - || cumulative_reset_markers < rinfo.rst_cnt[rinfo.scnc] + || cumulative_reset_markers < rinfo.rst_cnt[current_scan_index] { let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8; diff --git a/src/structs/lepton_file_reader.rs b/src/structs/lepton_file_reader.rs index 20b6cf2f..9fc5d212 100644 --- a/src/structs/lepton_file_reader.rs +++ b/src/structs/lepton_file_reader.rs @@ -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[..], &lh.jpeg_header, &lh.rinfo).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.rinfo.scnc += 1; + scnc += 1; } Ok(DecoderState::AppendTrailer(results)) diff --git a/src/structs/lepton_file_writer.rs b/src/structs/lepton_file_writer.rs index ff2bd629..377cf088 100644 --- a/src/structs/lepton_file_writer.rs +++ b/src/structs/lepton_file_writer.rs @@ -184,7 +184,6 @@ pub fn read_jpeg( &mut lp.rinfo, ) .context()?; - lp.rinfo.scnc += 1; let mut end_scan = reader.stream_position()?.try_into().unwrap(); @@ -251,7 +250,6 @@ pub fn read_jpeg( read_progressive_scan(&lp.jpeg_header, reader, &mut image_data[..], &mut lp.rinfo) .context()?; - lp.rinfo.scnc += 1; if lp.rinfo.early_eof_encountered { return err_exit_code( @@ -482,7 +480,7 @@ fn prepare_to_decode_next_scan( ); 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 556c2c85..9e421579 100644 --- a/src/structs/lepton_header.rs +++ b/src/structs/lepton_header.rs @@ -36,9 +36,6 @@ pub struct LeptonHeader { /// garbage data (default value - empty segment - means no garbage data) pub garbage_data: Vec, - /// the maximum component in a truncated image - pub max_cmp: u32, - pub rinfo: ReconstructionInfo, pub jpeg_file_size: u32, @@ -305,7 +302,7 @@ impl LeptonHeader { current_lepton_marker, LEPTON_HEADER_EARLY_EOF_MARKER, ) { - self.max_cmp = header_reader.read_u32::()?; + 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::()?; @@ -475,7 +472,7 @@ impl LeptonHeader { // EEE marker mrw.write_all(&LEPTON_HEADER_EARLY_EOF_MARKER)?; - mrw.write_u32::(self.max_cmp)?; + 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])?; From f6911efbafcd36ae6958bc0795c20312d9e80309 Mon Sep 17 00:00:00 2001 From: Kristof Date: Fri, 6 Dec 2024 18:31:32 +0100 Subject: [PATCH 14/14] add clarification for rst_cnt --- src/jpeg/jpeg_header.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/jpeg/jpeg_header.rs b/src/jpeg/jpeg_header.rs index 53a9b033..c65f2eef 100644 --- a/src/jpeg/jpeg_header.rs +++ b/src/jpeg/jpeg_header.rs @@ -108,10 +108,18 @@ pub struct ReconstructionInfo { /// 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 + /// 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