#![deny(unsafe_code)]
#![deny(missing_docs)]
extern crate alloc;
mod baseline;
mod bits;
mod color;
mod huffman;
mod idct;
mod parser;
mod progressive;
mod upsample;
pub mod ffi;
pub mod reference;
use alloc::vec::Vec;
use pith_digest::{Error, Result};
use pith_image::raster::{Gray, Image, Rgb};
use parser::{Frame, Marker, Parser};
#[derive(Clone, Debug)]
pub enum Jpeg {
Gray(Image<Gray, u8>),
Rgb(Image<Rgb, u8>),
}
impl Jpeg {
pub fn width(&self) -> u32 {
match self {
Jpeg::Gray(i) => i.width(),
Jpeg::Rgb(i) => i.width(),
}
}
pub fn height(&self) -> u32 {
match self {
Jpeg::Gray(i) => i.height(),
Jpeg::Rgb(i) => i.height(),
}
}
}
pub fn decode(data: &[u8]) -> Result<Jpeg> {
let mut p = Parser::new(data);
p.soi()?;
let mut tables = parser::Tables::default();
let mut frame: Option<Frame> = None;
let mut saw_scan = false;
let mut saw_eoi = false;
while !saw_eoi {
let marker = p.next_marker()?;
match marker {
Marker::Eoi => saw_eoi = true,
Marker::App(_) | Marker::Com => {
let seg = p.segment()?;
if let Marker::App(14) = marker {
parser::parse_app14(seg, &mut tables);
}
}
Marker::Dqt => parser::parse_dqt(p.segment()?, &mut tables)?,
Marker::Dht => parser::parse_dht(p.segment()?, &mut tables)?,
Marker::Dri => {
let seg = p.segment()?;
if seg.len() < 2 {
return Err(Error::truncated("DRI segment", 2, seg.len()));
}
tables.restart_interval = u16::from_be_bytes([seg[0], seg[1]]) as usize;
}
Marker::Sof(m) => {
let seg = p.segment()?;
frame = Some(parser::parse_sof(m, seg)?);
}
Marker::Sos => {
if frame.is_none() {
return Err(Error::BadValue("SOS before SOF"));
}
let seg = p.segment()?;
let f = frame.as_mut().expect("checked above");
if f.progressive {
progressive::decode_scan(&mut p, seg, f, &tables)?;
} else {
if saw_scan {
return Err(Error::BadValue("second SOS in sequential JPEG"));
}
baseline::decode_scan(&mut p, seg, f, &tables)?;
}
saw_scan = true;
}
Marker::Dnl => {
let _ = p.maybe_segment();
}
Marker::Rst(_) => {
}
Marker::Dac => {
let _ = p.segment()?;
return Err(Error::Unsupported(
"arithmetic entropy coding (DAC present)",
));
}
Marker::Dhp | Marker::Exp | Marker::Jpg | Marker::JpgExtension(_) => {
let _ = p.maybe_segment();
}
}
}
let frame = frame.ok_or(Error::BadValue("no frame (SOF) found"))?;
if !saw_scan {
return Err(Error::Truncated {
what: "scan data",
needed: 1,
found: 0,
});
}
finish(&frame, &tables)
}
fn finish(frame: &Frame, tables: &parser::Tables) -> Result<Jpeg> {
let sample_cap = pith_image::raster::MAX_BUFFER_BYTES;
let total_coefs: usize = frame.comps.iter().map(|c| c.coefs.len()).sum();
if total_coefs > sample_cap {
return Err(Error::too_large("coefficient buffers", sample_cap));
}
let mut planes: Vec<Vec<u8>> = Vec::with_capacity(frame.comps.len());
for comp in frame.comps.iter() {
let qt = tables.quant[comp.tq as usize]
.as_ref()
.ok_or(Error::BadValue("scan uses missing quantization table"))?;
let pw = comp.blocks_w * 8;
let ph = comp.blocks_h * 8;
if pw.checked_mul(ph).is_none_or(|n| n > sample_cap * 4) {
return Err(Error::too_large("component plane", sample_cap * 4));
}
let mut plane = alloc::vec![0u8; pw * ph];
for by in 0..comp.blocks_h {
for bx in 0..comp.blocks_w {
let bi = by * comp.blocks_w + bx;
let block = &comp.coefs[bi * 64..bi * 64 + 64];
idct::dequant_idct_into(block, qt, &mut plane, pw, bx * 8, by * 8);
}
}
planes.push(plane);
}
let mut full: Vec<Vec<u8>> = Vec::with_capacity(frame.comps.len());
for (comp, plane) in frame.comps.iter().zip(planes.iter()) {
full.push(upsample::to_full_size(
comp,
plane,
comp.blocks_w * 8,
frame,
)?);
}
color::convert(frame, &full, tables.adobe_transform)
}