use crate::consts::{JpegDecodeStatus, JpegType};
use crate::lepton_error::{AddContext, ExitCode, err_exit_code};
use crate::{LeptonError, Result};
use super::jpeg_header::{HuffCodes, JpegHeader};
pub struct JpegPositionState {
cmp: usize,
mcu: u32,
csc: usize,
sub: u32,
dpos: u32,
rstw: u32,
pub eobrun: u16,
pub prev_eobrun: u16,
}
impl JpegPositionState {
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;
let state = JpegPositionState {
cmp,
mcu,
csc: 0,
sub: 0,
dpos: mcu * mcumul,
rstw: if jf.rsti != 0 {
jf.rsti - (mcu % jf.rsti)
} else {
0
},
eobrun: 0,
prev_eobrun: 0,
};
return state;
}
pub fn get_mcu(&self) -> u32 {
self.mcu
}
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) -> u32 {
if self.rstw != 0 {
self.get_mcu() / jf.rsti
} else {
0
}
}
pub fn reset_rstw(&mut self, jf: &JpegHeader) {
self.rstw = jf.rsti;
self.prev_eobrun = 0;
}
fn next_mcu_pos_noninterleaved(&mut self, jf: &JpegHeader) -> JpegDecodeStatus {
self.dpos += 1;
let cmp_info = &jf.cmp_info[self.cmp];
if cmp_info.bch != cmp_info.nch && self.dpos % cmp_info.bch == cmp_info.nch {
self.dpos += cmp_info.bch - cmp_info.nch;
}
if cmp_info.bcv != cmp_info.ncv && self.dpos / cmp_info.bch == cmp_info.ncv {
self.dpos = cmp_info.bc;
}
if jf.jpeg_type == JpegType::Sequential {
self.mcu = self.dpos / (cmp_info.sfv * cmp_info.sfh);
}
if self.dpos >= cmp_info.bc {
return JpegDecodeStatus::ScanCompleted;
} else if jf.rsti > 0 {
self.rstw -= 1;
if self.rstw == 0 {
return JpegDecodeStatus::RestartIntervalExpired;
}
}
return JpegDecodeStatus::DecodeInProgress;
}
pub fn next_mcu_pos(&mut self, jf: &JpegHeader) -> JpegDecodeStatus {
if jf.cs_cmpc == 1 {
return self.next_mcu_pos_noninterleaved(jf);
}
let mut sta = JpegDecodeStatus::DecodeInProgress; let local_mcuh = jf.mcuh.get();
let mut local_mcu = self.mcu;
let mut local_cmp = self.cmp;
self.sub += 1;
let mut local_sub = self.sub;
if local_sub >= jf.cmp_info[local_cmp].mbs {
self.sub = 0;
local_sub = 0;
self.csc += 1;
if self.csc >= jf.cs_cmpc {
self.csc = 0;
self.cmp = jf.cs_cmp[0];
local_cmp = self.cmp;
self.mcu += 1;
local_mcu = self.mcu;
if local_mcu >= jf.mcuc {
sta = JpegDecodeStatus::ScanCompleted;
} else if jf.rsti > 0 {
self.rstw -= 1;
if self.rstw == 0 {
sta = JpegDecodeStatus::RestartIntervalExpired;
}
}
} else {
self.cmp = jf.cs_cmp[self.csc];
local_cmp = self.cmp;
}
}
let sfh = jf.cmp_info[local_cmp].sfh;
let sfv = jf.cmp_info[local_cmp].sfv;
if sfh > 1 {
let mcu_over_mcuh = local_mcu / local_mcuh;
let sub_over_sfv = local_sub / sfv;
let mcu_mod_mcuh = local_mcu - (mcu_over_mcuh * local_mcuh);
let sub_mod_sfv = local_sub - (sub_over_sfv * sfv);
let mut local_dpos = (mcu_over_mcuh * sfh) + sub_over_sfv;
local_dpos *= jf.cmp_info[local_cmp].bch;
local_dpos += (mcu_mod_mcuh * sfv) + sub_mod_sfv;
self.dpos = local_dpos;
} else if sfv > 1 {
self.dpos = (local_mcu * jf.cmp_info[local_cmp].mbs) + local_sub;
} else {
self.dpos = self.mcu;
}
return sta;
}
pub fn skip_eobrun(&mut self, jf: &JpegHeader) -> Result<JpegDecodeStatus> {
assert!(jf.cs_cmpc == 1, "this code only works for non-interleved");
if (self.eobrun) == 0 {
return Ok(JpegDecodeStatus::DecodeInProgress);
}
if jf.rsti > 0 {
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 -= u32::from(self.eobrun);
}
}
fn checked_add(a: u32, b: u32) -> Result<u32> {
a.checked_add(b)
.ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, "integer overflow"))
}
let cmp_info = &jf.cmp_info[self.cmp];
if cmp_info.bch != cmp_info.nch {
self.dpos = checked_add(
self.dpos,
(((self.dpos % cmp_info.bch) + u32::from(self.eobrun)) / cmp_info.nch)
* (cmp_info.bch - cmp_info.nch),
)
.context()?;
}
if cmp_info.bcv != cmp_info.ncv && self.dpos / cmp_info.bch >= cmp_info.ncv {
self.dpos =
checked_add(self.dpos, (cmp_info.bcv - cmp_info.ncv) * cmp_info.bch).context()?;
}
self.dpos = checked_add(self.dpos, u32::from(self.eobrun)).context()?;
self.eobrun = 0;
if self.dpos == cmp_info.bc {
Ok(JpegDecodeStatus::ScanCompleted)
} else if self.dpos > cmp_info.bc {
err_exit_code(
ExitCode::UnsupportedJpeg,
"skip_eobrun: position extended passed block count",
)
.context()
} else if jf.rsti > 0 && self.rstw == 0 {
Ok(JpegDecodeStatus::RestartIntervalExpired)
} else {
Ok(JpegDecodeStatus::DecodeInProgress)
}
}
pub fn check_optimal_eobrun(
&mut self,
is_current_block_empty: bool,
hc: &HuffCodes,
) -> Result<()> {
if is_current_block_empty {
if self.prev_eobrun > 0 && self.prev_eobrun < hc.max_eob_run - 1 {
return err_exit_code(
ExitCode::UnsupportedJpeg,
format!(
"non optimal eobruns not supported (could have encoded up to {0} zero runs in a row, but only did {1} followed by {2}",
hc.max_eob_run,
self.prev_eobrun + 1,
self.eobrun + 1
),
);
}
}
self.prev_eobrun = self.eobrun;
Ok(())
}
}