use std::cmp::min;
use std::io::{Cursor, ErrorKind, Read, Seek, Write};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use default_boxed::DefaultBoxed;
use flate2::Compression;
use flate2::read::ZlibDecoder;
use flate2::write::ZlibEncoder;
use crate::EnabledFeatures;
use crate::consts::*;
use crate::helpers::buffer_prefix_matches_marker;
use crate::jpeg::jpeg_header::{JpegHeader, ReconstructionInfo};
use crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};
use crate::structs::thread_handoff::ThreadHandoff;
pub const FIXED_HEADER_SIZE: usize = 28;
#[derive(Debug, DefaultBoxed)]
pub struct LeptonHeader {
pub raw_jpeg_header_read_index: usize,
pub thread_handoff: Vec<ThreadHandoff>,
pub jpeg_header: JpegHeader,
pub rinfo: ReconstructionInfo,
pub jpeg_file_size: u32,
pub uncompressed_lepton_header_size: Option<u32>,
pub git_revision_prefix: [u8; 4],
pub encoder_version: u8,
}
impl LeptonHeader {
pub fn bad_truncation_version(&self) -> bool {
self.encoder_version == 55
}
pub fn read_lepton_fixed_header(
&mut self,
header: &[u8; FIXED_HEADER_SIZE],
enabled_features: &mut EnabledFeatures,
) -> Result<usize> {
if header[0..2] != LEPTON_FILE_HEADER[0..2] {
return err_exit_code(ExitCode::BadLeptonFile, "header doesn't match");
}
if header[2] != LEPTON_VERSION {
return err_exit_code(
ExitCode::VersionUnsupported,
format!("incompatible file with version {0}", header[3]),
);
}
if header[3] != LEPTON_HEADER_BASELINE_JPEG_TYPE[0]
&& header[3] != LEPTON_HEADER_PROGRESSIVE_JPEG_TYPE[0]
{
return err_exit_code(
ExitCode::BadLeptonFile,
format!("Unknown filetype in header {0}", header[4]),
);
}
if header[8] == 'M' as u8 && header[9] == 'S' as u8 {
self.uncompressed_lepton_header_size =
Some(u32::from_le_bytes(header[10..14].try_into().unwrap()));
let flags = header[14];
if (flags & 0x80) != 0 {
enabled_features.use_16bit_dc_estimate = (flags & 0x01) != 0;
enabled_features.use_16bit_adv_predict = (flags & 0x02) != 0;
}
self.encoder_version = header[15];
self.git_revision_prefix = header[16..20].try_into().unwrap();
} else {
self.git_revision_prefix = header[8..12].try_into().unwrap();
}
self.jpeg_file_size = u32::from_le_bytes(header[20..24].try_into().unwrap());
let compressed_header_size =
u32::from_le_bytes(header[24..28].try_into().unwrap()) as usize;
Ok(compressed_header_size)
}
pub fn read_compressed_lepton_header<R: Read>(
&mut self,
reader: &mut R,
enabled_features: &mut EnabledFeatures,
compressed_header_size: usize,
) -> Result<()> {
if compressed_header_size > enabled_features.max_jpeg_file_size as usize {
return err_exit_code(ExitCode::BadLeptonFile, "Too big compressed header");
}
if self.jpeg_file_size > enabled_features.max_jpeg_file_size {
return err_exit_code(
ExitCode::BadLeptonFile,
format!(
"Only support images < {} megs",
enabled_features.max_jpeg_file_size / (1024 * 1024)
),
);
}
let mut compressed_reader = reader.take(compressed_header_size as u64);
self.rinfo.raw_jpeg_header = self
.read_lepton_compressed_header(&mut compressed_reader)
.context()?;
self.raw_jpeg_header_read_index = 0;
{
let mut header_data_cursor = Cursor::new(&self.rinfo.raw_jpeg_header[..]);
self.jpeg_header
.parse(&mut header_data_cursor, &enabled_features)
.context()?;
self.raw_jpeg_header_read_index = header_data_cursor.position() as usize;
}
self.rinfo.truncate_components.init(&self.jpeg_header);
if self.rinfo.early_eof_encountered {
self.rinfo
.truncate_components
.set_truncation_bounds(&self.jpeg_header, self.rinfo.max_dpos);
}
let num_threads = self.thread_handoff.len();
let max_luma = self.rinfo.truncate_components.get_block_height(0);
for i in 0..num_threads {
self.thread_handoff[i].luma_y_start =
min(self.thread_handoff[i].luma_y_start, max_luma);
self.thread_handoff[i].luma_y_end = min(self.thread_handoff[i].luma_y_end, max_luma);
}
self.thread_handoff[num_threads - 1].luma_y_end = max_luma;
if self.rinfo.early_eof_encountered {
let mut max_last_segment_size = self.jpeg_file_size
- u32::try_from(self.rinfo.garbage_data.len())?
- u32::try_from(self.raw_jpeg_header_read_index)?
- u32::try_from(SOI.len())?;
for i in 0..num_threads - 1 {
max_last_segment_size -= self.thread_handoff[i].segment_size;
}
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 {
last.segment_size = max_last_segment_size;
}
}
Ok(())
}
pub fn advance_next_header_segment(
&mut self,
enabled_features: &EnabledFeatures,
) -> Result<bool> {
let mut header_cursor =
Cursor::new(&self.rinfo.raw_jpeg_header[self.raw_jpeg_header_read_index..]);
let result = self
.jpeg_header
.parse(&mut header_cursor, enabled_features)
.context()?;
self.raw_jpeg_header_read_index += header_cursor.stream_position()? as usize;
Ok(result)
}
fn read_lepton_compressed_header<R: Read>(&mut self, src: &mut R) -> Result<Vec<u8>> {
let mut header_reader = ZlibDecoder::new(src);
let mut hdr_buf: [u8; 3] = [0; 3];
header_reader.read_exact(&mut hdr_buf)?;
if !buffer_prefix_matches_marker(hdr_buf, LEPTON_HEADER_MARKER) {
return err_exit_code(ExitCode::BadLeptonFile, "HDR marker not found");
}
let hdrs = header_reader.read_u32::<LittleEndian>()? as usize;
let mut hdr_data = Vec::new();
hdr_data.resize(hdrs, 0);
header_reader.read_exact(&mut hdr_data)?;
if self.rinfo.garbage_data.len() == 0 {
self.rinfo.garbage_data.extend(EOI);
}
loop {
let mut current_lepton_marker = [0u8; 3];
match header_reader.read_exact(&mut current_lepton_marker) {
Ok(_) => {}
Err(e) => {
if e.kind() == ErrorKind::UnexpectedEof {
break;
} else {
return Err(e.into());
}
}
}
if buffer_prefix_matches_marker(current_lepton_marker, LEPTON_HEADER_PAD_MARKER) {
self.rinfo.pad_bit = Some(header_reader.read_u8()?);
} else if buffer_prefix_matches_marker(
current_lepton_marker,
LEPTON_HEADER_JPG_RESTARTS_MARKER,
) {
self.rinfo.rst_cnt_set = true;
let rst_count = header_reader.read_u32::<LittleEndian>()?;
for _i in 0..rst_count {
self.rinfo
.rst_cnt
.push(header_reader.read_u32::<LittleEndian>()?);
}
} else if buffer_prefix_matches_marker(
current_lepton_marker,
LEPTON_HEADER_LUMA_SPLIT_MARKER,
) {
let mut thread_handoffs =
ThreadHandoff::deserialize(current_lepton_marker[2], &mut header_reader)?;
self.thread_handoff.append(&mut thread_handoffs);
} else if buffer_prefix_matches_marker(
current_lepton_marker,
LEPTON_HEADER_JPG_RESTART_ERRORS_MARKER,
) {
let rst_err_count = header_reader.read_u32::<LittleEndian>()? as usize;
let mut rst_err_data = Vec::<u8>::new();
rst_err_data.resize(rst_err_count, 0);
header_reader.read_exact(&mut rst_err_data)?;
self.rinfo.rst_err.append(&mut rst_err_data);
} else if buffer_prefix_matches_marker(
current_lepton_marker,
LEPTON_HEADER_GARBAGE_MARKER,
) {
let garbage_size = header_reader.read_u32::<LittleEndian>()? as usize;
let mut garbage_data_array = Vec::<u8>::new();
garbage_data_array.resize(garbage_size, 0);
header_reader.read_exact(&mut garbage_data_array)?;
self.rinfo.garbage_data = garbage_data_array;
} else if buffer_prefix_matches_marker(
current_lepton_marker,
LEPTON_HEADER_EARLY_EOF_MARKER,
) {
self.rinfo.max_cmp = header_reader.read_u32::<LittleEndian>()?;
self.rinfo.max_bpos = header_reader.read_u32::<LittleEndian>()?;
self.rinfo.max_sah = u8::try_from(header_reader.read_u32::<LittleEndian>()?)?;
self.rinfo.max_dpos[0] = header_reader.read_u32::<LittleEndian>()?;
self.rinfo.max_dpos[1] = header_reader.read_u32::<LittleEndian>()?;
self.rinfo.max_dpos[2] = header_reader.read_u32::<LittleEndian>()?;
self.rinfo.max_dpos[3] = header_reader.read_u32::<LittleEndian>()?;
self.rinfo.early_eof_encountered = true;
} else {
return err_exit_code(ExitCode::BadLeptonFile, "unknown data found");
}
}
let mut remaining_buf = Vec::new();
let remaining = header_reader.read_to_end(&mut remaining_buf)?;
assert!(remaining == 0);
return Ok(hdr_data);
}
pub fn write_lepton_header<W: Write>(
&self,
writer: &mut W,
enabled_features: &EnabledFeatures,
) -> Result<()> {
let mut lepton_header = Vec::<u8>::new();
{
let mut mrw = Cursor::new(&mut lepton_header);
self.write_lepton_jpeg_header(&mut mrw)?;
self.write_lepton_pad_bit(&mut mrw)?;
self.write_lepton_luma_splits(&mut mrw)?;
self.write_lepton_jpeg_restarts_if_needed(&mut mrw)?;
self.write_lepton_jpeg_restart_errors_if_needed(&mut mrw)?;
self.write_lepton_early_eof_truncation_data_if_needed(&mut mrw)?;
self.write_lepton_jpeg_garbage_if_needed(&mut mrw, false)?;
}
let mut compressed_header = Vec::<u8>::new(); {
let mut c = Cursor::new(&mut compressed_header);
let mut encoder = ZlibEncoder::new(&mut c, Compression::default());
encoder.write_all(&lepton_header[..]).context()?;
encoder.finish().context()?;
}
writer.write_all(&LEPTON_FILE_HEADER)?;
writer.write_u8(LEPTON_VERSION)?;
if self.jpeg_header.jpeg_type == JpegType::Progressive {
writer.write_all(&LEPTON_HEADER_PROGRESSIVE_JPEG_TYPE)?;
} else {
writer.write_all(&LEPTON_HEADER_BASELINE_JPEG_TYPE)?;
}
writer.write_u8(self.thread_handoff.len() as u8)?;
writer.write_all(&[0; 3])?;
writer.write_u8('M' as u8)?;
writer.write_u8('S' as u8)?;
writer.write_u32::<LittleEndian>(lepton_header.len() as u32)?;
writer.write_u8(
0x80 | if enabled_features.use_16bit_dc_estimate {
1
} else {
0
} | if enabled_features.use_16bit_adv_predict {
2
} else {
0
},
)?;
writer.write_u8(self.encoder_version)?;
writer.write_all(&self.git_revision_prefix)?;
writer.write_u32::<LittleEndian>(self.jpeg_file_size)?;
writer.write_u32::<LittleEndian>(compressed_header.len() as u32)?;
writer.write_all(&compressed_header[..])?;
writer.write_all(&LEPTON_HEADER_COMPLETION_MARKER)?;
Ok(())
}
fn write_lepton_jpeg_header<W: Write>(&self, mrw: &mut W) -> Result<()> {
mrw.write_all(&LEPTON_HEADER_MARKER)?;
mrw.write_u32::<LittleEndian>(self.rinfo.raw_jpeg_header.len() as u32)?;
mrw.write_all(&self.rinfo.raw_jpeg_header[..])?;
Ok(())
}
fn write_lepton_pad_bit<W: Write>(&self, mrw: &mut W) -> Result<()> {
mrw.write_all(&LEPTON_HEADER_PAD_MARKER)?;
mrw.write_u8(self.rinfo.pad_bit.unwrap_or(0))?;
Ok(())
}
fn write_lepton_luma_splits<W: Write>(&self, mrw: &mut W) -> Result<()> {
mrw.write_all(&LEPTON_HEADER_LUMA_SPLIT_MARKER)?;
ThreadHandoff::serialize(&self.thread_handoff, mrw)?;
Ok(())
}
fn write_lepton_jpeg_restarts_if_needed<W: Write>(&self, mrw: &mut W) -> Result<()> {
if self.rinfo.rst_cnt.len() > 0 {
mrw.write_all(&LEPTON_HEADER_JPG_RESTARTS_MARKER)?;
mrw.write_u32::<LittleEndian>(self.rinfo.rst_cnt.len() as u32)?;
for i in 0..self.rinfo.rst_cnt.len() {
mrw.write_u32::<LittleEndian>(self.rinfo.rst_cnt[i])?;
}
}
Ok(())
}
fn write_lepton_jpeg_restart_errors_if_needed<W: Write>(&self, mrw: &mut W) -> Result<()> {
if self.rinfo.rst_err.len() > 0 {
mrw.write_all(&LEPTON_HEADER_JPG_RESTART_ERRORS_MARKER)?;
mrw.write_u32::<LittleEndian>(self.rinfo.rst_err.len() as u32)?;
mrw.write_all(&self.rinfo.rst_err[..])?;
}
Ok(())
}
fn write_lepton_early_eof_truncation_data_if_needed<W: Write>(
&self,
mrw: &mut W,
) -> Result<()> {
if self.rinfo.early_eof_encountered {
mrw.write_all(&LEPTON_HEADER_EARLY_EOF_MARKER)?;
mrw.write_u32::<LittleEndian>(self.rinfo.max_cmp)?;
mrw.write_u32::<LittleEndian>(self.rinfo.max_bpos)?;
mrw.write_u32::<LittleEndian>(u32::from(self.rinfo.max_sah))?;
mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[0])?;
mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[1])?;
mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[2])?;
mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[3])?;
}
Ok(())
}
fn write_lepton_jpeg_garbage_if_needed<W: Write>(
&self,
mrw: &mut W,
prefix_garbage: bool,
) -> Result<()> {
if self.rinfo.garbage_data.len() > 0 {
if prefix_garbage {
mrw.write_all(&LEPTON_HEADER_PREFIX_GARBAGE_MARKER)?;
} else {
mrw.write_all(&LEPTON_HEADER_GARBAGE_MARKER)?;
}
mrw.write_u32::<LittleEndian>(self.rinfo.garbage_data.len() as u32)?;
mrw.write_all(&self.rinfo.garbage_data[..])?;
}
Ok(())
}
}
#[test]
fn test_roundtrip_fixed_header() {
let test_data = [
(0, true, true),
(128, false, false),
(129, true, false),
(130, false, true),
(131, true, true),
];
for (v, dc_16_bit, adv_16_bit) in test_data {
let fixed_buffer = [
207, 132, 1, 90, 1, 0, 0, 0, 77, 83, 140, 0, 0, 0, v, 187, 18, 52, 86, 120, 123, 0, 0,
0, 122, 0, 0, 0,
];
let mut other_enabled_features = EnabledFeatures::compat_lepton_vector_read();
let mut other = LeptonHeader::default_boxed();
let compressed_header_size = other
.read_lepton_fixed_header(&fixed_buffer, &mut other_enabled_features)
.unwrap();
assert_eq!(compressed_header_size, 122);
assert_eq!(other_enabled_features.use_16bit_dc_estimate, dc_16_bit);
assert_eq!(other_enabled_features.use_16bit_adv_predict, adv_16_bit);
}
for (dc_16_bit, adv_16_bit) in [(false, false), (true, false), (false, true), (true, true)] {
let mut header = make_minimal_lepton_header();
header.git_revision_prefix = [0x12, 0x34, 0x56, 0x78];
header.encoder_version = 0xBB;
let mut enabled_features = EnabledFeatures::compat_lepton_vector_write();
enabled_features.use_16bit_dc_estimate = dc_16_bit;
enabled_features.use_16bit_adv_predict = adv_16_bit;
let (result_header, result_features) = verify_roundtrip(&header, &enabled_features);
assert_eq!(result_features.use_16bit_dc_estimate, dc_16_bit);
assert_eq!(result_features.use_16bit_adv_predict, adv_16_bit);
assert_eq!(
result_header.git_revision_prefix,
header.git_revision_prefix
);
assert_eq!(result_header.encoder_version, header.encoder_version);
}
}
#[test]
fn parse_and_write_header() {
use crate::structs::lepton_header::FIXED_HEADER_SIZE;
let lh = make_minimal_lepton_header();
let enabled_features = EnabledFeatures::compat_lepton_vector_write();
let mut serialized = Vec::new();
lh.write_lepton_header(&mut Cursor::new(&mut serialized), &enabled_features)
.unwrap();
let mut other = LeptonHeader::default_boxed();
let mut other_reader = Cursor::new(&serialized);
let mut fixed_buffer = [0; FIXED_HEADER_SIZE];
other_reader.read_exact(&mut fixed_buffer).unwrap();
let mut other_enabled_features = EnabledFeatures::compat_lepton_vector_read();
let compressed_header_size = other
.read_lepton_fixed_header(&fixed_buffer, &mut other_enabled_features)
.unwrap();
other
.read_compressed_lepton_header(
&mut other_reader,
&mut other_enabled_features,
compressed_header_size,
)
.unwrap();
assert_eq!(
lh.uncompressed_lepton_header_size,
other.uncompressed_lepton_header_size
);
assert_eq!(lh.git_revision_prefix, other.git_revision_prefix);
assert_eq!(lh.encoder_version, other.encoder_version);
assert_eq!(lh.jpeg_file_size, other.jpeg_file_size);
assert_eq!(lh.rinfo.raw_jpeg_header, other.rinfo.raw_jpeg_header);
assert_eq!(lh.thread_handoff, other.thread_handoff);
}
#[cfg(test)]
fn make_minimal_lepton_header() -> Box<LeptonHeader> {
use crate::jpeg::jpeg_header::parse_jpeg_header;
let min_jpeg = [
0xffu8, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00,
0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03,
0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09,
0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b,
0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xC1, 0x00, 0x0b, 0x08, 0x00,
0x10, 0x00, 0x10, 0x01, 0x01, 0x11, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, ];
let enabled_features = EnabledFeatures::compat_lepton_vector_read();
let mut lh = LeptonHeader::default_boxed();
lh.jpeg_file_size = 123;
lh.uncompressed_lepton_header_size = Some(156);
parse_jpeg_header(
&mut Cursor::new(min_jpeg),
&enabled_features,
&mut lh.jpeg_header,
&mut lh.rinfo,
)
.unwrap();
lh.thread_handoff.push(ThreadHandoff {
luma_y_start: 0,
luma_y_end: 1,
segment_offset_in_file: 0, segment_size: 500,
overhang_byte: 0,
num_overhang_bits: 1,
last_dc: [1, 2, 3, 0],
});
lh.thread_handoff.push(ThreadHandoff {
luma_y_start: 1,
luma_y_end: 2,
segment_offset_in_file: 0,
segment_size: 600,
overhang_byte: 1,
num_overhang_bits: 2,
last_dc: [2, 3, 4, 0],
});
lh
}
#[cfg(test)]
fn verify_roundtrip(
header: &LeptonHeader,
enabled_features: &EnabledFeatures,
) -> (Box<LeptonHeader>, EnabledFeatures) {
let mut output = Vec::new();
header
.write_lepton_header(&mut output, &enabled_features)
.unwrap();
let mut read_header = LeptonHeader::default_boxed();
let mut read_enabled_features = EnabledFeatures::compat_lepton_vector_read();
println!("output: {:?}", &output[0..FIXED_HEADER_SIZE]);
read_header
.read_lepton_fixed_header(
&output[..FIXED_HEADER_SIZE].try_into().unwrap(),
&mut read_enabled_features,
)
.unwrap();
read_header
.read_compressed_lepton_header(
&mut Cursor::new(&output[FIXED_HEADER_SIZE..]),
&mut read_enabled_features,
output.len() - FIXED_HEADER_SIZE,
)
.unwrap();
(read_header, read_enabled_features)
}