use crate::entropy::Reader;
use crate::format::{
AdobeTransform, Frame, IccChunks, Scan, ZIGZAG, adobe_transform, exif_orientation, marker,
};
use crate::huffman::HuffmanTable;
use crate::idct::{self, Scale};
use crate::progressive::{Coefficients, MAX_SCANS, Tables};
use otf_pixels_core::{
Codec, Comment, DecodeCapability, Decoder, Density, Format, ImageDescriptor, Limits,
Orientation, PixelFormat, PixelsError, ResolutionUnit, Result, Source, SourceInfo,
};
const XMP_ID: &[u8] = b"http://ns.adobe.com/xap/1.0/\0";
fn jfif_density(payload: &[u8]) -> Option<Density> {
let rest = payload.strip_prefix(b"JFIF\0")?;
let (&unit, rest) = rest.get(2..)?.split_first()?;
let x = u16::from_be_bytes([*rest.first()?, *rest.get(1)?]);
let y = u16::from_be_bytes([*rest.get(2)?, *rest.get(3)?]);
let unit = match unit {
1 => ResolutionUnit::Inch,
2 => ResolutionUnit::Centimetre,
_ => return None,
};
Some(Density {
x: f64::from(x),
y: f64::from(y),
unit,
})
}
fn chroma_name(frame: &Frame) -> Option<&'static str> {
let [luma, chroma, ..] = &frame.components[..] else {
return None;
};
let (h, v) = (luma.h / chroma.h.max(1), luma.v / chroma.v.max(1));
let base = match (h, v) {
(1, 1) => "4:4:4",
(2, 1) => "4:2:2",
(2, 2) => "4:2:0",
(1, 2) => "4:4:0",
(4, 1) => "4:1:1",
(4, 2) => "4:1:0",
_ => return None,
};
Some(if frame.components.len() == 4 {
match base {
"4:4:4" => "4:4:4:4",
"4:2:2" => "4:2:2:4",
"4:2:0" => "4:2:0:4",
"4:4:0" => "4:4:0:4",
"4:1:1" => "4:1:1:4",
_ => "4:1:0:4",
}
} else {
base
})
}
const MAX_BLOCKS_PER_MCU: u32 = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Colour {
Grayscale,
YCbCr,
Rgb,
}
#[derive(Debug)]
struct Plane {
stride: usize,
samples: Vec<u8>,
columns: Vec<u32>,
rows: Vec<u32>,
}
#[derive(Debug)]
pub struct JpegDecoder<S: Source> {
inner: Box<Engine<S>>,
icc: Option<Vec<u8>>,
exif: Option<Vec<u8>>,
xmp: Option<Vec<u8>>,
info: SourceInfo,
}
impl<S: Source> JpegDecoder<S> {
pub fn new(source: S, limits: Limits) -> Result<Self> {
Self::with_scale(source, limits, Scale::Full)
}
pub fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
let mut inner = Box::new(Engine::with_scale(source, limits, scale)?);
let icc = inner.icc.take();
let exif = inner.exif.take();
let xmp = inner.xmp.take();
let info = std::mem::take(&mut inner.info);
Ok(Self {
inner,
icc,
exif,
xmp,
info,
})
}
#[must_use]
pub const fn scale(&self) -> Scale {
self.inner.scale
}
#[must_use]
pub const fn is_progressive(&self) -> bool {
self.inner.progressive
}
}
impl<S: Source + std::fmt::Debug> Decoder for JpegDecoder<S> {
fn descriptor(&self) -> ImageDescriptor {
self.inner.descriptor()
}
fn icc_profile(&self) -> Option<&[u8]> {
self.icc.as_deref()
}
fn exif(&self) -> Option<&[u8]> {
self.exif.as_deref()
}
fn xmp(&self) -> Option<&[u8]> {
self.xmp.as_deref()
}
fn source_info(&self) -> SourceInfo {
self.info.clone()
}
fn orientation(&self) -> Orientation {
self.inner.orientation.unwrap_or_default()
}
fn capability(&self) -> DecodeCapability {
self.inner.capability()
}
fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
self.inner.reduced_descriptor(target)
}
fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
self.inner.reduce_to(descriptor)
}
fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
self.inner.read_row(out)
}
}
struct Engine<S: Source> {
reader: Reader<S>,
descriptor: ImageDescriptor,
frame: Frame,
scan: Scan,
colour: Colour,
quant: [[u16; 64]; 4],
dc_tables: [Option<HuffmanTable>; 4],
ac_tables: [Option<HuffmanTable>; 4],
restart_interval: u16,
restarts_left: u32,
orientation: Option<Orientation>,
icc: Option<Vec<u8>>,
exif: Option<Vec<u8>>,
xmp: Option<Vec<u8>>,
info: SourceInfo,
planes: Vec<Plane>,
expanded: Vec<Vec<u8>>,
predictors: Vec<i32>,
band: Vec<u8>,
band_row: u32,
band_height: u32,
mcus_per_line: u32,
mcu_rows: u32,
mcu_row: u32,
scale: Scale,
limits: Limits,
row: u32,
progressive: bool,
coefficients: Option<Coefficients>,
latched: Vec<Option<[u16; 64]>>,
}
impl<S: Source> std::fmt::Debug for Engine<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Engine")
.field("descriptor", &self.descriptor)
.field("colour", &self.colour)
.field("components", &self.frame.components)
.field("scale", &self.scale)
.field("restart_interval", &self.restart_interval)
.field("orientation", &self.orientation)
.field("row", &self.row)
.finish_non_exhaustive()
}
}
impl<S: Source> Engine<S> {
fn with_scale(source: S, limits: Limits, scale: Scale) -> Result<Self> {
let mut reader = Reader::new(source);
let mut progressive = false;
let mut quant = [[0_u16; 64]; 4];
let mut dc_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
let mut ac_tables: [Option<HuffmanTable>; 4] = [None, None, None, None];
let mut restart_interval = 0_u16;
let mut orientation = None;
let mut exif: Option<Vec<u8>> = None;
let mut xmp: Option<Vec<u8>> = None;
let mut jfif = None;
let mut comments = Vec::new();
let mut icc = IccChunks::default();
let mut adobe = None;
let mut frame: Option<Frame> = None;
let first = reader.next_marker()?;
if first != marker::SOI {
return Err(PixelsError::malformed(
"jpeg",
format!("stream begins with marker {first:#04x}, not SOI"),
));
}
let scan = loop {
let code = reader.next_marker()?;
match code {
marker::SOF0 | marker::SOF1 | marker::SOF2 => {
if frame.is_some() {
return Err(PixelsError::malformed(
"jpeg",
"stream declares more than one frame",
));
}
progressive = code == marker::SOF2;
let parsed = Frame::parse(&reader.read_segment()?)?;
limits.check(u32::from(parsed.width), u32::from(parsed.height))?;
frame = Some(parsed);
}
marker::DAC => {
return Err(PixelsError::unsupported(
"jpeg: arithmetic coding; baseline JPEG is Huffman coded",
));
}
code if marker::is_frame(code) => {
return Err(PixelsError::unsupported(format!(
"jpeg: frame type {code:#04x} is not baseline"
)));
}
marker::DQT => read_quantization_tables(&reader.read_segment()?, &mut quant)?,
marker::DHT => {
read_huffman_tables(&reader.read_segment()?, &mut dc_tables, &mut ac_tables)?;
}
marker::DRI => {
let payload = reader.read_segment()?;
let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
return Err(PixelsError::malformed(
"jpeg",
"DRI segment carries no interval",
));
};
restart_interval = u16::from_be_bytes([hi, lo]);
}
marker::APP1 => {
let payload = reader.read_segment()?;
orientation = orientation.or_else(|| exif_orientation(&payload));
if exif.is_none() {
exif = payload.strip_prefix(b"Exif\0\0").map(<[u8]>::to_vec);
}
if xmp.is_none() {
xmp = payload.strip_prefix(XMP_ID).map(<[u8]>::to_vec);
}
}
marker::APP0 => {
let payload = reader.read_segment()?;
jfif = jfif.or_else(|| jfif_density(&payload));
}
marker::COM => {
let payload = reader.read_segment()?;
let text = String::from_utf8(payload.clone())
.unwrap_or_else(|_| payload.iter().map(|&c| char::from(c)).collect());
comments.push(Comment {
keyword: String::new(),
text: text.trim_end_matches('\0').to_owned(),
});
}
marker::APP2 => icc.push(&reader.read_segment()?),
marker::APP14 => {
let payload = reader.read_segment()?;
adobe = adobe_transform(&payload).or(adobe);
}
marker::SOS => {
let Some(ref frame) = frame else {
return Err(PixelsError::malformed(
"jpeg",
"scan begins before any frame header",
));
};
break Scan::parse(&reader.read_segment()?, frame)?;
}
marker::EOI => {
return Err(PixelsError::malformed(
"jpeg",
"stream ends before any scan",
));
}
0xE3..=0xED | marker::APP15 => reader.skip_segment()?,
code if marker::is_standalone(code) => {}
_ => reader.skip_segment()?,
}
};
let Some(frame) = frame else {
return Err(PixelsError::malformed("jpeg", "stream has no frame header"));
};
if !progressive {
check_baseline_scan(&scan)?;
}
let mut info = SourceInfo::default();
info.density = jfif.or_else(|| {
exif.as_deref()
.and_then(otf_pixels_core::Density::from_exif_block)
});
info.progressive = progressive;
info.bits_per_sample = Some(frame.precision);
info.chroma_subsampling = chroma_name(&frame);
info.comments = comments;
let colour = colour_model(&frame, adobe)?;
let pixel = match colour {
Colour::Grayscale => PixelFormat::Gray8,
Colour::YCbCr | Colour::Rgb => PixelFormat::Rgb8,
};
let (full_width, full_height) = (u32::from(frame.width), u32::from(frame.height));
let descriptor = ImageDescriptor::with_limits(
scale.apply(full_width),
scale.apply(full_height),
pixel,
&limits,
)?;
if !progressive && scan.components.len() != frame.components.len() {
return Err(PixelsError::unsupported(
"jpeg: non-interleaved baseline scan",
));
}
for component in scan.components.iter().filter(|_| !progressive) {
let slot = frame
.components
.get(component.index)
.map_or(0, |c| c.quant as usize);
if quant
.get(slot)
.is_none_or(|table| table.iter().all(|&q| q == 0))
{
return Err(PixelsError::malformed(
"jpeg",
format!("scan uses quantization table {slot}, which was never defined"),
));
}
if dc_tables
.get(component.dc as usize)
.is_none_or(Option::is_none)
|| ac_tables
.get(component.ac as usize)
.is_none_or(Option::is_none)
{
return Err(PixelsError::malformed(
"jpeg",
format!(
"scan uses Huffman tables {}/{}, which were never defined",
component.dc, component.ac
),
));
}
}
let (h_max, v_max) = (u32::from(frame.h_max()), u32::from(frame.v_max()));
let blocks: u32 = frame
.components
.iter()
.map(|c| u32::from(c.h) * u32::from(c.v))
.sum();
if blocks > MAX_BLOCKS_PER_MCU {
return Err(PixelsError::malformed(
"jpeg",
format!(
"an MCU would hold {blocks} blocks; the format allows {MAX_BLOCKS_PER_MCU}"
),
));
}
let mcus_per_line = full_width.div_ceil(h_max * 8);
let mcu_rows = full_height.div_ceil(v_max * 8);
let mut decoder = Self {
reader,
descriptor,
predictors: vec![0; frame.components.len()],
frame,
scan,
colour,
quant,
dc_tables,
ac_tables,
restart_interval,
restarts_left: u32::from(restart_interval),
orientation,
icc: icc.assemble(),
exif,
xmp,
info,
limits,
planes: Vec::new(),
expanded: Vec::new(),
band: Vec::new(),
band_row: 0,
band_height: 0,
mcus_per_line,
mcu_rows,
mcu_row: 0,
scale: Scale::Full,
row: 0,
progressive,
coefficients: None,
latched: Vec::new(),
};
decoder.apply_scale(scale)?;
Ok(decoder)
}
fn apply_scale(&mut self, scale: Scale) -> Result<()> {
if self.row > 0 || self.mcu_row > 0 {
return Err(PixelsError::invalid_argument(
"scale",
format!("{} rows have already been decoded", self.row),
));
}
let full_width = u32::from(self.frame.width);
let full_height = u32::from(self.frame.height);
let (h_max, v_max) = (u32::from(self.frame.h_max()), u32::from(self.frame.v_max()));
let descriptor = ImageDescriptor::with_limits(
scale.apply(full_width),
scale.apply(full_height),
self.descriptor.pixel,
&self.limits,
)?;
let sample = scale.block_size();
let band_height = v_max * sample;
let mut planes = Vec::with_capacity(self.frame.components.len());
for component in &self.frame.components {
let (h, v) = (u32::from(component.h), u32::from(component.v));
let stride = usize::try_from(self.mcus_per_line * h * sample)
.map_err(|_| PixelsError::malformed("jpeg", "component plane overflows"))?;
let height = usize::try_from(v * sample).unwrap_or(32);
let samples = stride
.checked_mul(height)
.ok_or_else(|| PixelsError::malformed("jpeg", "component plane overflows"))?;
planes.push(Plane {
stride,
samples: vec![0_u8; samples],
columns: (0..descriptor.width).map(|x| x * h / h_max).collect(),
rows: (0..band_height).map(|y| y * v / v_max).collect(),
});
}
let band = descriptor
.row_bytes()
.checked_mul(band_height as usize)
.ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band overflows"))?;
self.descriptor = descriptor;
self.expanded = vec![vec![0_u8; descriptor.width as usize]; planes.len()];
self.planes = planes;
self.band = vec![0_u8; band];
self.band_row = band_height;
self.band_height = band_height;
self.scale = scale;
Ok(())
}
fn fill_band(&mut self) -> Result<()> {
if self.mcu_row >= self.mcu_rows {
return Err(PixelsError::malformed(
"jpeg",
"more rows were requested than the frame declares",
));
}
if self.progressive {
return self.fill_band_progressive();
}
let mut coefficients = [0_i32; 64];
let scale = self.scale;
let sample = scale.block_size() as usize;
for mcu in 0..self.mcus_per_line {
if self.restart_interval > 0 && self.restarts_left == 0 {
if !self.reader.restart()? {
let met = self.reader.pending_marker();
return Err(PixelsError::malformed(
"jpeg",
match met {
Some(code) => format!(
"expected a restart marker between MCU intervals, met {code:#04x}"
),
None => "expected a restart marker between MCU intervals".to_owned(),
},
));
}
self.predictors.iter_mut().for_each(|p| *p = 0);
self.restarts_left = u32::from(self.restart_interval);
}
for scanned in &self.scan.components {
let Some(component) = self.frame.components.get(scanned.index) else {
continue;
};
let (h, v) = (u32::from(component.h), u32::from(component.v));
let (Some(dc), Some(ac)) = (
self.dc_tables
.get(scanned.dc as usize)
.and_then(Option::as_ref),
self.ac_tables
.get(scanned.ac as usize)
.and_then(Option::as_ref),
) else {
return Err(PixelsError::malformed(
"jpeg",
"scan names a Huffman table that was never defined",
));
};
let quant = self
.quant
.get(component.quant as usize)
.ok_or_else(|| PixelsError::malformed("jpeg", "component names no table"))?;
let Some(predictor) = self.predictors.get_mut(scanned.index) else {
continue;
};
let Some(plane) = self.planes.get_mut(scanned.index) else {
continue;
};
for block_y in 0..v {
for block_x in 0..h {
decode_block(
&mut self.reader,
dc,
ac,
quant,
predictor,
&mut coefficients,
)?;
let x = ((mcu * h) + block_x) as usize * sample;
let y = block_y as usize * sample;
idct::scaled_block(
&coefficients,
scale,
&mut plane.samples,
y * plane.stride + x,
plane.stride,
);
}
}
}
if self.restart_interval > 0 {
self.restarts_left = self.restarts_left.saturating_sub(1);
}
}
self.mcu_row += 1;
self.convert_band();
self.band_row = 0;
Ok(())
}
fn fill_band_progressive(&mut self) -> Result<()> {
if self.coefficients.is_none() {
self.decode_scans()?;
}
let Some(stored) = self.coefficients.as_ref() else {
return Err(PixelsError::graph(
"jpeg coefficients vanished after decoding",
));
};
let mut natural = [0_i32; 64];
let scale = self.scale;
let sample = scale.block_size() as usize;
let mcu_row = self.mcu_row as usize;
for (index, component) in self.frame.components.iter().enumerate() {
let (h, v) = (usize::from(component.h), usize::from(component.v));
let quant = self
.latched
.get(index)
.copied()
.flatten()
.or_else(|| self.quant.get(component.quant as usize).copied())
.unwrap_or([0; 64]);
let Some(plane) = self.planes.get_mut(index) else {
continue;
};
for block_y in 0..v {
for block_x in 0..h * self.mcus_per_line as usize {
let Some(block) = stored.block(index, block_x, mcu_row * v + block_y) else {
continue;
};
for ((&at, &value), &step) in ZIGZAG.iter().zip(block).zip(&quant) {
if let Some(slot) = natural.get_mut(at) {
*slot = i32::from(value) * i32::from(step);
}
}
idct::scaled_block(
&natural,
scale,
&mut plane.samples,
block_y * sample * plane.stride + block_x * sample,
plane.stride,
);
}
}
}
self.mcu_row += 1;
self.convert_band();
self.band_row = 0;
Ok(())
}
fn decode_scans(&mut self) -> Result<()> {
let mut coefficients = Coefficients::new(&self.frame, self.mcus_per_line, self.mcu_rows)?;
let mut latched: Vec<Option<[u16; 64]>> = vec![None; self.frame.components.len()];
let mut scan = self.scan.clone();
let mut scans = 0;
'scans: loop {
scans += 1;
if scans > MAX_SCANS {
return Err(PixelsError::malformed(
"jpeg",
format!("progressive frame has more than {MAX_SCANS} scans"),
));
}
for scanned in &scan.components {
let Some(component) = self.frame.components.get(scanned.index) else {
continue;
};
if let Some(slot) = latched.get_mut(scanned.index) {
if slot.is_none() {
*slot = self.quant.get(component.quant as usize).copied();
}
}
}
let tables = Tables {
dc: &self.dc_tables,
ac: &self.ac_tables,
restart_interval: self.restart_interval,
};
crate::progressive::decode_scan(
&mut self.reader,
&self.frame,
&scan,
&tables,
&mut coefficients,
)?;
self.reader.align();
loop {
let Ok(code) = self.reader.next_marker() else {
break 'scans;
};
match code {
marker::SOS => {
scan = Scan::parse(&self.reader.read_segment()?, &self.frame)?;
continue 'scans;
}
marker::EOI => break 'scans,
marker::DHT => read_huffman_tables(
&self.reader.read_segment()?,
&mut self.dc_tables,
&mut self.ac_tables,
)?,
marker::DQT => {
read_quantization_tables(&self.reader.read_segment()?, &mut self.quant)?;
}
marker::DRI => {
let payload = self.reader.read_segment()?;
let (Some(&hi), Some(&lo)) = (payload.first(), payload.get(1)) else {
return Err(PixelsError::malformed(
"jpeg",
"DRI segment carries no interval",
));
};
self.restart_interval = u16::from_be_bytes([hi, lo]);
}
code if marker::is_frame(code) => {
return Err(PixelsError::malformed(
"jpeg",
"stream declares more than one frame",
));
}
code if marker::is_standalone(code) => {}
_ => self.reader.skip_segment()?,
}
}
}
self.coefficients = Some(coefficients);
self.latched = latched;
Ok(())
}
fn convert_band(&mut self) {
let width = self.descriptor.width as usize;
let row_bytes = self.descriptor.row_bytes();
let h_max = usize::from(self.frame.h_max());
for y in 0..self.band_height as usize {
let Some(out) = self
.band
.get_mut(y * row_bytes..)
.and_then(|rest| rest.get_mut(..row_bytes))
else {
continue;
};
for ((plane, component), expanded) in self
.planes
.iter()
.zip(&self.frame.components)
.zip(self.expanded.iter_mut())
{
let h = usize::from(component.h.max(1));
let ratio = if h_max % h == 0 { h_max / h } else { 0 };
plane.expand_row(y, ratio, expanded);
}
match self.colour {
Colour::Grayscale => {
if let Some(grey) = self.expanded.first() {
for (slot, &value) in out.iter_mut().zip(grey).take(width) {
*slot = value;
}
}
}
Colour::Rgb => {
let [a, b, c] =
[0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
for (pixel, ((&r, &g), &b)) in
out.chunks_exact_mut(3).zip(a.iter().zip(b).zip(c))
{
pixel.copy_from_slice(&[r, g, b]);
}
}
Colour::YCbCr => {
let [luma, cb, cr] =
[0, 1, 2].map(|i| self.expanded.get(i).map_or(&[][..], Vec::as_slice));
for (pixel, ((&y, &cb), &cr)) in
out.chunks_exact_mut(3).zip(luma.iter().zip(cb).zip(cr))
{
pixel.copy_from_slice(&ycbcr_to_rgb([y, cb, cr]));
}
}
}
}
}
}
impl Plane {
fn expand_row(&self, y: usize, ratio: usize, out: &mut [u8]) {
let source = self.row(y);
match ratio {
1 => {
let count = out.len().min(source.len());
if let (Some(to), Some(from)) = (out.get_mut(..count), source.get(..count)) {
to.copy_from_slice(from);
}
}
2 => {
let half = out.len() / 2;
let mut pairs = out.chunks_exact_mut(2);
for (pair, &value) in (&mut pairs).zip(source) {
pair.copy_from_slice(&[value, value]);
}
if let ([last], Some(&value)) = (pairs.into_remainder(), source.get(half)) {
*last = value;
}
}
_ => {
for (x, slot) in out.iter_mut().enumerate() {
*slot = self.sample(source, x);
}
}
}
}
fn row(&self, y: usize) -> &[u8] {
let row = self.rows.get(y).copied().unwrap_or(0) as usize;
self.samples
.get(row * self.stride..)
.and_then(|rest| rest.get(..self.stride))
.unwrap_or(&[])
}
fn sample(&self, row: &[u8], x: usize) -> u8 {
let column = self.columns.get(x).copied().unwrap_or(0) as usize;
row.get(column).copied().unwrap_or(0)
}
}
fn decode_block<S: Source>(
reader: &mut Reader<S>,
dc: &HuffmanTable,
ac: &HuffmanTable,
quant: &[u16; 64],
predictor: &mut i32,
out: &mut [i32; 64],
) -> Result<()> {
out.fill(0);
let magnitude = reader.decode(dc)?;
if magnitude > 15 {
return Err(PixelsError::malformed(
"jpeg",
format!("DC coefficient claims {magnitude} bits; 15 is the maximum"),
));
}
let difference = reader.receive_extend(u32::from(magnitude))?;
*predictor = predictor.wrapping_add(difference);
if let (Some(slot), Some(&step)) = (out.first_mut(), quant.first()) {
*slot = predictor.saturating_mul(i32::from(step));
}
let mut index = 1_usize;
while index < 64 {
let symbol = reader.decode(ac)?;
let (run, size) = ((symbol >> 4) as usize, u32::from(symbol & 0x0F));
if size == 0 {
if run != 15 {
break;
}
index += 16;
continue;
}
index += run;
if index > 63 {
return Err(PixelsError::malformed(
"jpeg",
"coefficient run passes the end of the block",
));
}
let value = reader.receive_extend(size)?;
let step = quant.get(index).copied().unwrap_or(0);
if let Some(slot) = ZIGZAG.get(index).and_then(|&at| out.get_mut(at)) {
*slot = value.saturating_mul(i32::from(step));
}
index += 1;
}
Ok(())
}
#[inline]
pub(crate) fn ycbcr_to_rgb([y, cb, cr]: [u8; 3]) -> [u8; 3] {
const HALF: i32 = 1 << 15;
let luma = (i32::from(y) << 16) + HALF;
let blue = i32::from(i16::from(cb) - 128);
let red = i32::from(i16::from(cr) - 128);
let r = (luma + (red << 16) + 26_345 * red) >> 16;
let g = (luma - 22_554 * blue - (red << 16) + 18_734 * red) >> 16;
let b = (luma + (blue << 17) - 14_942 * blue) >> 16;
[
r.clamp(0, 255) as u8,
g.clamp(0, 255) as u8,
b.clamp(0, 255) as u8,
]
}
fn colour_model(frame: &Frame, adobe: Option<AdobeTransform>) -> Result<Colour> {
match frame.components.len() {
1 => Ok(Colour::Grayscale),
3 => {
let labelled_rgb = frame.components.iter().map(|c| c.id).eq(*b"RGB");
Ok(if adobe == Some(AdobeTransform::None) || labelled_rgb {
Colour::Rgb
} else {
Colour::YCbCr
})
}
count => Err(PixelsError::unsupported(format!(
"jpeg: {count}-component images (CMYK/YCCK)"
))),
}
}
fn check_baseline_scan(scan: &Scan) -> Result<()> {
if scan.spectral_start != 0 || scan.spectral_end != 63 {
return Err(PixelsError::malformed(
"jpeg",
format!(
"baseline scan selects coefficients {}..={}; it must select all 64",
scan.spectral_start, scan.spectral_end
),
));
}
if scan.approx_high != 0 || scan.approx_low != 0 {
return Err(PixelsError::malformed(
"jpeg",
"baseline scan uses successive approximation, which is progressive only",
));
}
Ok(())
}
fn read_quantization_tables(payload: &[u8], quant: &mut [[u16; 64]; 4]) -> Result<()> {
let mut at = 0_usize;
while at < payload.len() {
let Some(&header) = payload.get(at) else {
break;
};
at += 1;
let (precision, slot) = (header >> 4, (header & 0x0F) as usize);
if slot > 3 {
return Err(PixelsError::malformed(
"jpeg",
format!("DQT names table {slot}; only 0..=3 exist"),
));
}
let wide = match precision {
0 => false,
1 => true,
other => {
return Err(PixelsError::malformed(
"jpeg",
format!("DQT declares precision {other}; only 0 and 1 exist"),
));
}
};
let Some(table) = quant.get_mut(slot) else {
break;
};
for entry in table.iter_mut() {
let value = if wide {
let (Some(&hi), Some(&lo)) = (payload.get(at), payload.get(at + 1)) else {
return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
};
at += 2;
u16::from_be_bytes([hi, lo])
} else {
let Some(&value) = payload.get(at) else {
return Err(PixelsError::malformed("jpeg", "DQT segment is truncated"));
};
at += 1;
u16::from(value)
};
*entry = value;
}
}
Ok(())
}
fn read_huffman_tables(
payload: &[u8],
dc_tables: &mut [Option<HuffmanTable>; 4],
ac_tables: &mut [Option<HuffmanTable>; 4],
) -> Result<()> {
let mut at = 0_usize;
while at < payload.len() {
let Some(&header) = payload.get(at) else {
break;
};
at += 1;
let (class, slot) = (header >> 4, (header & 0x0F) as usize);
if slot > 3 || class > 1 {
return Err(PixelsError::malformed(
"jpeg",
format!("DHT names class {class} table {slot}; classes are 0..=1, slots 0..=3"),
));
}
let Some(counts) = payload.get(at..at + 16) else {
return Err(PixelsError::malformed(
"jpeg",
"DHT segment ends inside its code-length counts",
));
};
let mut lengths = [0_u8; 16];
lengths.copy_from_slice(counts);
at += 16;
let total: usize = lengths.iter().map(|&c| c as usize).sum();
let Some(values) = payload.get(at..at + total) else {
return Err(PixelsError::malformed(
"jpeg",
"DHT segment ends inside its symbol list",
));
};
at += total;
let table = HuffmanTable::new(&lengths, values.to_vec())?;
let slots = if class == 0 {
&mut *dc_tables
} else {
&mut *ac_tables
};
if let Some(entry) = slots.get_mut(slot) {
*entry = Some(table);
}
}
Ok(())
}
impl<S: Source + std::fmt::Debug> Decoder for Engine<S> {
fn descriptor(&self) -> ImageDescriptor {
self.descriptor
}
fn capability(&self) -> DecodeCapability {
DecodeCapability::Sequential
}
fn reduced_descriptor(&self, target: (u32, u32)) -> Option<ImageDescriptor> {
if self.row > 0 || self.mcu_row > 0 {
return None;
}
let full = (u32::from(self.frame.width), u32::from(self.frame.height));
let scale = Scale::fitting(full, target);
if scale == self.scale {
return None;
}
ImageDescriptor::new(
scale.apply(full.0),
scale.apply(full.1),
self.descriptor.pixel,
)
.ok()
}
fn reduce_to(&mut self, descriptor: ImageDescriptor) -> Result<()> {
let full = (u32::from(self.frame.width), u32::from(self.frame.height));
let scale = Scale::ALL
.into_iter()
.find(|scale| {
scale.apply(full.0) == descriptor.width && scale.apply(full.1) == descriptor.height
})
.ok_or_else(|| {
PixelsError::invalid_argument(
"descriptor",
format!(
"{}x{} is not an M/8 scale of {}x{}",
descriptor.width, descriptor.height, full.0, full.1
),
)
})?;
self.apply_scale(scale)
}
fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
if self.row >= self.descriptor.height {
return Err(PixelsError::invalid_argument(
"out",
format!("all {} rows have already been read", self.descriptor.height),
));
}
let row_bytes = self.descriptor.row_bytes();
if out.len() != row_bytes {
return Err(PixelsError::invalid_argument(
"out",
format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
));
}
if self.band_row >= self.band_height {
self.fill_band()?;
}
let start = self.band_row as usize * row_bytes;
let row = self
.band
.get(start..)
.and_then(|rest| rest.get(..row_bytes))
.ok_or_else(|| PixelsError::malformed("jpeg", "MCU row band is short"))?;
out.copy_from_slice(row);
self.band_row += 1;
self.row += 1;
Ok(())
}
}
#[must_use]
pub fn probe(prefix: &[u8]) -> bool {
prefix.get(..3) == Some(&crate::format::SIGNATURE[..])
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JpegCodec;
impl Codec for JpegCodec {
fn format(&self) -> Format {
Format::Jpeg
}
fn magic_len(&self) -> usize {
3
}
fn probe(&self, prefix: &[u8]) -> bool {
probe(prefix)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn colour_conversion_matches_the_direct_formula_everywhere() {
fn direct([y, cb, cr]: [u8; 3]) -> [u8; 3] {
const HALF: i32 = 1 << 15;
let luma = i32::from(y) << 16;
let blue = i32::from(cb) - 128;
let red = i32::from(cr) - 128;
let r = (luma + 91_881 * red + HALF) >> 16;
let g = (luma - 22_554 * blue - 46_802 * red + HALF) >> 16;
let b = (luma + 116_130 * blue + HALF) >> 16;
[r, g, b].map(|v| v.clamp(0, 255) as u8)
}
for y in 0..=255 {
for cb in 0..=255 {
for cr in 0..=255 {
assert_eq!(ycbcr_to_rgb([y, cb, cr]), direct([y, cb, cr]));
}
}
}
}
}