use anyhow::{Context, Result};
use byteorder::WriteBytesExt;
use crate::{
consts::{JPegDecodeStatus, JPegType},
helpers::{err_exit_code, u16_bit_length},
here, jpeg_code,
lepton_error::ExitCode,
};
use std::io::Write;
use super::{
bit_writer::BitWriter, block_based_image::BlockBasedImage, jpeg_header::HuffCodes,
jpeg_position_state::JpegPositionState, lepton_format::LeptonHeader, row_spec::RowSpec,
thread_handoff::ThreadHandoff,
};
pub fn jpeg_write_row_range<W: Write>(
writer: &mut W,
framebuffer: &[BlockBasedImage],
mcuv: i32,
thread_handoff: &ThreadHandoff,
max_coded_heights: &[u32],
huffw: &mut BitWriter,
lh: &LeptonHeader,
) -> Result<()> {
huffw.reset_from_overhang_byte_and_num_bits(
thread_handoff.overhang_byte,
thread_handoff.num_overhang_bits.into(),
);
let mut last_dc = thread_handoff.last_dc.clone();
let mut decode_index = 0;
loop {
let cur_row =
RowSpec::get_row_spec_from_index(decode_index, framebuffer, mcuv, max_coded_heights);
decode_index += 1;
if cur_row.done {
break;
}
if cur_row.skip {
continue;
}
if cur_row.min_row_luma_y < thread_handoff.luma_y_start {
continue;
}
if cur_row.next_row_luma_y > thread_handoff.luma_y_end {
break; }
if cur_row.last_row_to_complete_mcu {
recode_one_mcu_row(
huffw,
cur_row.mcu_row_index * lh.jpeg_header.mcuh,
writer,
&mut last_dc,
framebuffer,
lh,
)
.context(here!())?;
huffw.flush_with_escape(writer).context(here!())?;
}
}
Ok(())
}
pub fn jpeg_write_entire_scan<W: Write>(
writer: &mut W,
framebuffer: &[BlockBasedImage],
lh: &LeptonHeader,
) -> Result<()> {
let mut last_dc = [0i16; 4];
let mut huffw = BitWriter::new();
let max_coded_heights = lh.truncate_components.get_max_coded_heights();
let mut decode_index = 0;
loop {
let cur_row = RowSpec::get_row_spec_from_index(
decode_index,
framebuffer,
lh.truncate_components.mcu_count_vertical,
&max_coded_heights[..],
);
decode_index += 1;
if cur_row.done {
break;
}
if cur_row.skip {
continue;
}
if cur_row.last_row_to_complete_mcu {
let r = recode_one_mcu_row(
&mut huffw,
cur_row.mcu_row_index * lh.jpeg_header.mcuh,
writer,
&mut last_dc,
framebuffer,
lh,
)
.context(here!())?;
huffw.flush_with_escape(writer).context(here!())?;
if r {
break;
}
}
}
huffw.flush_with_escape(writer).context(here!())?;
Ok(())
}
#[inline(never)]
fn recode_one_mcu_row<W: Write>(
huffw: &mut BitWriter,
mcu: i32,
writer: &mut W,
lastdc: &mut [i16],
framebuffer: &[BlockBasedImage],
ch: &LeptonHeader,
) -> Result<bool> {
let jf = &ch.jpeg_header;
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 = [0i16; 64]; for bpos in 0..64 {
block[bpos] = current_block.get_coefficient_zigzag(bpos);
}
let dc = block[0];
block[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,
);
huffw.flush_with_escape(writer).context(here!())?;
sta = state.next_mcu_pos(&jf);
} else if jf.cs_to == 0 {
if jf.cs_sah == 0 {
let tmp = current_block.get_coefficient_zigzag(0) >> jf.cs_sal;
let v = tmp - lastdc[state.get_cmp()];
lastdc[state.get_cmp()] = tmp;
write_coef(huffw, v, 0, jf.get_huff_dc_codes(state.get_cmp()));
} else {
huffw.write(
((current_block.get_coefficient_zigzag(0) >> jf.cs_sal) & 1) as u32,
1,
);
}
huffw.flush_with_escape(writer).context(here!())?;
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_coefficient_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(here!())?;
sta = state.next_mcu_pos(jf);
if sta != JPegDecodeStatus::DecodeInProgress {
encode_eobrun(huffw, jf.get_huff_ac_codes(state.get_cmp()), &mut state);
}
huffw.flush_with_escape(writer).context(here!())?;
} 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(here!())?;
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);
}
huffw.flush_with_escape(writer).context(here!())?;
}
}
if old_mcu != state.get_mcu() && state.get_mcu() % jf.mcuh == 0 {
end_of_row = true;
if sta == JPegDecodeStatus::DecodeInProgress {
huffw.flush_with_escape(writer).context(here!())?;
return Ok(false);
}
}
huffw.flush_with_escape(writer).context(here!())?;
}
huffw.pad(ch.pad_bit.unwrap_or(0));
assert!(
huffw.has_no_remainder(),
"shouldnt have a remainder after padding"
);
huffw.flush_with_escape(writer).context(here!())?;
if sta == JPegDecodeStatus::ScanCompleted {
return Ok(true); } else {
assert!(sta == JPegDecodeStatus::RestartIntervalExpired);
if jf.rsti > 0 {
if ch.rst_cnt.len() == 0
|| (!ch.rst_cnt_set)
|| cumulative_reset_markers < ch.rst_cnt[ch.scnc]
{
let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8;
writer.write_u8(0xFF)?;
writer.write_u8(rst)?;
cumulative_reset_markers += 1;
}
state.reset_rstw(jf);
for i in 0..lastdc.len() {
lastdc[i] = 0;
}
}
}
}
Ok(false)
}
#[inline(never)]
fn encode_block_seq(
huffw: &mut BitWriter,
dctbl: &HuffCodes,
actbl: &HuffCodes,
block: &[i16; 64],
) {
write_coef(huffw, block[0], 0, dctbl);
let mut z = 0;
for bpos in 1..64 {
let tmp = block[bpos];
if tmp == 0 {
z += 1;
continue;
}
while z >= 16 {
huffw.write(actbl.c_val[0xF0].into(), actbl.c_len[0xF0].into());
z -= 16;
}
write_coef(huffw, tmp, z, actbl);
z = 0;
}
if z != 0 {
huffw.write(actbl.c_val[0x00].into(), actbl.c_len[0x00].into());
}
}
#[inline(always)]
fn write_coef(huffw: &mut BitWriter, coef: i16, z: u8, tbl: &HuffCodes) {
let (n, s) = envli(coef);
let hc = ((z & 0xf) << 4) + s;
let val = (u32::from(tbl.c_val[usize::from(hc)]) << s) | u32::from(n);
let new_bits = u32::from(tbl.c_len[usize::from(hc)]) + u32::from(s);
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, 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(here!());
}
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, 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(here!());
}
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(x as u32, 1);
}
}
fn div_pow2(v: i16, p: u8) -> i16 {
(if v < 0 { v + ((1 << p) - 1) } else { v }) >> p
}
#[inline(always)]
fn envli(v: i16) -> (u16, u8) {
let mask = v >> 15; let abs = ((v ^ mask) - mask) as u16;
let s = u16_bit_length(abs);
let n = (v + (((1 << s) - 1) & mask)) as u16;
debug_assert_eq!(n, if v > 0 { v } else { v - 1 + (1 << s) } as u16);
return (n, s);
}
fn encode_eobrun_bits(s: u8, v: u16) -> u16 {
v - (1 << s)
}