use bytemuck::{cast, cast_ref};
use wide::{CmpEq, i16x16};
use crate::consts::{JpegDecodeStatus, JpegType};
use crate::helpers::u16_bit_length;
use crate::lepton_error::{AddContext, ExitCode, err_exit_code};
use crate::Result;
use super::bit_writer::BitWriter;
use super::block_based_image::{AlignedBlock, BlockBasedImage};
use super::jpeg_code;
use super::jpeg_header::{HuffCodes, JpegHeader, ReconstructionInfo, RestartSegmentCodingInfo};
use super::jpeg_position_state::JpegPositionState;
use super::row_spec::RowSpec;
pub struct JpegIncrementalWriter<'a> {
last_dc: [i16; 4],
huffw: BitWriter,
reconstruction_info: &'a ReconstructionInfo,
jpeg_header: &'a JpegHeader,
capacity: usize,
current_scan_index: usize,
}
impl<'a> JpegIncrementalWriter<'a> {
pub fn new(
capacity: usize,
reconstruction_info: &'a ReconstructionInfo,
rinfo: Option<&RestartSegmentCodingInfo>,
jpeg_header: &'a JpegHeader,
current_scan_index: usize,
) -> JpegIncrementalWriter<'a> {
let mut huffw = BitWriter::new(Vec::with_capacity(capacity));
if let Some(rinfo) = rinfo {
huffw.reset_from_overhang_byte_and_num_bits(
rinfo.overhang_byte,
u32::from(rinfo.num_overhang_bits),
);
}
JpegIncrementalWriter {
last_dc: if let Some(r) = rinfo {
r.last_dc
} else {
[0i16; 4]
},
huffw,
jpeg_header,
reconstruction_info,
capacity,
current_scan_index,
}
}
pub fn amount_buffered(&self) -> usize {
self.huffw.amount_buffered()
}
pub fn process_row(
&mut self,
cur_row: &RowSpec,
image_data: &[BlockBasedImage],
) -> Result<bool> {
if cur_row.last_row_to_complete_mcu {
self.huffw.ensure_space(self.capacity);
return Ok(recode_one_mcu_row(
&mut self.huffw,
cur_row.mcu_row_index * self.jpeg_header.mcuh.get(),
&mut self.last_dc,
image_data,
self.jpeg_header,
self.reconstruction_info,
self.current_scan_index,
)
.context()?);
}
Ok(false)
}
pub fn detach_buffer(&mut self) -> Vec<u8> {
self.huffw.detach_buffer()
}
}
pub fn jpeg_write_entire_scan(
image_data: &[BlockBasedImage],
jpeg_header: &JpegHeader,
rinfo: &ReconstructionInfo,
current_scan_index: usize,
) -> Result<Vec<u8>> {
let mut inc_write =
JpegIncrementalWriter::new(128 * 1024, rinfo, None, jpeg_header, current_scan_index);
let max_coded_heights = rinfo.truncate_components.get_max_coded_heights();
let mut decode_index = 0;
loop {
let cur_row = RowSpec::get_row_spec_from_index(
decode_index,
image_data,
jpeg_header.mcuv.get(),
&max_coded_heights,
);
decode_index += 1;
if cur_row.done {
break;
}
if cur_row.skip {
continue;
}
if inc_write.process_row(&cur_row, image_data)? {
break;
}
}
Ok(inc_write.detach_buffer())
}
#[inline(never)]
fn recode_one_mcu_row(
huffw: &mut BitWriter,
mcu: u32,
lastdc: &mut [i16],
framebuffer: &[BlockBasedImage],
jf: &JpegHeader,
rinfo: &ReconstructionInfo,
current_scan_index: usize,
) -> Result<bool> {
let mut state = JpegPositionState::new(jf, mcu);
let mut cumulative_reset_markers = state.get_cumulative_reset_markers(jf);
let mut end_of_row = false;
let mut correction_bits = Vec::new();
while !end_of_row {
let mut sta = JpegDecodeStatus::DecodeInProgress;
while sta == JpegDecodeStatus::DecodeInProgress {
let current_block = framebuffer[state.get_cmp()].get_block(state.get_dpos());
let old_mcu = state.get_mcu();
if jf.jpeg_type == JpegType::Sequential {
let mut block = current_block.zigzag_from_transposed();
let dc = block.get_block()[0];
block.get_block_mut()[0] -= lastdc[state.get_cmp()];
lastdc[state.get_cmp()] = dc;
encode_block_seq(
huffw,
jf.get_huff_dc_codes(state.get_cmp()),
jf.get_huff_ac_codes(state.get_cmp()),
&block,
);
sta = state.next_mcu_pos(&jf);
} else if jf.cs_to == 0 {
if jf.cs_sah == 0 {
let tmp = current_block.get_transposed_from_zigzag(0) >> jf.cs_sal;
let v = tmp - lastdc[state.get_cmp()];
lastdc[state.get_cmp()] = tmp;
write_coef(
huffw,
v < 0,
v.unsigned_abs(),
0,
jf.get_huff_dc_codes(state.get_cmp()),
);
} else {
huffw.write(
((current_block.get_transposed_from_zigzag(0) >> jf.cs_sal) & 1) as u32,
1,
);
}
sta = state.next_mcu_pos(jf);
} else {
let mut block = [0i16; 64];
for bpos in jf.cs_from..jf.cs_to + 1 {
block[usize::from(bpos)] = div_pow2(
current_block.get_transposed_from_zigzag(usize::from(bpos)),
jf.cs_sal,
);
}
if jf.cs_sah == 0 {
encode_ac_prg_fs(
huffw,
jf.get_huff_ac_codes(state.get_cmp()),
&block,
&mut state,
jf.cs_from,
jf.cs_to,
)
.context()?;
sta = state.next_mcu_pos(jf);
if sta != JpegDecodeStatus::DecodeInProgress {
encode_eobrun(huffw, jf.get_huff_ac_codes(state.get_cmp()), &mut state);
}
} else {
encode_ac_prg_sa(
huffw,
jf.get_huff_ac_codes(state.get_cmp()),
&block,
&mut state,
jf.cs_from,
jf.cs_to,
&mut correction_bits,
)
.context()?;
sta = state.next_mcu_pos(jf);
if sta != JpegDecodeStatus::DecodeInProgress {
encode_eobrun(huffw, jf.get_huff_ac_codes(state.get_cmp()), &mut state);
encode_crbits(huffw, &mut correction_bits);
}
}
}
if old_mcu != state.get_mcu() && state.get_mcu() % jf.mcuh == 0 {
end_of_row = true;
if sta == JpegDecodeStatus::DecodeInProgress {
return Ok(false);
}
}
}
huffw.pad(rinfo.pad_bit.unwrap_or(0));
assert!(
huffw.has_no_remainder(),
"shouldnt have a remainder after padding"
);
if sta == JpegDecodeStatus::ScanCompleted {
return Ok(true); } else {
assert!(sta == JpegDecodeStatus::RestartIntervalExpired);
if jf.rsti > 0 {
if rinfo.rst_cnt.len() == 0
|| (!rinfo.rst_cnt_set)
|| cumulative_reset_markers < rinfo.rst_cnt[current_scan_index]
{
let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8;
huffw.write_byte_unescaped(0xFF);
huffw.write_byte_unescaped(rst);
cumulative_reset_markers += 1;
}
state.reset_rstw(jf);
for i in 0..lastdc.len() {
lastdc[i] = 0;
}
}
}
}
Ok(false)
}
#[inline(never)]
pub(crate) fn encode_block_seq(
huffw: &mut BitWriter,
dctbl: &HuffCodes,
actbl: &HuffCodes,
block: &AlignedBlock,
) {
let block_simd: &[i16x16; 4] = cast_ref(block.get_block());
let mut mask = (block_simd[0].simd_eq(i16x16::ZERO).to_bitmask() as u64)
| ((block_simd[1].simd_eq(i16x16::ZERO).to_bitmask() as u64) << 16)
| ((block_simd[2].simd_eq(i16x16::ZERO).to_bitmask() as u64) << 32)
| ((block_simd[3].simd_eq(i16x16::ZERO).to_bitmask() as u64) << 48);
let abs_value: [u16; 64] = cast(block_simd.map(|x| x.abs()));
let is_neg: [u16; 64] = cast(block_simd.map(|x| x >> 15));
write_coef(huffw, (is_neg[0] & 256) != 0, abs_value[0], 0, dctbl);
mask = !mask;
mask >>= 1;
let mut bpos = 1;
while mask != 0 {
let mut zeros = mask.trailing_zeros();
if zeros > 15 {
zeros = 15;
}
bpos += zeros + 1;
mask >>= zeros + 1;
write_coef(
huffw,
(is_neg[(bpos - 1) as usize] & 256) != 0, abs_value[(bpos - 1) as usize],
zeros,
actbl,
);
if bpos >= 64 {
return;
}
}
huffw.write(actbl.c_val[0x00].into(), actbl.c_len[0x00].into());
}
#[inline(always)]
fn write_coef(huffw: &mut BitWriter, is_neg: bool, abs_coef: u16, z: u32, tbl: &HuffCodes) {
let s = 32 - u32::from(abs_coef).leading_zeros();
let hc = z << 4 | s;
let val = tbl.c_val_shift_s[(hc | ((is_neg as u32) << 8)) as usize] ^ u32::from(abs_coef);
let new_bits = u32::from(tbl.c_len_plus_s[hc as usize]);
huffw.write(val, new_bits);
}
fn encode_ac_prg_fs(
huffw: &mut BitWriter,
actbl: &HuffCodes,
block: &[i16; 64],
state: &mut JpegPositionState,
from: u8,
to: u8,
) -> Result<()> {
let mut z = 0;
for bpos in from..to + 1 {
let tmp = block[usize::from(bpos)];
if tmp != 0 {
encode_eobrun(huffw, actbl, state);
while z >= 16 {
huffw.write(actbl.c_val[0xF0].into(), actbl.c_len[0xF0].into());
z -= 16;
}
write_coef(huffw, tmp < 0, tmp.unsigned_abs(), z, actbl);
z = 0;
} else {
z += 1;
}
}
if z > 0 {
if actbl.max_eob_run == 0 {
return err_exit_code(
ExitCode::UnsupportedJpeg,
"there must be at least one EOB symbol run in the huffman table to encode EOBs",
)
.context();
}
state.eobrun += 1;
if state.eobrun == actbl.max_eob_run {
encode_eobrun(huffw, actbl, state);
}
}
Ok(())
}
fn encode_ac_prg_sa(
huffw: &mut BitWriter,
actbl: &HuffCodes,
block: &[i16; 64],
state: &mut JpegPositionState,
from: u8,
to: u8,
correction_bits: &mut Vec<u8>,
) -> Result<()> {
let mut eob = from;
{
let mut bpos = to;
while bpos >= from {
if (block[usize::from(bpos)] == 1) || (block[usize::from(bpos)] == -1) {
eob = bpos + 1;
break;
}
bpos -= 1;
}
}
if (eob > from) && state.eobrun > 0 {
encode_eobrun(huffw, actbl, state);
encode_crbits(huffw, correction_bits);
}
let mut z = 0;
for bpos in from..eob {
let tmp = block[usize::from(bpos)];
if tmp == 0 {
z += 1; if z == 16 {
huffw.write(actbl.c_val[0xF0].into(), actbl.c_len[0xF0].into());
encode_crbits(huffw, correction_bits);
z = 0;
}
}
else if (tmp == 1) || (tmp == -1) {
write_coef(huffw, tmp < 0, tmp.unsigned_abs(), z, actbl);
encode_crbits(huffw, correction_bits);
z = 0;
} else {
let n = (block[usize::from(bpos)] & 0x1) as u8;
correction_bits.push(n);
}
}
for bpos in eob..to + 1 {
if block[usize::from(bpos)] != 0 {
let n = (block[usize::from(bpos)] & 0x1) as u8;
correction_bits.push(n);
}
}
if eob <= to {
if actbl.max_eob_run == 0 {
return err_exit_code(
ExitCode::UnsupportedJpeg,
"there must be at least one EOB symbol run in the huffman table to encode EOBs",
)
.context();
}
state.eobrun += 1;
if state.eobrun == actbl.max_eob_run {
encode_eobrun(huffw, actbl, state);
encode_crbits(huffw, correction_bits);
}
}
Ok(())
}
fn encode_eobrun(huffw: &mut BitWriter, actbl: &HuffCodes, state: &mut JpegPositionState) {
if (state.eobrun) > 0 {
debug_assert!((state.eobrun) <= actbl.max_eob_run);
let mut s = u16_bit_length(state.eobrun);
s -= 1;
let n = encode_eobrun_bits(s, state.eobrun);
let hc = s << 4;
huffw.write(
actbl.c_val[usize::from(hc)].into(),
actbl.c_len[usize::from(hc)].into(),
);
huffw.write(u32::from(n), u32::from(s));
state.eobrun = 0;
}
}
fn encode_crbits(huffw: &mut BitWriter, correction_bits: &mut Vec<u8>) {
for x in correction_bits.drain(..) {
huffw.write(u32::from(x), 1);
}
}
fn div_pow2(v: i16, p: u8) -> i16 {
(if v < 0 { v + ((1 << p) - 1) } else { v }) >> p
}
fn encode_eobrun_bits(s: u8, v: u16) -> u16 {
v - (1 << s)
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
use crate::{
helpers::read_file,
jpeg::{
bit_reader::BitReader,
bit_writer::BitWriter,
block_based_image::AlignedBlock,
jpeg_header::{HuffTree, generate_huff_table_from_distribution},
jpeg_read::decode_block_seq,
},
};
fn round_trip_block(block: &AlignedBlock, expected: &[u8]) {
let mut bitwriter = BitWriter::new(Vec::with_capacity(1024));
let mut dcdistribution = [0; 256];
for i in 0..256 {
dcdistribution[i] = 256 - i;
}
let dctbl = generate_huff_table_from_distribution(&dcdistribution);
let mut acdistribution = [0; 256];
for i in 0..256 {
acdistribution[i] = 1 + 256;
}
let actbl = generate_huff_table_from_distribution(&acdistribution);
encode_block_seq(&mut bitwriter, &dctbl, &actbl, block);
bitwriter.pad(0);
let buf = bitwriter.detach_buffer();
assert_eq!(buf, expected);
let mut bitreader = BitReader::new(Cursor::new(&buf));
let mut block_decoded = [0i16; 64];
decode_block_seq(
&mut bitreader,
&HuffTree::construct_hufftree(&dctbl, true).unwrap(),
&HuffTree::construct_hufftree(&actbl, true).unwrap(),
&mut block_decoded,
)
.unwrap();
assert_eq!(&block_decoded, block.get_block());
}
#[test]
fn test_encode_block_seq() {
let mut block = AlignedBlock::default();
for i in 0..64 {
block.get_block_mut()[i] = (i as i16) - 32;
}
let expected = [
152, 252, 176, 37, 131, 44, 41, 97, 203, 18, 88, 178, 198, 150, 60, 178, 37, 147, 44,
169, 101, 203, 50, 89, 178, 206, 150, 126, 176, 107, 14, 177, 107, 30, 178, 107, 46,
179, 107, 56, 136, 17, 34, 40, 69, 128, 128, 47, 120, 250, 3, 0, 226, 48, 70, 136, 225,
31, 173, 26, 211, 173, 90, 215, 173, 154, 219, 173, 218, 223, 45, 9, 104, 203, 74, 90,
114, 212, 150, 172, 181, 165, 175, 45, 137, 108, 203, 106, 91, 114, 220, 150, 236, 183,
165, 190,
];
round_trip_block(&block, &expected);
}
#[test]
fn test_encode_block_magnitude() {
let mut block = AlignedBlock::default();
for i in 0..15 {
block.get_block_mut()[i] = (1u16 << i) as i16;
}
for i in 0..15 {
block.get_block_mut()[i + 20] = -((1u16 << i) as i16);
}
let expected = [
165, 1, 132, 102, 180, 75, 64, 138, 6, 248, 8, 16, 27, 208, 13, 120, 2, 122, 0, 75,
192, 4, 60, 0, 8, 224, 0, 109, 128, 1, 250, 1, 68, 94, 179, 203, 60, 137, 246, 247,
232, 15, 251, 207, 253, 119, 254, 121, 255, 0, 203, 191, 252, 59, 255, 0, 200, 223,
255, 0, 109, 127, 254, 0,
];
round_trip_block(&block, &expected);
}
#[test]
fn test_encode_block_zero_runs() {
let mut block = AlignedBlock::default();
for i in 0..10 {
block.get_block_mut()[i] = i as i16;
}
for i in 30..50 {
block.get_block_mut()[i] = -(i as i16);
}
for i in 50..52 {
block.get_block_mut()[i] = i as i16;
}
let expected = [
169, 223, 1, 128, 113, 24, 35, 68, 112, 143, 214, 141, 105, 167, 249, 12, 176, 8, 159,
34, 120, 137, 210, 39, 8, 155, 34, 104, 137, 146, 38, 8, 151, 34, 88, 137, 82, 37, 8,
147, 34, 72, 137, 18, 36, 8, 143, 34, 56, 139, 34, 44, 192, 0,
];
round_trip_block(&block, &expected);
}
#[test]
fn test_encode_block_long_zero_cnt() {
let mut block = AlignedBlock::default();
block.get_block_mut()[63] = 1;
let expected = [169, 79, 79, 79, 33];
round_trip_block(&block, &expected);
}
#[test]
fn test_encode_block_seq_zero() {
let block = AlignedBlock::default();
let expected = [168, 0];
round_trip_block(&block, &expected);
}
fn roundtrip_jpeg<R: std::io::BufRead + std::io::Seek>(
reader: &mut R,
enabled_features: &crate::EnabledFeatures,
) -> Vec<u8> {
use crate::consts::*;
use crate::jpeg::jpeg_header::{JpegHeader, ReconstructionInfo};
use crate::jpeg::jpeg_read::read_jpeg_file;
let mut jpeg_header = JpegHeader::default();
let mut rinfo = ReconstructionInfo::default();
let mut headers = Vec::new();
let (image_data, partitions, end_scan_position) = read_jpeg_file(
reader,
&mut jpeg_header,
&mut rinfo,
&enabled_features,
|header, raw_header| {
headers.push((header.clone(), raw_header.to_vec()));
},
)
.unwrap();
let mut reconstructed = Vec::new();
reconstructed.extend_from_slice(&SOI);
if jpeg_header.is_single_scan() {
reconstructed.extend_from_slice(rinfo.raw_jpeg_header.as_slice());
let mut prev_offset = 0;
for (offset, coding_info) in partitions {
let mut r = jpeg_write_baseline_row_range(
(offset - prev_offset) as usize,
&coding_info,
&image_data,
&jpeg_header,
&rinfo,
)
.unwrap();
reconstructed.append(&mut r);
prev_offset = offset;
}
assert_eq!(reconstructed.len(), end_scan_position as usize);
reconstructed.extend_from_slice(&EOI);
} else {
let mut scnc = 0;
for (jh, raw_header) in headers {
reconstructed.extend_from_slice(&raw_header);
let scan = jpeg_write_entire_scan(&image_data, &jh, &rinfo, scnc).unwrap();
reconstructed.extend_from_slice(&scan);
scnc += 1;
}
reconstructed.extend_from_slice(&EOI);
assert_eq!(reconstructed.len(), end_scan_position as usize);
}
reconstructed
}
#[test]
fn roundtrip_baseline_jpeg() {
let file = read_file("iphone", ".jpg");
let enabled_features = crate::EnabledFeatures::compat_lepton_scalar_read();
let reconstructed = roundtrip_jpeg(&mut std::io::Cursor::new(&file), &enabled_features);
assert!(reconstructed == file);
}
#[test]
fn roundtrip_progressive_jpeg() {
let file = read_file("iphoneprogressive", ".jpg");
let enabled_features = crate::EnabledFeatures::compat_lepton_scalar_read();
let reconstructed = roundtrip_jpeg(&mut std::io::Cursor::new(&file), &enabled_features);
assert!(reconstructed == file);
}
#[test]
fn test_benchmark_write_jpeg() {
let mut f = benchmarks::benchmark_write_jpeg();
for _ in 0..10 {
f();
}
}
#[test]
fn test_benchmark_write_block() {
let mut f = benchmarks::benchmark_write_block();
for _ in 0..10 {
f();
}
}
}
#[cfg(any(test, feature = "micro_benchmark"))]
fn jpeg_write_baseline_row_range(
encoded_length: usize,
restart_info: &RestartSegmentCodingInfo,
image_data: &[BlockBasedImage],
jpeg_header: &JpegHeader,
rinfo: &ReconstructionInfo,
) -> Result<Vec<u8>> {
let max_coded_heights: Vec<u32> = rinfo.truncate_components.get_max_coded_heights();
let mut writer =
JpegIncrementalWriter::new(encoded_length, rinfo, Some(restart_info), jpeg_header, 0);
let mut decode_index = 0;
loop {
let cur_row: RowSpec = RowSpec::get_row_spec_from_index(
decode_index,
image_data,
rinfo.truncate_components.mcu_count_vertical,
&max_coded_heights,
);
decode_index += 1;
if cur_row.done {
break;
}
if cur_row.skip {
continue;
}
if cur_row.luma_y < restart_info.luma_y_start {
continue;
}
if cur_row.luma_y > restart_info.luma_y_end {
break; }
writer.process_row(&cur_row, image_data).context()?;
}
Ok(writer.detach_buffer())
}
#[cfg(any(test, feature = "micro_benchmark"))]
pub mod benchmarks {
use std::mem;
use super::*;
use crate::{
EnabledFeatures,
helpers::read_file,
jpeg::{
bit_writer::BitWriter,
block_based_image::AlignedBlock,
jpeg_header::{JpegHeader, ReconstructionInfo, generate_huff_table_from_distribution},
jpeg_read::read_jpeg_file,
},
};
#[inline(never)]
pub fn benchmark_write_block() -> Box<dyn FnMut()> {
let mut dcdistribution = [0; 256];
for i in 0..256 {
dcdistribution[i] = 256 - i;
}
let dctbl = generate_huff_table_from_distribution(&dcdistribution);
let mut acdistribution = [0; 256];
for i in 0..256 {
acdistribution[i] = 1 + 256;
}
let actbl = generate_huff_table_from_distribution(&acdistribution);
let mut block = AlignedBlock::default();
for i in 0..10 {
block.get_block_mut()[i] = i as i16;
}
for i in 30..50 {
block.get_block_mut()[i] = -(i as i16);
}
for i in 50..52 {
block.get_block_mut()[i] = i as i16;
}
let mut storage = Vec::with_capacity(1024);
Box::new(move || {
let mut bitwriter = BitWriter::new(mem::take(&mut storage));
encode_block_seq(&mut bitwriter, &dctbl, &actbl, &block);
storage = bitwriter.detach_buffer();
storage.clear();
})
}
#[inline(never)]
pub fn benchmark_write_jpeg() -> Box<dyn FnMut()> {
let file = read_file("android", ".jpg");
let mut reader = std::io::Cursor::new(&file);
let enabled_features = EnabledFeatures::compat_lepton_vector_write();
let mut jpeg_header = JpegHeader::default();
let mut rinfo = ReconstructionInfo::default();
let (image_data, partitions, _end_scan) = read_jpeg_file(
&mut reader,
&mut jpeg_header,
&mut rinfo,
&enabled_features,
|_, _| {},
)
.unwrap();
Box::new(move || {
let mut prev_offset = 0;
for (offset, coding_info) in &partitions {
use std::hint::black_box;
let r = jpeg_write_baseline_row_range(
(offset - prev_offset) as usize,
&coding_info,
&image_data,
&jpeg_header,
&rinfo,
)
.unwrap();
black_box(r);
prev_offset = *offset;
}
})
}
}