use alloc::borrow::Cow;
use super::DeblockMode;
use super::config::ResolvedCrop;
use super::pipeline::StripProcessor;
use super::pool::PoolGuard;
use crate::color::{ycbcr_planes_i16_to_rgb_u8, ycbcr_to_rgb, ycbcr_to_rgb_f32};
use crate::deblock::BoundaryStrength;
use crate::entropy::{EntropyDecoder, EntropyDecoderState};
use crate::error::{Error, Result, ScanRead};
use crate::foundation::consts::{DCT_BLOCK_SIZE, MAX_HUFFMAN_TABLES};
use crate::huffman::HuffmanDecodeTable;
use crate::types::{ColorSpace, Dimensions, Subsampling};
use imgref::ImgRefMut;
#[derive(Debug, Clone)]
pub struct ScanlineInfo {
pub dimensions: Dimensions,
pub color_space: ColorSpace,
pub is_xyb: bool,
pub subsampling: Subsampling,
}
pub struct ScanlineReader<'a> {
data: Cow<'a, [u8]>,
width: u32,
height: u32,
num_components: u8,
buffered_rgb: Option<Vec<u8>>,
strip: StripProcessor,
current_row: usize, current_mcu_row: usize, row_in_mcu: usize, mcu_row_decoded: bool,
quant_tables: [Option<[u16; DCT_BLOCK_SIZE]>; 4],
quant_indices: [usize; 3],
dc_tables: [Option<HuffmanDecodeTable>; MAX_HUFFMAN_TABLES],
ac_tables: [Option<HuffmanDecodeTable>; MAX_HUFFMAN_TABLES],
table_mapping: [(usize, usize); 3],
scan_data_start: usize, decoder_state: Option<EntropyDecoderState>,
restart_interval: u16,
mcu_count: u32,
next_restart_num: u8,
coeffs_buf: [i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [u8; 4],
is_xyb: bool,
is_rgb: bool,
stored_coeffs: Option<super::DecodedCoefficients>,
next_mcu_preloaded: bool,
crop: Option<ResolvedCrop>,
crop_skip_done: bool,
#[cfg(feature = "parallel")]
wave_state: Option<super::fused_parallel::WaveParallelState>,
#[cfg(feature = "parallel")]
wave_buf: Vec<u8>,
#[cfg(feature = "parallel")]
wave_first_row: usize,
#[cfg(feature = "parallel")]
wave_row_count: usize,
#[cfg(feature = "parallel")]
wave_next_seg: usize,
#[cfg(feature = "parallel")]
wave_y: Vec<i16>,
#[cfg(feature = "parallel")]
wave_cb: Vec<i16>,
#[cfg(feature = "parallel")]
wave_cr: Vec<i16>,
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: usize,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: usize,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: usize,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: usize,
#[cfg(feature = "parallel")]
wave_planar_next_seg: usize,
pub(super) pool_guard: Option<PoolGuard<'a>>,
deblock_mode: DeblockMode,
deblock_strength: Option<[BoundaryStrength; 3]>,
deblock_prev_rows: Vec<i16>,
deblock_has_prev: bool,
}
impl<'a> ScanlineReader<'a> {
pub(super) fn from_scan_data(
scan: super::parser::ParsedScanData<'a>,
chroma_upsampling: super::ChromaUpsampling,
idct_method: super::IdctMethod,
output_target: super::OutputTarget,
deblock_mode: DeblockMode,
) -> Result<Self> {
let data_cow = Cow::Borrowed(scan.data);
Self::from_scan_data_cow(
scan,
data_cow,
chroma_upsampling,
idct_method,
output_target,
deblock_mode,
)
}
pub(super) fn from_scan_data_cow(
scan: super::parser::ParsedScanData<'_>,
data_cow: Cow<'a, [u8]>,
chroma_upsampling: super::ChromaUpsampling,
idct_method: super::IdctMethod,
output_target: super::OutputTarget,
deblock_mode: DeblockMode,
) -> Result<Self> {
let super::parser::ParsedScanData {
data: _,
width,
height,
num_components,
h_samp,
v_samp,
quant_tables,
quant_indices,
dc_tables,
ac_tables,
table_mapping,
scan_data_start,
restart_interval,
is_xyb,
is_rgb,
} = scan;
let strip = StripProcessor::new(
width,
num_components,
h_samp,
v_samp,
chroma_upsampling,
idct_method,
output_target,
)?;
let effective_deblock = match deblock_mode {
DeblockMode::Auto | DeblockMode::AutoStreamable => DeblockMode::Boundary4Tap,
other => other,
};
let active = effective_deblock == DeblockMode::Boundary4Tap;
let deblock_strength = if active {
let mut strengths = [BoundaryStrength::from_dc_quant(1); 3];
for i in 0..num_components.min(3) as usize {
if let Some(ref qt) = quant_tables[quant_indices[i]] {
strengths[i] = BoundaryStrength::from_dc_quant(qt[0]);
}
}
Some(strengths)
} else {
None
};
let deblock_prev_rows = if active {
let ys = strip.strip_stride;
let cs = strip.chroma_strip_stride;
let total = if num_components == 1 {
2 * ys
} else {
2 * ys + 2 * cs + 2 * cs
};
alloc::vec![0i16; total]
} else {
Vec::new()
};
Ok(Self {
data: data_cow,
width,
height,
num_components,
buffered_rgb: None,
strip,
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
quant_tables,
quant_indices,
dc_tables,
ac_tables,
table_mapping,
scan_data_start,
decoder_state: None,
restart_interval,
mcu_count: 0,
next_restart_num: 0,
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4],
is_xyb,
is_rgb,
stored_coeffs: None,
next_mcu_preloaded: false,
crop: None,
crop_skip_done: false,
#[cfg(feature = "parallel")]
wave_state: None,
#[cfg(feature = "parallel")]
wave_buf: Vec::new(),
#[cfg(feature = "parallel")]
wave_first_row: 0,
#[cfg(feature = "parallel")]
wave_row_count: 0,
#[cfg(feature = "parallel")]
wave_next_seg: 0,
#[cfg(feature = "parallel")]
wave_y: Vec::new(),
#[cfg(feature = "parallel")]
wave_cb: Vec::new(),
#[cfg(feature = "parallel")]
wave_cr: Vec::new(),
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_next_seg: 0,
pool_guard: None,
deblock_mode: effective_deblock,
deblock_strength,
deblock_prev_rows,
deblock_has_prev: false,
})
}
pub(crate) fn new_buffered(
data: &'a [u8],
width: u32,
height: u32,
num_components: u8,
subsampling: Subsampling,
pixels: Vec<u8>,
is_xyb: bool,
) -> Self {
Self {
data: Cow::Borrowed(data),
width,
height,
num_components,
buffered_rgb: Some(pixels),
strip: StripProcessor::new_dummy(subsampling),
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
quant_tables: [None, None, None, None],
quant_indices: [0, 0, 0],
dc_tables: [None, None, None, None],
ac_tables: [None, None, None, None],
table_mapping: [(0, 0), (0, 0), (0, 0)],
scan_data_start: 0,
decoder_state: None,
restart_interval: 0,
mcu_count: 0,
next_restart_num: 0,
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4],
is_xyb,
is_rgb: false,
stored_coeffs: None,
next_mcu_preloaded: false,
crop: None,
crop_skip_done: false,
#[cfg(feature = "parallel")]
wave_state: None,
#[cfg(feature = "parallel")]
wave_buf: Vec::new(),
#[cfg(feature = "parallel")]
wave_first_row: 0,
#[cfg(feature = "parallel")]
wave_row_count: 0,
#[cfg(feature = "parallel")]
wave_next_seg: 0,
#[cfg(feature = "parallel")]
wave_y: Vec::new(),
#[cfg(feature = "parallel")]
wave_cb: Vec::new(),
#[cfg(feature = "parallel")]
wave_cr: Vec::new(),
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_next_seg: 0,
pool_guard: None,
deblock_mode: DeblockMode::Off,
deblock_strength: None,
deblock_prev_rows: Vec::new(),
deblock_has_prev: false,
}
}
pub(crate) fn new_buffered_cow(
data: Cow<'a, [u8]>,
width: u32,
height: u32,
num_components: u8,
subsampling: Subsampling,
pixels: Vec<u8>,
is_xyb: bool,
) -> Self {
Self {
data,
width,
height,
num_components,
buffered_rgb: Some(pixels),
strip: StripProcessor::new_dummy(subsampling),
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
quant_tables: [None, None, None, None],
quant_indices: [0, 0, 0],
dc_tables: [None, None, None, None],
ac_tables: [None, None, None, None],
table_mapping: [(0, 0), (0, 0), (0, 0)],
scan_data_start: 0,
decoder_state: None,
restart_interval: 0,
mcu_count: 0,
next_restart_num: 0,
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4],
is_xyb,
is_rgb: false,
stored_coeffs: None,
next_mcu_preloaded: false,
crop: None,
crop_skip_done: false,
#[cfg(feature = "parallel")]
wave_state: None,
#[cfg(feature = "parallel")]
wave_buf: Vec::new(),
#[cfg(feature = "parallel")]
wave_first_row: 0,
#[cfg(feature = "parallel")]
wave_row_count: 0,
#[cfg(feature = "parallel")]
wave_next_seg: 0,
#[cfg(feature = "parallel")]
wave_y: Vec::new(),
#[cfg(feature = "parallel")]
wave_cb: Vec::new(),
#[cfg(feature = "parallel")]
wave_cr: Vec::new(),
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_next_seg: 0,
pool_guard: None,
deblock_mode: DeblockMode::Off,
deblock_strength: None,
deblock_prev_rows: Vec::new(),
deblock_has_prev: false,
}
}
pub(crate) fn from_coefficients(
coefficients: super::DecodedCoefficients,
chroma_upsampling: super::ChromaUpsampling,
idct_method: super::IdctMethod,
output_target: super::OutputTarget,
) -> Result<Self> {
let width = coefficients.width;
let height = coefficients.height;
let num_components = coefficients.components.len() as u8;
let h_samp = if num_components == 1 {
[coefficients.components[0].h_samp, 1, 1]
} else {
[
coefficients.components[0].h_samp,
coefficients.components[1].h_samp,
coefficients.components[2].h_samp,
]
};
let v_samp = if num_components == 1 {
[coefficients.components[0].v_samp, 1, 1]
} else {
[
coefficients.components[0].v_samp,
coefficients.components[1].v_samp,
coefficients.components[2].v_samp,
]
};
let quant_indices = if num_components == 1 {
[coefficients.components[0].quant_table_idx as usize, 0, 0]
} else {
[
coefficients.components[0].quant_table_idx as usize,
coefficients.components[1].quant_table_idx as usize,
coefficients.components[2].quant_table_idx as usize,
]
};
let mut quant_tables = [None; 4];
for (i, qt) in coefficients.quant_tables.iter().enumerate() {
if i < 4 {
quant_tables[i] = *qt;
}
}
let strip = StripProcessor::new(
width,
num_components,
h_samp,
v_samp,
chroma_upsampling,
idct_method,
output_target,
)?;
Ok(Self {
data: Cow::Borrowed(&[]), width,
height,
num_components,
buffered_rgb: None,
strip,
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
quant_tables,
quant_indices,
dc_tables: [None, None, None, None],
ac_tables: [None, None, None, None],
table_mapping: [(0, 0), (0, 0), (0, 0)],
scan_data_start: 0,
decoder_state: None,
restart_interval: 0,
mcu_count: 0,
next_restart_num: 0,
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4],
is_xyb: false,
is_rgb: false,
stored_coeffs: Some(coefficients),
next_mcu_preloaded: false,
crop: None,
crop_skip_done: false,
#[cfg(feature = "parallel")]
wave_state: None,
#[cfg(feature = "parallel")]
wave_buf: Vec::new(),
#[cfg(feature = "parallel")]
wave_first_row: 0,
#[cfg(feature = "parallel")]
wave_row_count: 0,
#[cfg(feature = "parallel")]
wave_next_seg: 0,
#[cfg(feature = "parallel")]
wave_y: Vec::new(),
#[cfg(feature = "parallel")]
wave_cb: Vec::new(),
#[cfg(feature = "parallel")]
wave_cr: Vec::new(),
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_next_seg: 0,
pool_guard: None,
deblock_mode: DeblockMode::Off,
deblock_strength: None,
deblock_prev_rows: Vec::new(),
deblock_has_prev: false,
})
}
pub(crate) fn set_crop(&mut self, crop: ResolvedCrop) {
self.crop = Some(crop);
}
pub(crate) fn replace_data(&mut self, data: Cow<'a, [u8]>) {
self.data = data;
}
#[cfg(feature = "parallel")]
pub(super) fn new_wave_parallel(
data: &'a [u8],
wave_state: super::fused_parallel::WaveParallelState,
) -> Self {
let width = wave_state.width as u32;
let height = wave_state.height as u32;
let rgb_row_bytes = wave_state.width * 3;
let seg_rgb_bytes = wave_state.pixel_rows_per_seg * rgb_row_bytes;
let wave_buf_size = wave_state.wave_size * seg_rgb_bytes;
let wave_buf = vec![0u8; wave_buf_size];
Self {
data: Cow::Borrowed(data),
width,
height,
num_components: 3,
buffered_rgb: None,
strip: StripProcessor::new_dummy(Subsampling::S420),
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
quant_tables: [None, None, None, None],
quant_indices: [0, 0, 0],
dc_tables: [None, None, None, None],
ac_tables: [None, None, None, None],
table_mapping: [(0, 0), (0, 0), (0, 0)],
scan_data_start: 0,
decoder_state: None,
restart_interval: 0,
mcu_count: 0,
next_restart_num: 0,
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4],
is_xyb: false,
is_rgb: false,
stored_coeffs: None,
next_mcu_preloaded: false,
crop: None,
crop_skip_done: false,
wave_state: Some(wave_state),
wave_buf,
wave_first_row: 0,
wave_row_count: 0,
wave_next_seg: 0,
#[cfg(feature = "parallel")]
wave_y: Vec::new(),
#[cfg(feature = "parallel")]
wave_cb: Vec::new(),
#[cfg(feature = "parallel")]
wave_cr: Vec::new(),
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_next_seg: 0,
pool_guard: None,
deblock_mode: DeblockMode::Off,
deblock_strength: None,
deblock_prev_rows: Vec::new(),
deblock_has_prev: false,
}
}
#[cfg(feature = "parallel")]
pub(super) fn new_wave_parallel_cow(
data: Cow<'a, [u8]>,
wave_state: super::fused_parallel::WaveParallelState,
) -> Self {
let width = wave_state.width as u32;
let height = wave_state.height as u32;
let rgb_row_bytes = wave_state.width * 3;
let seg_rgb_bytes = wave_state.pixel_rows_per_seg * rgb_row_bytes;
let wave_buf_size = wave_state.wave_size * seg_rgb_bytes;
let wave_buf = vec![0u8; wave_buf_size];
Self {
data,
width,
height,
num_components: 3,
buffered_rgb: None,
strip: StripProcessor::new_dummy(Subsampling::S420),
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
quant_tables: [None, None, None, None],
quant_indices: [0, 0, 0],
dc_tables: [None, None, None, None],
ac_tables: [None, None, None, None],
table_mapping: [(0, 0), (0, 0), (0, 0)],
scan_data_start: 0,
decoder_state: None,
restart_interval: 0,
mcu_count: 0,
next_restart_num: 0,
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4],
is_xyb: false,
is_rgb: false,
stored_coeffs: None,
next_mcu_preloaded: false,
crop: None,
crop_skip_done: false,
wave_state: Some(wave_state),
wave_buf,
wave_first_row: 0,
wave_row_count: 0,
wave_next_seg: 0,
#[cfg(feature = "parallel")]
wave_y: Vec::new(),
#[cfg(feature = "parallel")]
wave_cb: Vec::new(),
#[cfg(feature = "parallel")]
wave_cr: Vec::new(),
#[cfg(feature = "parallel")]
wave_planar_first_luma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_luma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_first_chroma_row: 0,
#[cfg(feature = "parallel")]
wave_planar_chroma_rows: 0,
#[cfg(feature = "parallel")]
wave_planar_next_seg: 0,
pool_guard: None,
deblock_mode: DeblockMode::Off,
deblock_strength: None,
deblock_prev_rows: Vec::new(),
deblock_has_prev: false,
}
}
#[cfg(feature = "parallel")]
pub(super) fn attach_wave_state(
&mut self,
wave_state: super::fused_parallel::WaveParallelState,
) {
self.wave_state = Some(wave_state);
}
#[inline]
pub fn width(&self) -> u32 {
self.crop.map_or(self.width, |c| c.width)
}
#[inline]
pub fn height(&self) -> u32 {
self.crop.map_or(self.height, |c| c.height)
}
pub fn info(&self) -> ScanlineInfo {
ScanlineInfo {
dimensions: Dimensions {
width: self.width(),
height: self.height(),
},
color_space: if self.num_components == 1 {
ColorSpace::Grayscale
} else {
ColorSpace::YCbCr
},
is_xyb: self.is_xyb,
subsampling: self.strip.subsampling,
}
}
#[inline]
pub fn subsampling(&self) -> Subsampling {
self.strip.subsampling
}
#[inline]
pub fn current_row(&self) -> usize {
self.current_row
}
#[inline]
pub fn is_finished(&self) -> bool {
self.current_row >= self.height() as usize
}
#[inline]
pub fn is_grayscale(&self) -> bool {
self.num_components == 1
}
#[inline]
pub fn num_components(&self) -> u8 {
self.num_components
}
#[inline]
pub fn chroma_width(&self) -> u32 {
if self.num_components == 1 {
return 0;
}
#[cfg(feature = "parallel")]
if let Some(ref ws) = self.wave_state {
return ws.chroma_width() as u32;
}
self.strip.chroma_strip_width as u32
}
#[inline]
pub fn chroma_height(&self) -> u32 {
if self.num_components == 1 {
return 0;
}
#[cfg(feature = "parallel")]
if let Some(ref ws) = self.wave_state {
let v_scale = ws.max_v_samp;
let c_v = ws.comp_v_samps.get(1).copied().unwrap_or(1).max(1);
return if c_v < v_scale {
((self.height as usize + v_scale - 1) / v_scale) as u32
} else {
self.height
};
}
let max_v = self.strip.v_samp[0]
.max(self.strip.v_samp[1])
.max(self.strip.v_samp[2]);
let c_v = self.strip.v_samp[1];
if c_v < max_v {
(self.height + 1) / 2
} else {
self.height
}
}
#[inline]
pub fn luma_rows_per_mcu(&self) -> usize {
#[cfg(feature = "parallel")]
if let Some(ref ws) = self.wave_state {
return ws.mcu_pixel_height;
}
self.strip.mcu_height
}
#[inline]
pub fn chroma_rows_per_mcu(&self) -> usize {
if self.num_components == 1 {
return 0;
}
#[cfg(feature = "parallel")]
if let Some(ref ws) = self.wave_state {
let c_v = ws.comp_v_samps.get(1).copied().unwrap_or(1).max(1);
return c_v * 8;
}
self.strip.chroma_strip_height
}
#[cfg(feature = "ultrahdr")]
pub fn gain_map_jpeg(&self) -> Option<&[u8]> {
let mut parser = match super::parser::JpegParser::new(&self.data, u64::MAX, None) {
Ok(p) => p,
Err(_) => return None,
};
if parser.read_header().is_err() {
return None;
}
let (range, _metadata) = parser.extract_gainmap_early(&self.data).ok()?;
let (start, end) = range?;
Some(&self.data[start..end])
}
fn skip_to_crop_start(&mut self) -> Result<()> {
let crop = match self.crop {
Some(c) => c,
None => return Ok(()),
};
if self.crop_skip_done || self.current_mcu_row >= crop.mcu_row_start {
self.crop_skip_done = true;
return Ok(());
}
let needs_prev_context = self.strip.needs_vertical_upsample() && crop.mcu_row_start > 0;
if self.buffered_rgb.is_some() {
self.crop_skip_done = true;
return Ok(());
}
if self.stored_coeffs.is_some() {
if needs_prev_context {
self.current_mcu_row = crop.mcu_row_start - 1;
self.mcu_row_decoded = false;
self.decode_mcu_row()?;
}
self.current_mcu_row = crop.mcu_row_start;
self.mcu_row_decoded = false;
self.crop_skip_done = true;
return Ok(());
}
let skip_target = if needs_prev_context {
crop.mcu_row_start - 1
} else {
crop.mcu_row_start
};
let scan_data = &self.data[self.scan_data_start..];
let mut decoder = EntropyDecoder::new(scan_data);
for comp_idx in 0..self.num_components as usize {
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
if let Some(ref table) = self.dc_tables[dc_idx] {
decoder.set_dc_table(dc_idx, table);
}
if let Some(ref table) = self.ac_tables[ac_idx] {
decoder.set_ac_table(ac_idx, table);
}
}
if let Some(ref state) = self.decoder_state {
decoder.restore_state(*state);
}
let mcu_cols = self.strip.mcu_cols();
while self.current_mcu_row < skip_target {
for _mcu_x in 0..mcu_cols {
if self.restart_interval > 0
&& self.mcu_count > 0
&& self.mcu_count % self.restart_interval as u32 == 0
{
decoder.align_to_byte();
decoder.read_restart_marker(self.next_restart_num)?;
self.next_restart_num = (self.next_restart_num + 1) & 7;
decoder.reset_dc();
self.prev_coeff_counts = [64; 4];
}
for comp_idx in 0..self.num_components as usize {
let h_blocks = self.strip.h_samp[comp_idx] as usize;
let v_blocks = self.strip.v_samp[comp_idx] as usize;
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
for _v in 0..v_blocks {
for _h in 0..h_blocks {
match decoder.decode_block_into(
&mut self.coeffs_buf,
self.prev_coeff_counts[comp_idx],
comp_idx,
dc_idx,
ac_idx,
)? {
ScanRead::Value(c) => {
self.prev_coeff_counts[comp_idx] =
self.prev_coeff_counts[comp_idx].max(c);
}
ScanRead::EndOfScan | ScanRead::Truncated => {
self.prev_coeff_counts[comp_idx] = 64;
}
}
}
}
}
self.mcu_count += 1;
}
self.current_mcu_row += 1;
}
self.decoder_state = Some(decoder.save_state());
self.mcu_row_decoded = false;
if needs_prev_context {
self.decode_mcu_row()?;
self.current_mcu_row += 1;
self.mcu_row_decoded = false;
}
self.crop_skip_done = true;
Ok(())
}
#[inline]
fn mcu_row_in_crop(&self, mcu_row: usize) -> bool {
match self.crop {
Some(c) => {
let effective_start = c.mcu_row_start.saturating_sub(1);
mcu_row >= effective_start && mcu_row < c.mcu_row_end
}
None => true,
}
}
fn resolve_quant_tables(&self) -> Result<[[u16; 64]; 4]> {
let quant_y = self.quant_tables[self.quant_indices[0]]
.ok_or_else(|| Error::internal("missing Y quantization table"))?;
let quant_cb = if self.num_components > 1 {
self.quant_tables[self.quant_indices[1]]
.ok_or_else(|| Error::internal("missing Cb quantization table"))?
} else {
quant_y
};
let quant_cr = if self.num_components > 2 {
self.quant_tables[self.quant_indices[2]]
.ok_or_else(|| Error::internal("missing Cr quantization table"))?
} else {
quant_y
};
Ok([quant_y, quant_cb, quant_cr, quant_y])
}
fn decode_mcu_row(&mut self) -> Result<()> {
if self.mcu_row_decoded {
return Ok(());
}
if self.stored_coeffs.is_some() {
return self.decode_mcu_row_from_coefficients();
}
let scan_data = &self.data[self.scan_data_start..];
let mut decoder = EntropyDecoder::new(scan_data);
for comp_idx in 0..self.num_components as usize {
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
if let Some(ref table) = self.dc_tables[dc_idx] {
decoder.set_dc_table(dc_idx, table);
}
if let Some(ref table) = self.ac_tables[ac_idx] {
decoder.set_ac_table(ac_idx, table);
}
}
if let Some(ref state) = self.decoder_state {
decoder.restore_state(*state);
}
let quant_refs = self.resolve_quant_tables()?;
let mcu_cols = self.strip.mcu_cols();
for mcu_x in 0..mcu_cols {
if self.restart_interval > 0
&& self.mcu_count > 0
&& self.mcu_count % self.restart_interval as u32 == 0
{
decoder.align_to_byte();
decoder.read_restart_marker(self.next_restart_num)?;
self.next_restart_num = (self.next_restart_num + 1) & 7;
decoder.reset_dc();
self.prev_coeff_counts = [64; 4];
}
let do_idct = self.mcu_row_in_crop(self.current_mcu_row);
for comp_idx in 0..self.num_components as usize {
let h_blocks = self.strip.h_samp[comp_idx] as usize;
let v_blocks = self.strip.v_samp[comp_idx] as usize;
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
let quant = &quant_refs[comp_idx];
for v in 0..v_blocks {
for h in 0..h_blocks {
let coeff_count = match decoder.decode_block_into(
&mut self.coeffs_buf,
self.prev_coeff_counts[comp_idx],
comp_idx,
dc_idx,
ac_idx,
)? {
ScanRead::Value(c) => c,
ScanRead::EndOfScan | ScanRead::Truncated => {
self.prev_coeff_counts[comp_idx] = 64;
continue;
}
};
self.prev_coeff_counts[comp_idx] =
self.prev_coeff_counts[comp_idx].max(coeff_count);
if do_idct {
self.strip.idct_block(
comp_idx,
mcu_x,
h,
v,
&self.coeffs_buf,
coeff_count,
quant,
);
}
}
}
}
self.mcu_count += 1;
}
self.decoder_state = Some(decoder.save_state());
if self.mcu_row_in_crop(self.current_mcu_row) {
self.strip.truncate_chroma_padding(
self.width as usize,
self.height as usize,
self.current_mcu_row,
);
if self.deblock_mode == DeblockMode::Boundary4Tap {
self.apply_boundary_deblock();
}
self.strip.upsample_chroma();
}
self.mcu_row_decoded = true;
Ok(())
}
fn apply_boundary_deblock(&mut self) {
let strengths = match self.deblock_strength {
Some(s) => s,
None => return,
};
let mcu_row = self.current_mcu_row;
let is_grayscale = self.num_components == 1;
{
let w = self.strip.strip_width;
let stride = self.strip.strip_stride;
let h = self.strip.mcu_height;
let strength = strengths[0];
filter_strip_vertical_i16(&mut self.strip.y_strip, w, stride, h, &strength);
filter_strip_horizontal_i16(&mut self.strip.y_strip, w, stride, h, &strength);
if self.deblock_has_prev && mcu_row > 0 {
filter_inter_mcu_horizontal_i16(
&self.deblock_prev_rows,
0, stride,
&mut self.strip.y_strip,
stride,
w,
&strength,
);
}
if h >= 2 {
let src_row_m2 = (h - 2) * stride;
let src_row_m1 = (h - 1) * stride;
self.deblock_prev_rows[..stride]
.copy_from_slice(&self.strip.y_strip[src_row_m2..src_row_m2 + stride]);
self.deblock_prev_rows[stride..2 * stride]
.copy_from_slice(&self.strip.y_strip[src_row_m1..src_row_m1 + stride]);
}
}
if !is_grayscale {
let cw = self.strip.chroma_strip_width;
let cs = self.strip.chroma_strip_stride;
let ch = self.strip.chroma_strip_height;
let ys = self.strip.strip_stride;
for (comp_idx, plane) in [&mut self.strip.cb_strip, &mut self.strip.cr_strip]
.into_iter()
.enumerate()
{
let strength = strengths[comp_idx + 1];
filter_strip_vertical_i16(plane, cw, cs, ch, &strength);
filter_strip_horizontal_i16(plane, cw, cs, ch, &strength);
if self.deblock_has_prev && mcu_row > 0 {
let prev_offset = 2 * ys + comp_idx * 2 * cs;
filter_inter_mcu_horizontal_i16(
&self.deblock_prev_rows,
prev_offset,
cs,
plane,
cs,
cw,
&strength,
);
}
if ch >= 2 {
let src_row_m2 = (ch - 2) * cs;
let src_row_m1 = (ch - 1) * cs;
let dst_offset = 2 * ys + comp_idx * 2 * cs;
self.deblock_prev_rows[dst_offset..dst_offset + cs]
.copy_from_slice(&plane[src_row_m2..src_row_m2 + cs]);
self.deblock_prev_rows[dst_offset + cs..dst_offset + 2 * cs]
.copy_from_slice(&plane[src_row_m1..src_row_m1 + cs]);
}
}
}
self.deblock_has_prev = true;
}
fn decode_mcu_row_from_coefficients(&mut self) -> Result<()> {
if !self.mcu_row_in_crop(self.current_mcu_row) {
self.mcu_row_decoded = true;
return Ok(());
}
let quant_refs = self.resolve_quant_tables()?;
let mcu_cols = self.strip.mcu_cols();
let coeffs = self.stored_coeffs.as_ref().unwrap();
for mcu_x in 0..mcu_cols {
for comp_idx in 0..self.num_components as usize {
let h_blocks = self.strip.h_samp[comp_idx] as usize;
let v_blocks = self.strip.v_samp[comp_idx] as usize;
let quant = &quant_refs[comp_idx];
let blocks_wide = coeffs.components[comp_idx].blocks_wide;
for v in 0..v_blocks {
for h in 0..h_blocks {
let by = self.current_mcu_row * v_blocks + v;
let bx = mcu_x * h_blocks + h;
let block_idx = by * blocks_wide + bx;
let block = coeffs.components[comp_idx].block(block_idx);
self.coeffs_buf.copy_from_slice(block);
let coeff_count = if block.iter().all(|&c| c == 0) {
0
} else {
block
.iter()
.rposition(|&c| c != 0)
.map(|p| (p + 1) as u8)
.unwrap_or(0)
};
self.strip.idct_block(
comp_idx,
mcu_x,
h,
v,
&self.coeffs_buf,
coeff_count,
quant,
);
}
}
}
}
if self.strip.needs_vertical_upsample() && !self.is_last_mcu_row() {
self.peek_next_chroma_row(&quant_refs);
}
self.strip.truncate_chroma_padding(
self.width as usize,
self.height as usize,
self.current_mcu_row,
);
self.strip.upsample_chroma();
self.mcu_row_decoded = true;
Ok(())
}
fn peek_next_chroma_row(&mut self, quant_refs: &[[u16; 64]; 4]) {
let mcu_cols = self.strip.mcu_cols();
let coeffs = self.stored_coeffs.as_ref().unwrap();
let next_mcu_row = self.current_mcu_row + 1;
for comp_idx in 1..self.num_components as usize {
let v_blocks = self.strip.v_samp[comp_idx] as usize;
let h_blocks = self.strip.h_samp[comp_idx] as usize;
let blocks_wide = coeffs.components[comp_idx].blocks_wide;
let by = next_mcu_row * v_blocks; let quant = &quant_refs[comp_idx];
let next_row = if comp_idx == 1 {
&mut self.strip.next_cb_row
} else {
&mut self.strip.next_cr_row
};
for mcu_x in 0..mcu_cols {
for h in 0..h_blocks {
let bx = mcu_x * h_blocks + h;
let block_idx = by * blocks_wide + bx;
let block = coeffs.components[comp_idx].block(block_idx);
let coeff_count = if block.iter().all(|&c| c == 0) {
0
} else {
block
.iter()
.rposition(|&c| c != 0)
.map(|p| (p + 1) as u8)
.unwrap_or(0)
};
let x_offset = mcu_x * h_blocks * 8 + h * 8;
if coeff_count <= 1 {
let dc = block[0] as i32 * quant[0] as i32;
let val = ((dc + 1024) >> 11).clamp(0, 255) as i16;
for px in 0..8 {
if x_offset + px < next_row.len() {
next_row[x_offset + px] = val;
}
}
} else {
let mut temp_coeffs = [0i16; 64];
temp_coeffs.copy_from_slice(block);
let mut dequant_buf = [0i32; 64];
crate::quant::dequantize_unzigzag_i32_into_partial(
&temp_coeffs,
quant,
&mut dequant_buf,
coeff_count,
);
let mut temp_pixels = [0i16; 64];
match self.strip.idct_method {
super::IdctMethod::Libjpeg => {
super::idct_int::idct_int_tiered_libjpeg(
&mut dequant_buf,
&mut temp_pixels,
8,
coeff_count,
);
}
super::IdctMethod::Jpegli => {
super::idct_int::idct_int_tiered(
&mut dequant_buf,
&mut temp_pixels,
8,
coeff_count,
);
}
}
for px in 0..8 {
if x_offset + px < next_row.len() {
next_row[x_offset + px] = temp_pixels[px];
}
}
}
}
}
}
self.strip.has_next_context = true;
}
fn is_last_mcu_row(&self) -> bool {
let mcu_height = self.strip.mcu_height;
let total_mcu_rows = (self.height as usize + mcu_height - 1) / mcu_height;
self.current_mcu_row + 1 >= total_mcu_rows
}
fn ensure_crop_initialized(&mut self) -> Result<()> {
if self.crop.is_some() && !self.crop_skip_done {
self.skip_to_crop_start()?;
let crop = self.crop.unwrap();
self.row_in_mcu = crop.y as usize - crop.mcu_row_start * self.strip.mcu_height;
}
Ok(())
}
fn ensure_row_ready(&mut self) -> Result<()> {
self.decode_mcu_row()?;
if self.row_in_mcu == self.strip.mcu_height - 1
&& self.strip.needs_vertical_upsample()
&& !self.strip.has_deferred_bottom
&& !self.is_last_mcu_row()
&& self.stored_coeffs.is_none()
{
self.prepare_streaming_bottom()?;
}
Ok(())
}
fn prepare_streaming_bottom(&mut self) -> Result<()> {
self.strip.save_deferred_y_row();
self.current_mcu_row += 1;
self.mcu_row_decoded = false;
self.decode_mcu_row_streaming()?;
self.strip.compute_deferred_bottom();
self.strip.truncate_chroma_padding(
self.width as usize,
self.height as usize,
self.current_mcu_row,
);
self.strip.upsample_chroma();
self.current_mcu_row -= 1; self.next_mcu_preloaded = true;
self.mcu_row_decoded = true;
Ok(())
}
fn decode_mcu_row_streaming(&mut self) -> Result<()> {
let scan_data = &self.data[self.scan_data_start..];
let mut decoder = EntropyDecoder::new(scan_data);
for comp_idx in 0..self.num_components as usize {
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
if let Some(ref table) = self.dc_tables[dc_idx] {
decoder.set_dc_table(dc_idx, table);
}
if let Some(ref table) = self.ac_tables[ac_idx] {
decoder.set_ac_table(ac_idx, table);
}
}
if let Some(ref state) = self.decoder_state {
decoder.restore_state(*state);
}
let quant_refs = self.resolve_quant_tables()?;
let mcu_cols = self.strip.mcu_cols();
for mcu_x in 0..mcu_cols {
if self.restart_interval > 0
&& self.mcu_count > 0
&& self.mcu_count % self.restart_interval as u32 == 0
{
decoder.align_to_byte();
decoder.read_restart_marker(self.next_restart_num)?;
self.next_restart_num = (self.next_restart_num + 1) & 7;
decoder.reset_dc();
self.prev_coeff_counts = [64; 4];
}
for comp_idx in 0..self.num_components as usize {
let h_blocks = self.strip.h_samp[comp_idx] as usize;
let v_blocks = self.strip.v_samp[comp_idx] as usize;
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
let quant = &quant_refs[comp_idx];
for v in 0..v_blocks {
for h in 0..h_blocks {
let coeff_count = match decoder.decode_block_into(
&mut self.coeffs_buf,
self.prev_coeff_counts[comp_idx],
comp_idx,
dc_idx,
ac_idx,
)? {
ScanRead::Value(c) => c,
ScanRead::EndOfScan | ScanRead::Truncated => {
self.prev_coeff_counts[comp_idx] = 64;
continue;
}
};
self.prev_coeff_counts[comp_idx] =
self.prev_coeff_counts[comp_idx].max(coeff_count);
self.strip.idct_block(
comp_idx,
mcu_x,
h,
v,
&self.coeffs_buf,
coeff_count,
quant,
);
}
}
}
self.mcu_count += 1;
}
self.decoder_state = Some(decoder.save_state());
Ok(())
}
fn advance_mcu_row(&mut self) {
self.current_mcu_row += 1;
self.row_in_mcu = 0;
self.strip.has_deferred_bottom = false;
if self.next_mcu_preloaded {
self.mcu_row_decoded = true;
self.next_mcu_preloaded = false;
} else {
self.mcu_row_decoded = false;
}
}
pub fn read_rows_rgb8(&mut self, mut output: ImgRefMut<'_, u8>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width * 3 {
return Err(Error::internal("output buffer too narrow for RGB8"));
}
#[cfg(feature = "parallel")]
if self.wave_state.is_some() && !self.wave_buf.is_empty() {
return self.read_rows_rgb8_wave(output);
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let bpp = if self.num_components == 1 { 1 } else { 3 };
let src_row_bytes = img_width * bpp;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let out_row = output.rows_mut().nth(rows_written).unwrap();
if bpp == 3 {
let src_start = image_row * src_row_bytes + crop_x * 3;
out_row[..out_width * 3]
.copy_from_slice(&buffer[src_start..src_start + out_width * 3]);
} else {
let src_start = image_row * src_row_bytes + crop_x;
for px in 0..out_width {
let v = buffer[src_start + px];
out_row[px * 3] = v;
out_row[px * 3 + 1] = v;
out_row[px * 3 + 2] = v;
}
}
rows_written += 1;
self.current_row += 1;
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let is_grayscale = self.num_components == 1;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let strip_cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
if is_grayscale {
let y = self.strip.y_row(self.row_in_mcu, strip_cols);
for px in 0..out_width {
let v = y[crop_x + px].clamp(0, 255) as u8;
out_row[px * 3] = v;
out_row[px * 3 + 1] = v;
out_row[px * 3 + 2] = v;
}
} else if self.is_rgb {
let (y, cb, cr) = self.strip.row_planes(self.row_in_mcu, strip_cols);
for px in 0..out_width {
out_row[px * 3] = y[crop_x + px].clamp(0, 255) as u8;
out_row[px * 3 + 1] = cb[crop_x + px].clamp(0, 255) as u8;
out_row[px * 3 + 2] = cr[crop_x + px].clamp(0, 255) as u8;
}
} else {
let (y, cb, cr) = self.strip.row_planes(self.row_in_mcu, strip_cols);
ycbcr_planes_i16_to_rgb_u8(
&y[crop_x..crop_x + out_width],
&cb[crop_x..crop_x + out_width],
&cr[crop_x..crop_x + out_width],
out_row,
);
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
#[cfg(feature = "parallel")]
fn read_rows_rgb8_wave(&mut self, mut output: ImgRefMut<'_, u8>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let out_height = self.height() as usize;
let rgb_row_bytes = self.width as usize * 3;
let mut rows_written = 0;
while rows_written < max_rows && self.current_row < out_height {
if self.current_row >= self.wave_first_row + self.wave_row_count {
self.decode_next_wave()?;
}
let row_in_wave = self.current_row - self.wave_first_row;
let src_start = row_in_wave * rgb_row_bytes;
let src_end = src_start + out_width * 3;
let out_row = output.rows_mut().nth(rows_written).unwrap();
out_row[..out_width * 3].copy_from_slice(&self.wave_buf[src_start..src_end]);
rows_written += 1;
self.current_row += 1;
}
Ok(rows_written)
}
#[cfg(feature = "parallel")]
fn decode_next_wave(&mut self) -> Result<()> {
let state = self.wave_state.as_ref().unwrap();
let seg_start = self.wave_next_seg;
let seg_end = (seg_start + state.wave_size).min(state.num_segments);
if seg_start >= seg_end {
return Ok(());
}
let wave_count = seg_end - seg_start;
let _ = wave_count;
let row_count =
state.decode_wave_box(&self.data, seg_start, seg_end, &mut self.wave_buf)?;
self.wave_first_row = seg_start * state.pixel_rows_per_seg;
self.wave_row_count = row_count;
self.wave_next_seg = seg_end;
Ok(())
}
#[cfg(feature = "parallel")]
fn read_rows_planar_i16_wave(
&mut self,
y: &mut [i16],
y_stride: usize,
cb: &mut [i16],
cr: &mut [i16],
c_stride: usize,
max_mcu_rows: usize,
) -> Result<(usize, usize)> {
let state = self.wave_state.as_ref().unwrap();
let luma_width = state.width;
let chroma_width = state.chroma_width();
let luma_rows_per_mcu = state.mcu_pixel_height;
let chroma_rows_per_mcu = if chroma_width > 0 {
state.chroma_rows_per_seg() / (state.pixel_rows_per_seg / luma_rows_per_mcu).max(1)
} else {
0
};
let luma_rows_per_seg = state.pixel_rows_per_seg;
let chroma_rows_per_seg = state.chroma_rows_per_seg();
let out_height = state.height;
let chroma_height = if chroma_width > 0 {
(state.height + state.max_v_samp - 1) / state.max_v_samp
} else {
0
};
let total_mcu_rows = (out_height + luma_rows_per_mcu - 1) / luma_rows_per_mcu;
if self.wave_y.is_empty() {
let ws = state.wave_size;
let y_seg_samples = luma_rows_per_seg * luma_width;
let c_seg_samples = chroma_rows_per_seg * chroma_width;
self.wave_y = vec![0i16; ws * y_seg_samples];
if chroma_width > 0 {
self.wave_cb = vec![0i16; ws * c_seg_samples];
self.wave_cr = vec![0i16; ws * c_seg_samples];
}
}
let mut total_luma_rows = 0usize;
let mut total_chroma_rows = 0usize;
for _ in 0..max_mcu_rows {
if self.current_mcu_row >= total_mcu_rows {
break;
}
let luma_row_abs = self.current_mcu_row * luma_rows_per_mcu;
let chroma_row_abs = self.current_mcu_row * chroma_rows_per_mcu;
if luma_row_abs >= self.wave_planar_first_luma_row + self.wave_planar_luma_rows {
self.decode_next_wave_planar()?;
}
let remaining_luma = out_height.saturating_sub(luma_row_abs);
let luma_rows_this = luma_rows_per_mcu.min(remaining_luma);
let remaining_chroma = chroma_height.saturating_sub(chroma_row_abs);
let chroma_rows_this = if chroma_width > 0 {
chroma_rows_per_mcu.min(remaining_chroma)
} else {
0
};
let y_wave_row = luma_row_abs - self.wave_planar_first_luma_row;
for row in 0..luma_rows_this {
let src_off = (y_wave_row + row) * luma_width;
let dst_off = (total_luma_rows + row) * y_stride;
y[dst_off..dst_off + luma_width]
.copy_from_slice(&self.wave_y[src_off..src_off + luma_width]);
}
if chroma_width > 0 && chroma_rows_this > 0 {
let c_wave_row = chroma_row_abs - self.wave_planar_first_chroma_row;
for row in 0..chroma_rows_this {
let src_off = (c_wave_row + row) * chroma_width;
let dst_off = (total_chroma_rows + row) * c_stride;
cb[dst_off..dst_off + chroma_width]
.copy_from_slice(&self.wave_cb[src_off..src_off + chroma_width]);
cr[dst_off..dst_off + chroma_width]
.copy_from_slice(&self.wave_cr[src_off..src_off + chroma_width]);
}
}
total_luma_rows += luma_rows_this;
total_chroma_rows += chroma_rows_this;
self.current_row += luma_rows_this;
self.current_mcu_row += 1;
}
Ok((total_luma_rows, total_chroma_rows))
}
#[cfg(feature = "parallel")]
fn decode_next_wave_planar(&mut self) -> Result<()> {
let state = self.wave_state.as_ref().unwrap();
let seg_start = self.wave_planar_next_seg;
let seg_end = (seg_start + state.wave_size).min(state.num_segments);
if seg_start >= seg_end {
return Ok(());
}
let luma_rows_per_seg = state.pixel_rows_per_seg;
let chroma_rows_per_seg = state.chroma_rows_per_seg();
let (luma_rows, chroma_rows) = state.decode_wave_planar(
&self.data,
seg_start,
seg_end,
&mut self.wave_y,
&mut self.wave_cb,
&mut self.wave_cr,
)?;
self.wave_planar_first_luma_row = seg_start * luma_rows_per_seg;
self.wave_planar_luma_rows = luma_rows;
self.wave_planar_first_chroma_row = seg_start * chroma_rows_per_seg;
self.wave_planar_chroma_rows = chroma_rows;
self.wave_planar_next_seg = seg_end;
Ok(())
}
pub fn read_rows_rgbx8(&mut self, output: ImgRefMut<'_, u8>) -> Result<usize> {
self.read_rows_xrgb_4bpp(output, false)
}
pub fn read_rows_bgr8(&mut self, mut output: ImgRefMut<'_, u8>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width * 3 {
return Err(Error::internal("output buffer too narrow for BGR8"));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let bpp = if self.num_components == 1 { 1 } else { 3 };
let src_row_bytes = img_width * bpp;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let out_row = output.rows_mut().nth(rows_written).unwrap();
if bpp == 3 {
let src_offset = image_row * src_row_bytes + crop_x * 3;
for x in 0..out_width {
out_row[x * 3] = buffer[src_offset + x * 3 + 2]; out_row[x * 3 + 1] = buffer[src_offset + x * 3 + 1]; out_row[x * 3 + 2] = buffer[src_offset + x * 3]; }
} else {
let src_offset = image_row * src_row_bytes + crop_x;
for x in 0..out_width {
let v = buffer[src_offset + x];
out_row[x * 3] = v;
out_row[x * 3 + 1] = v;
out_row[x * 3 + 2] = v;
}
}
rows_written += 1;
self.current_row += 1;
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let is_grayscale = self.num_components == 1;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let strip_cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
if is_grayscale {
let y = self.strip.y_row(self.row_in_mcu, strip_cols);
for px in 0..out_width {
let v = y[crop_x + px].clamp(0, 255) as u8;
out_row[px * 3] = v;
out_row[px * 3 + 1] = v;
out_row[px * 3 + 2] = v;
}
} else if self.is_rgb {
let (y, cb, cr) = self.strip.row_planes(self.row_in_mcu, strip_cols);
for px in 0..out_width {
out_row[px * 3] = cr[crop_x + px].clamp(0, 255) as u8; out_row[px * 3 + 1] = cb[crop_x + px].clamp(0, 255) as u8; out_row[px * 3 + 2] = y[crop_x + px].clamp(0, 255) as u8; }
} else {
let (y, cb, cr) = self.strip.row_planes(self.row_in_mcu, strip_cols);
for px in 0..out_width {
let (r, g, b) = ycbcr_to_rgb(
y[crop_x + px].clamp(0, 255) as u8,
cb[crop_x + px].clamp(0, 255) as u8,
cr[crop_x + px].clamp(0, 255) as u8,
);
out_row[px * 3] = b;
out_row[px * 3 + 1] = g;
out_row[px * 3 + 2] = r;
}
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
#[inline]
pub fn read_rows_rgba8(&mut self, output: ImgRefMut<'_, u8>) -> Result<usize> {
self.read_rows_rgbx8(output)
}
pub fn read_rows_bgra8(&mut self, output: ImgRefMut<'_, u8>) -> Result<usize> {
self.read_rows_xrgb_4bpp(output, true)
}
#[inline]
pub fn read_rows_bgrx8(&mut self, output: ImgRefMut<'_, u8>) -> Result<usize> {
self.read_rows_bgra8(output)
}
fn read_rows_xrgb_4bpp(
&mut self,
mut output: ImgRefMut<'_, u8>,
swap_rb: bool,
) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width * 4 {
return Err(Error::internal("output buffer too narrow for 4bpp"));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let bpp = if self.num_components == 1 { 1 } else { 3 };
let src_row_bytes = img_width * bpp;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let out_row = output.rows_mut().nth(rows_written).unwrap();
if bpp == 1 {
let src_offset = image_row * src_row_bytes + crop_x;
for x in 0..out_width {
let v = buffer[src_offset + x];
out_row[x * 4] = v;
out_row[x * 4 + 1] = v;
out_row[x * 4 + 2] = v;
out_row[x * 4 + 3] = 255;
}
} else if swap_rb {
let src_offset = image_row * src_row_bytes + crop_x * 3;
for x in 0..out_width {
out_row[x * 4] = buffer[src_offset + x * 3 + 2]; out_row[x * 4 + 1] = buffer[src_offset + x * 3 + 1]; out_row[x * 4 + 2] = buffer[src_offset + x * 3]; out_row[x * 4 + 3] = 255;
}
} else {
let src_offset = image_row * src_row_bytes + crop_x * 3;
for x in 0..out_width {
out_row[x * 4] = buffer[src_offset + x * 3];
out_row[x * 4 + 1] = buffer[src_offset + x * 3 + 1];
out_row[x * 4 + 2] = buffer[src_offset + x * 3 + 2];
out_row[x * 4 + 3] = 255;
}
}
rows_written += 1;
self.current_row += 1;
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let is_grayscale = self.num_components == 1;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let strip_cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
if is_grayscale {
let y_row = self.strip.y_row(self.row_in_mcu, strip_cols);
for x in 0..out_width {
let v = y_row[crop_x + x].clamp(0, 255) as u8;
out_row[x * 4] = v;
out_row[x * 4 + 1] = v;
out_row[x * 4 + 2] = v;
out_row[x * 4 + 3] = 255;
}
} else {
let (y_row, cb_row, cr_row) = self.strip.row_planes(self.row_in_mcu, strip_cols);
for x in 0..out_width {
let sx = crop_x + x;
let (r, g, b) = if self.is_rgb {
(
y_row[sx].clamp(0, 255) as u8,
cb_row[sx].clamp(0, 255) as u8,
cr_row[sx].clamp(0, 255) as u8,
)
} else {
ycbcr_to_rgb(
y_row[sx].clamp(0, 255) as u8,
cb_row[sx].clamp(0, 255) as u8,
cr_row[sx].clamp(0, 255) as u8,
)
};
if swap_rb {
out_row[x * 4] = b;
out_row[x * 4 + 1] = g;
out_row[x * 4 + 2] = r;
} else {
out_row[x * 4] = r;
out_row[x * 4 + 1] = g;
out_row[x * 4 + 2] = b;
}
out_row[x * 4 + 3] = 255;
}
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
pub fn read_rows_rgba_f32(&mut self, mut output: ImgRefMut<'_, f32>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width * 4 {
return Err(Error::internal("output buffer too narrow for RGBA f32"));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let bpp = if self.num_components == 1 { 1 } else { 3 };
let src_row_bytes = img_width * bpp;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let out_row = output.rows_mut().nth(rows_written).unwrap();
if bpp == 3 {
let src_offset = image_row * src_row_bytes + crop_x * 3;
for x in 0..out_width {
out_row[x * 4] = srgb_to_linear(buffer[src_offset + x * 3]);
out_row[x * 4 + 1] = srgb_to_linear(buffer[src_offset + x * 3 + 1]);
out_row[x * 4 + 2] = srgb_to_linear(buffer[src_offset + x * 3 + 2]);
out_row[x * 4 + 3] = 1.0;
}
} else {
let src_offset = image_row * src_row_bytes + crop_x;
for x in 0..out_width {
let v = srgb_to_linear(buffer[src_offset + x]);
out_row[x * 4] = v;
out_row[x * 4 + 1] = v;
out_row[x * 4 + 2] = v;
out_row[x * 4 + 3] = 1.0;
}
}
rows_written += 1;
self.current_row += 1;
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let is_grayscale = self.num_components == 1;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let strip_cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
if is_grayscale {
let y_row = self.strip.y_row(self.row_in_mcu, strip_cols);
for x in 0..out_width {
let v = y_row[crop_x + x].clamp(0, 255) as f32 / 255.0;
let linear = srgb_to_linear_f32(v);
out_row[x * 4] = linear;
out_row[x * 4 + 1] = linear;
out_row[x * 4 + 2] = linear;
out_row[x * 4 + 3] = 1.0;
}
} else {
let (y_row, cb_row, cr_row) = self.strip.row_planes(self.row_in_mcu, strip_cols);
for x in 0..out_width {
let sx = crop_x + x;
let (r, g, b) = if self.is_rgb {
(
y_row[sx] as f32 / 255.0,
cb_row[sx] as f32 / 255.0,
cr_row[sx] as f32 / 255.0,
)
} else {
let (rf, gf, bf) = ycbcr_to_rgb_f32(
y_row[sx] as f32,
cb_row[sx] as f32,
cr_row[sx] as f32,
);
(rf / 255.0, gf / 255.0, bf / 255.0)
};
out_row[x * 4] = srgb_to_linear_f32(r);
out_row[x * 4 + 1] = srgb_to_linear_f32(g);
out_row[x * 4 + 2] = srgb_to_linear_f32(b);
out_row[x * 4 + 3] = 1.0;
}
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
pub fn read_rows_ycbcr_f32(
&mut self,
y_plane: &mut [f32],
cb_plane: &mut [f32],
cr_plane: &mut [f32],
stride: usize,
max_rows: usize,
) -> Result<usize> {
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if stride < out_width {
return Err(Error::internal("stride too small for image width"));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
if self.num_components == 1 {
let max_image_row = crop_y + out_height;
if max_image_row * img_width > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_offset = image_row * img_width + crop_x;
let out_offset = rows_written * stride;
for x in 0..out_width {
y_plane[out_offset + x] = buffer[src_offset + x] as f32 / 255.0;
cb_plane[out_offset + x] = 0.0;
cr_plane[out_offset + x] = 0.0;
}
rows_written += 1;
self.current_row += 1;
}
} else {
let src_row_bytes = img_width * 3;
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_start = image_row * src_row_bytes + crop_x * 3;
let out_offset = rows_written * stride;
for x in 0..out_width {
let r = buffer[src_start + x * 3] as f32;
let g = buffer[src_start + x * 3 + 1] as f32;
let b = buffer[src_start + x * 3 + 2] as f32;
y_plane[out_offset + x] = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;
cb_plane[out_offset + x] = (-0.169 * r - 0.331 * g + 0.500 * b) / 255.0;
cr_plane[out_offset + x] = (0.500 * r - 0.419 * g - 0.081 * b) / 255.0;
}
rows_written += 1;
self.current_row += 1;
}
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let is_grayscale = self.num_components == 1;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let cols = full_width.min(self.strip.strip_width);
let out_offset = rows_written * stride;
if is_grayscale {
let y_slice = self.strip.y_row(self.row_in_mcu, cols);
for x in 0..out_width {
y_plane[out_offset + x] = y_slice[crop_x + x] as f32 / 255.0;
cb_plane[out_offset + x] = 0.0;
cr_plane[out_offset + x] = 0.0;
}
} else {
let (y_slice, cb_slice, cr_slice) = self.strip.row_planes(self.row_in_mcu, cols);
for x in 0..out_width {
y_plane[out_offset + x] = y_slice[crop_x + x] as f32 / 255.0;
cb_plane[out_offset + x] = (cb_slice[crop_x + x] as f32 - 128.0) / 255.0;
cr_plane[out_offset + x] = (cr_slice[crop_x + x] as f32 - 128.0) / 255.0;
}
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
pub fn read_rows_ycbcr_native_i16(
&mut self,
y: &mut [i16],
y_stride: usize,
cb: &mut [i16],
cr: &mut [i16],
c_stride: usize,
max_mcu_rows: usize,
) -> Result<(usize, usize)> {
if self.crop.is_some() {
return Err(Error::internal(
"read_rows_ycbcr_native_i16 does not support crop",
));
}
#[cfg(feature = "parallel")]
if self.wave_state.is_some() {
return self.read_rows_planar_i16_wave(y, y_stride, cb, cr, c_stride, max_mcu_rows);
}
self.read_rows_planar_i16_seq(y, y_stride, cb, cr, c_stride, max_mcu_rows)
}
#[deprecated(since = "0.5.0", note = "renamed to read_rows_ycbcr_f32")]
pub fn read_rows_ycbcr_planes(
&mut self,
y_plane: &mut [f32],
cb_plane: &mut [f32],
cr_plane: &mut [f32],
stride: usize,
max_rows: usize,
) -> Result<usize> {
self.read_rows_ycbcr_f32(y_plane, cb_plane, cr_plane, stride, max_rows)
}
#[deprecated(since = "0.5.0", note = "renamed to read_rows_ycbcr_native_i16")]
pub fn read_rows_planar_i16(
&mut self,
y: &mut [i16],
y_stride: usize,
cb: &mut [i16],
cr: &mut [i16],
c_stride: usize,
max_mcu_rows: usize,
) -> Result<(usize, usize)> {
self.read_rows_ycbcr_native_i16(y, y_stride, cb, cr, c_stride, max_mcu_rows)
}
fn read_rows_planar_i16_seq(
&mut self,
y: &mut [i16],
y_stride: usize,
cb: &mut [i16],
cr: &mut [i16],
c_stride: usize,
max_mcu_rows: usize,
) -> Result<(usize, usize)> {
let luma_width = self.width as usize;
let chroma_width = self.strip.chroma_strip_width;
let luma_rows_per_mcu = self.strip.mcu_height;
let chroma_rows_per_mcu = self.strip.chroma_strip_height;
let out_height = self.height as usize;
let is_grayscale = self.num_components == 1;
let total_mcu_rows = (out_height + luma_rows_per_mcu - 1) / luma_rows_per_mcu;
let mut total_luma_rows = 0usize;
let mut total_chroma_rows = 0usize;
for _ in 0..max_mcu_rows {
if self.current_mcu_row >= total_mcu_rows {
break;
}
self.decode_mcu_row()?;
let remaining_luma =
out_height.saturating_sub(self.current_mcu_row * luma_rows_per_mcu);
let luma_rows_this = luma_rows_per_mcu.min(remaining_luma);
let chroma_height_total = if is_grayscale {
0
} else {
self.chroma_height() as usize
};
let remaining_chroma =
chroma_height_total.saturating_sub(self.current_mcu_row * chroma_rows_per_mcu);
let chroma_rows_this = chroma_rows_per_mcu.min(remaining_chroma);
let strip_cols_y = luma_width.min(self.strip.strip_width);
let strip_cols_c = chroma_width.min(self.strip.chroma_strip_stride);
for row in 0..luma_rows_this {
let src = self.strip.y_row(row, strip_cols_y);
let dst_off = (total_luma_rows + row) * y_stride;
y[dst_off..dst_off + luma_width.min(strip_cols_y)]
.copy_from_slice(&src[..luma_width.min(strip_cols_y)]);
}
if !is_grayscale {
for row in 0..chroma_rows_this {
let (cb_src, cr_src) = self.strip.chroma_row_native(row, strip_cols_c);
let dst_off = (total_chroma_rows + row) * c_stride;
let copy_width = chroma_width.min(strip_cols_c);
cb[dst_off..dst_off + copy_width].copy_from_slice(&cb_src[..copy_width]);
cr[dst_off..dst_off + copy_width].copy_from_slice(&cr_src[..copy_width]);
}
}
total_luma_rows += luma_rows_this;
total_chroma_rows += chroma_rows_this;
self.current_row += luma_rows_this;
self.advance_mcu_row();
}
Ok((total_luma_rows, total_chroma_rows))
}
pub fn read_rows_gray8(&mut self, mut output: ImgRefMut<'_, u8>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width {
return Err(Error::internal("output buffer too narrow for grayscale"));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
if self.num_components == 1 {
let max_image_row = crop_y + out_height;
if max_image_row * img_width > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_offset = image_row * img_width + crop_x;
let out_row = output.rows_mut().nth(rows_written).unwrap();
out_row[..out_width]
.copy_from_slice(&buffer[src_offset..src_offset + out_width]);
rows_written += 1;
self.current_row += 1;
}
} else {
let src_row_bytes = img_width * 3;
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_start = image_row * src_row_bytes + crop_x * 3;
let out_row = output.rows_mut().nth(rows_written).unwrap();
for x in 0..out_width {
let r = buffer[src_start + x * 3] as u32;
let g = buffer[src_start + x * 3 + 1] as u32;
let b = buffer[src_start + x * 3 + 2] as u32;
out_row[x] = ((299 * r + 587 * g + 114 * b) / 1000) as u8;
}
rows_written += 1;
self.current_row += 1;
}
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
let y_slice = self.strip.y_row(self.row_in_mcu, cols);
for x in 0..out_width {
out_row[x] = y_slice[crop_x + x].clamp(0, 255) as u8;
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
pub fn read_rows_gray_f32(&mut self, mut output: ImgRefMut<'_, f32>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width {
return Err(Error::internal(
"output buffer too narrow for grayscale f32",
));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
if self.num_components == 1 {
let max_image_row = crop_y + out_height;
if max_image_row * img_width > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_offset = image_row * img_width + crop_x;
let out_row = output.rows_mut().nth(rows_written).unwrap();
for x in 0..out_width {
out_row[x] = buffer[src_offset + x] as f32 / 255.0;
}
rows_written += 1;
self.current_row += 1;
}
} else {
let src_row_bytes = img_width * 3;
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_start = image_row * src_row_bytes + crop_x * 3;
let out_row = output.rows_mut().nth(rows_written).unwrap();
for x in 0..out_width {
let r = buffer[src_start + x * 3] as f32;
let g = buffer[src_start + x * 3 + 1] as f32;
let b = buffer[src_start + x * 3 + 2] as f32;
out_row[x] = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;
}
rows_written += 1;
self.current_row += 1;
}
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
let y_slice = self.strip.y_row(self.row_in_mcu, cols);
for x in 0..out_width {
out_row[x] = y_slice[crop_x + x] as f32 / 255.0;
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
pub fn read_rows_gray_linear_f32(&mut self, mut output: ImgRefMut<'_, f32>) -> Result<usize> {
let max_rows = output.height();
let out_width = self.width() as usize;
let crop_x = self.crop.map_or(0, |c| c.x as usize);
let out_height = self.height() as usize;
if output.width() < out_width {
return Err(Error::internal(
"output buffer too narrow for linear grayscale f32",
));
}
if let Some(ref buffer) = self.buffered_rgb {
let mut rows_written = 0;
let img_width = self.width as usize;
let crop_y = self.crop.map_or(0, |c| c.y as usize);
if self.num_components == 1 {
let max_image_row = crop_y + out_height;
if max_image_row * img_width > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_offset = image_row * img_width + crop_x;
let out_row = output.rows_mut().nth(rows_written).unwrap();
for x in 0..out_width {
out_row[x] = srgb_to_linear(buffer[src_offset + x]);
}
rows_written += 1;
self.current_row += 1;
}
} else {
let src_row_bytes = img_width * 3;
let max_image_row = crop_y + out_height;
if max_image_row * src_row_bytes > buffer.len() {
return Err(Error::internal(
"buffered RGB data too small for image dimensions",
));
}
while rows_written < max_rows && self.current_row < out_height {
let image_row = crop_y + self.current_row;
let src_start = image_row * src_row_bytes + crop_x * 3;
let out_row = output.rows_mut().nth(rows_written).unwrap();
for x in 0..out_width {
let r = srgb_to_linear(buffer[src_start + x * 3]);
let g = srgb_to_linear(buffer[src_start + x * 3 + 1]);
let b = srgb_to_linear(buffer[src_start + x * 3 + 2]);
out_row[x] = 0.299 * r + 0.587 * g + 0.114 * b;
}
rows_written += 1;
self.current_row += 1;
}
}
return Ok(rows_written);
}
self.ensure_crop_initialized()?;
let mut rows_written = 0;
let full_width = self.width as usize;
while rows_written < max_rows && self.current_row < out_height {
self.ensure_row_ready()?;
let cols = full_width.min(self.strip.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
let y_slice = self.strip.y_row(self.row_in_mcu, cols);
for x in 0..out_width {
out_row[x] = srgb_to_linear_f32(y_slice[crop_x + x] as f32 / 255.0);
}
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.strip.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
}
#[inline]
fn srgb_to_linear(srgb: u8) -> f32 {
srgb_to_linear_f32(srgb as f32 / 255.0)
}
#[inline]
fn srgb_to_linear_f32(s: f32) -> f32 {
if s <= 0.04045 {
s / 12.92
} else {
((s + 0.055) / 1.055).powf(2.4)
}
}
fn filter_strip_vertical_i16(
plane: &mut [i16],
width: usize,
stride: usize,
height: usize,
strength: &BoundaryStrength,
) {
if strength.max_delta < 0.5 || width < 16 || height < 2 {
return;
}
let thresh = strength.threshold;
let max_d = strength.max_delta;
let num_boundaries = width / 8;
for bx in 1..num_boundaries {
let col = bx * 8;
if col + 1 >= width || col < 2 {
continue;
}
for y in 0..height {
let base = y * stride;
let p1 = plane[base + col - 2] as f32;
let p0 = plane[base + col - 1] as f32;
let q0 = plane[base + col] as f32;
let q1 = plane[base + col + 1] as f32;
let disc = (p0 - q0).abs();
if disc < thresh {
continue;
}
let avg = (p1 + 3.0 * p0 + 3.0 * q0 + q1) * 0.125;
let delta_p = (avg - p0).clamp(-max_d, max_d);
let delta_q = (avg - q0).clamp(-max_d, max_d);
plane[base + col - 1] = (p0 + delta_p).round() as i16;
plane[base + col] = (q0 + delta_q).round() as i16;
}
}
}
fn filter_strip_horizontal_i16(
plane: &mut [i16],
width: usize,
stride: usize,
height: usize,
strength: &BoundaryStrength,
) {
if strength.max_delta < 0.5 || width < 2 || height < 16 {
return;
}
let thresh = strength.threshold;
let max_d = strength.max_delta;
let num_boundaries = height / 8;
for by in 1..num_boundaries {
let row = by * 8;
if row + 1 >= height || row < 2 {
continue;
}
let off_p1 = (row - 2) * stride;
let off_p0 = (row - 1) * stride;
let off_q0 = row * stride;
let off_q1 = (row + 1) * stride;
for x in 0..width {
let p1 = plane[off_p1 + x] as f32;
let p0 = plane[off_p0 + x] as f32;
let q0 = plane[off_q0 + x] as f32;
let q1 = plane[off_q1 + x] as f32;
let disc = (p0 - q0).abs();
if disc < thresh {
continue;
}
let avg = (p1 + 3.0 * p0 + 3.0 * q0 + q1) * 0.125;
let delta_p = (avg - p0).clamp(-max_d, max_d);
let delta_q = (avg - q0).clamp(-max_d, max_d);
plane[off_p0 + x] = (p0 + delta_p).round() as i16;
plane[off_q0 + x] = (q0 + delta_q).round() as i16;
}
}
}
fn filter_inter_mcu_horizontal_i16(
prev_rows: &[i16],
prev_offset: usize,
prev_stride: usize,
curr_plane: &mut [i16],
curr_stride: usize,
width: usize,
strength: &BoundaryStrength,
) {
if strength.max_delta < 0.5 || width < 2 {
return;
}
if curr_plane.len() < 2 * curr_stride {
return;
}
let thresh = strength.threshold;
let max_d = strength.max_delta;
for x in 0..width {
let p1 = prev_rows[prev_offset + x] as f32;
let p0 = prev_rows[prev_offset + prev_stride + x] as f32;
let q0 = curr_plane[x] as f32;
let q1 = curr_plane[curr_stride + x] as f32;
let disc = (p0 - q0).abs();
if disc < thresh {
continue;
}
let avg = (p1 + 3.0 * p0 + 3.0 * q0 + q1) * 0.125;
let delta_q = (avg - q0).clamp(-max_d, max_d);
curr_plane[x] = (q0 + delta_q).round() as i16;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_strip_vertical_i16() {
let stride = 32;
let width = 32;
let height = 8;
let mut plane = vec![0i16; stride * height];
for y in 0..height {
for x in 0..width {
plane[y * stride + x] = if x < 8 { 50 } else { 200 };
}
}
let strength = BoundaryStrength::from_dc_quant(20);
filter_strip_vertical_i16(&mut plane, width, stride, height, &strength);
let p0 = plane[2 * stride + 7]; let q0 = plane[2 * stride + 8]; assert!(p0 > 50, "p0 should increase from 50: got {p0}");
assert!(q0 < 200, "q0 should decrease from 200: got {q0}");
}
#[test]
fn test_filter_strip_horizontal_i16() {
let stride = 16;
let width = 16;
let height = 16;
let mut plane = vec![0i16; stride * height];
for y in 0..height {
for x in 0..width {
plane[y * stride + x] = if y < 8 { 50 } else { 200 };
}
}
let strength = BoundaryStrength::from_dc_quant(20);
filter_strip_horizontal_i16(&mut plane, width, stride, height, &strength);
let p0 = plane[7 * stride + 5]; let q0 = plane[8 * stride + 5]; assert!(p0 > 50, "p0 should increase from 50: got {p0}");
assert!(q0 < 200, "q0 should decrease from 200: got {q0}");
}
fn encode_rgb(width: u32, height: u32, pixels: &[u8], quality: f32) -> Vec<u8> {
use crate::encode::v2::{ChromaSubsampling, EncoderConfig, PixelLayout};
use enough::Unstoppable;
let config = EncoderConfig::ycbcr(quality, ChromaSubsampling::None);
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
.unwrap();
enc.push_packed(pixels, Unstoppable).unwrap();
enc.finish().unwrap()
}
fn encode_rgb_subsampled(
width: u32,
height: u32,
pixels: &[u8],
quality: f32,
subsampling: crate::encode::v2::ChromaSubsampling,
) -> Vec<u8> {
use crate::encode::v2::{EncoderConfig, PixelLayout};
use enough::Unstoppable;
let config = EncoderConfig::ycbcr(quality, subsampling).progressive(false);
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
.unwrap();
enc.push_packed(pixels, Unstoppable).unwrap();
enc.finish().unwrap()
}
fn compare_u8_slices(a: &[u8], b: &[u8]) -> (u8, usize, Option<usize>) {
assert_eq!(a.len(), b.len(), "slice length mismatch");
let mut max_diff: u8 = 0;
let mut diff_count: usize = 0;
let mut first_diff_idx: Option<usize> = None;
for (i, (&va, &vb)) in a.iter().zip(b.iter()).enumerate() {
let diff = (va as i16 - vb as i16).unsigned_abs() as u8;
if diff > 0 {
diff_count += 1;
if first_diff_idx.is_none() {
first_diff_idx = Some(i);
}
if diff > max_diff {
max_diff = diff;
}
}
}
(max_diff, diff_count, first_diff_idx)
}
#[allow(dead_code)]
fn compare_f32_slices(a: &[f32], b: &[f32]) -> (f32, usize, Option<usize>) {
assert_eq!(a.len(), b.len(), "slice length mismatch");
let mut max_diff: f32 = 0.0;
let mut diff_count: usize = 0;
let mut first_diff_idx: Option<usize> = None;
for (i, (&va, &vb)) in a.iter().zip(b.iter()).enumerate() {
let diff = (va - vb).abs();
if diff > 1e-6 {
diff_count += 1;
if first_diff_idx.is_none() {
first_diff_idx = Some(i);
}
if diff > max_diff {
max_diff = diff;
}
}
}
(max_diff, diff_count, first_diff_idx)
}
fn assert_slices_equal_u8(actual: &[u8], expected: &[u8], context: &str) {
let (max_diff, diff_count, first_diff_idx) = compare_u8_slices(actual, expected);
if diff_count > 0 {
let first_idx = first_diff_idx.unwrap();
panic!(
"{}: slices differ - max_diff={}, diff_count={}/{} ({:.2}%), first_diff at idx {} (actual={}, expected={})",
context,
max_diff,
diff_count,
actual.len(),
100.0 * diff_count as f64 / actual.len() as f64,
first_idx,
actual[first_idx],
expected[first_idx]
);
}
}
#[allow(dead_code)]
fn assert_slices_equal_f32(actual: &[f32], expected: &[f32], context: &str) {
let (max_diff, diff_count, first_diff_idx) = compare_f32_slices(actual, expected);
if diff_count > 0 {
let first_idx = first_diff_idx.unwrap();
panic!(
"{}: slices differ - max_diff={:.6}, diff_count={}/{} ({:.2}%), first_diff at idx {} (actual={:.6}, expected={:.6})",
context,
max_diff,
diff_count,
actual.len(),
100.0 * diff_count as f64 / actual.len() as f64,
first_idx,
actual[first_idx],
expected[first_idx]
);
}
}
#[test]
fn test_srgb_to_linear() {
assert!((srgb_to_linear(0) - 0.0).abs() < 1e-6);
assert!((srgb_to_linear(255) - 1.0).abs() < 1e-6);
assert!((srgb_to_linear(128) - 0.2159).abs() < 0.01);
}
#[test]
fn test_scanline_reader_rgb8() {
use crate::decode::Decoder;
let width = 64u32;
let height = 48u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 4) as u8; pixels[idx + 1] = (y * 5) as u8; pixels[idx + 2] = 128; }
}
let jpeg = encode_rgb(width, height, &pixels, 95.0);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
assert_eq!(reader.width(), width);
assert_eq!(reader.height(), height);
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let stride = (width * 3) as usize;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader
.read_rows_rgb8(output)
.expect("read_rows_rgb8 failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
assert_eq!(
scanline_pixels.len(),
decoded.pixels_u8().unwrap().len(),
"output size mismatch"
);
assert_slices_equal_u8(
&scanline_pixels,
decoded.pixels_u8().unwrap(),
"test_scanline_reader_rgb8",
);
}
#[test]
fn test_scanline_reader_partial_reads() {
use crate::decode::Decoder;
let width = 32u32;
let height = 32u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = ((x + y) * 4) as u8;
pixels[idx + 1] = ((x * 2 + y) % 256) as u8;
pixels[idx + 2] = ((y * 2 + x) % 256) as u8;
}
}
let jpeg = encode_rgb(width, height, &pixels, 90.0);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let stride = (width * 3) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let chunk_size = 3; let rows_to_read = chunk_size.min(height as usize - total_rows);
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, rows_to_read);
let rows = reader.read_rows_rgb8(output).expect("read failed");
assert!(rows > 0 || reader.is_finished());
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
assert_slices_equal_u8(
&scanline_pixels,
decoded.pixels_u8().unwrap(),
"test_scanline_reader_partial_reads",
);
}
#[test]
fn test_scanline_reader_rgbx8() {
use crate::decode::Decoder;
let width = 24u32;
let height = 24u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for i in 0..pixels.len() {
pixels[i] = ((i * 7) % 256) as u8;
}
let jpeg = encode_rgb(width, height, &pixels, 85.0);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut rgbx_pixels = vec![0u8; (width * height * 4) as usize];
let stride = (width * 4) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output = imgref::ImgRefMut::new(&mut rgbx_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_rgbx8(output).expect("read failed");
total_rows += rows;
}
let mut max_diff: u8 = 0;
let mut diff_count: usize = 0;
let mut first_diff: Option<(usize, usize, &str, u8, u8)> = None;
for y in 0..height as usize {
for x in 0..width as usize {
let rgb_idx = (y * width as usize + x) * 3;
let rgbx_idx = (y * width as usize + x) * 4;
for (c, name) in [(0, "R"), (1, "G"), (2, "B")] {
let actual = rgbx_pixels[rgbx_idx + c];
let expected = decoded.pixels_u8().unwrap()[rgb_idx + c];
let diff = (actual as i16 - expected as i16).unsigned_abs() as u8;
if diff > 0 {
diff_count += 1;
if first_diff.is_none() {
first_diff = Some((x, y, name, actual, expected));
}
if diff > max_diff {
max_diff = diff;
}
}
}
assert_eq!(rgbx_pixels[rgbx_idx + 3], 255, "Alpha should be 255");
}
}
if diff_count > 0 {
let (x, y, ch, actual, expected) = first_diff.unwrap();
let total = (width * height * 3) as usize;
panic!(
"test_scanline_reader_rgbx8: max_diff={}, diff_count={}/{} ({:.2}%), first_diff at ({},{}) {}={} expected={}",
max_diff,
diff_count,
total,
100.0 * diff_count as f64 / total as f64,
x,
y,
ch,
actual,
expected
);
}
}
#[test]
fn test_scanline_reader_rgba_f32() {
use crate::decode::Decoder;
let width = 16u32;
let height = 16u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for i in 0..pixels.len() {
pixels[i] = ((i * 11) % 256) as u8;
}
let jpeg = encode_rgb(width, height, &pixels, 90.0);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut rgba_pixels = vec![0.0f32; (width * height * 4) as usize];
let stride = (width * 4) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output = imgref::ImgRefMut::new(&mut rgba_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_rgba_f32(output).expect("read failed");
total_rows += rows;
}
for (i, &val) in rgba_pixels.iter().enumerate() {
if i % 4 == 3 {
assert!(
(val - 1.0).abs() < 1e-6,
"Alpha at {} should be 1.0, got {}",
i,
val
);
} else {
assert!(
(0.0..=1.0).contains(&val),
"Value at {} should be in [0,1], got {}",
i,
val
);
}
}
let mut max_diff: f32 = 0.0;
let mut diff_count: usize = 0;
let mut first_diff: Option<(usize, usize, usize, f32, f32)> = None;
for y in 0..height as usize {
for x in 0..width as usize {
let rgb_idx = (y * width as usize + x) * 3;
let rgba_idx = (y * width as usize + x) * 4;
for c in 0..3 {
let expected_linear = srgb_to_linear(decoded.pixels_u8().unwrap()[rgb_idx + c]);
let actual_linear = rgba_pixels[rgba_idx + c];
let diff = (expected_linear - actual_linear).abs();
if diff > 0.01 {
diff_count += 1;
if first_diff.is_none() {
first_diff = Some((x, y, c, actual_linear, expected_linear));
}
if diff > max_diff {
max_diff = diff;
}
}
}
}
}
if diff_count > 0 {
let (x, y, c, actual, expected) = first_diff.unwrap();
let total = (width * height * 3) as usize;
panic!(
"test_scanline_reader_rgba_f32: max_diff={:.6}, diff_count={}/{} ({:.2}%), first_diff at ({},{}) ch{}={:.6} expected={:.6}",
max_diff,
diff_count,
total,
100.0 * diff_count as f64 / total as f64,
x,
y,
c,
actual,
expected
);
}
}
#[test]
fn test_scanline_reader_ycbcr_planes() {
use crate::decode::Decoder;
let width = 32u32;
let height = 24u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for i in 0..pixels.len() {
pixels[i] = ((i * 13) % 256) as u8;
}
let jpeg = encode_rgb(width, height, &pixels, 90.0);
let decoder = Decoder::new();
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let plane_size = (width * height) as usize;
let mut y_plane = vec![0.0f32; plane_size];
let mut cb_plane = vec![0.0f32; plane_size];
let mut cr_plane = vec![0.0f32; plane_size];
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let offset = total_rows * width as usize;
let rows = reader
.read_rows_ycbcr_f32(
&mut y_plane[offset..],
&mut cb_plane[offset..],
&mut cr_plane[offset..],
width as usize,
remaining,
)
.expect("read failed");
total_rows += rows;
}
for i in 0..plane_size {
assert!(
(0.0..=1.0).contains(&y_plane[i]),
"Y[{}] = {} out of range",
i,
y_plane[i]
);
assert!(
(-0.6..=0.6).contains(&cb_plane[i]),
"Cb[{}] = {} out of range",
i,
cb_plane[i]
);
assert!(
(-0.6..=0.6).contains(&cr_plane[i]),
"Cr[{}] = {} out of range",
i,
cr_plane[i]
);
}
}
#[test]
fn test_scanline_reader_non_mcu_aligned() {
use crate::decode::Decoder;
let width = 37u32;
let height = 29u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 7) as u8;
pixels[idx + 1] = (y * 9) as u8;
pixels[idx + 2] = ((x + y) * 3) as u8;
}
}
let jpeg = encode_rgb(width, height, &pixels, 90.0);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let stride = (width * 3) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_rgb8(output).expect("read failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
assert_slices_equal_u8(
&scanline_pixels,
decoded.pixels_u8().unwrap(),
"test_scanline_reader_non_mcu_aligned",
);
}
#[test]
fn test_scanline_reader_420() {
use crate::decode::Decoder;
use crate::encode::v2::ChromaSubsampling;
let width = 64u32;
let height = 48u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 4) as u8; pixels[idx + 1] = (y * 5) as u8; pixels[idx + 2] = 128; }
}
let jpeg = encode_rgb_subsampled(width, height, &pixels, 95.0, ChromaSubsampling::Quarter);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
assert_eq!(reader.width(), width);
assert_eq!(reader.height(), height);
assert_eq!(reader.subsampling(), Subsampling::S420);
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let stride = (width * 3) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader
.read_rows_rgb8(output)
.expect("read_rows_rgb8 failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
assert_eq!(
scanline_pixels.len(),
decoded.pixels_u8().unwrap().len(),
"output size mismatch"
);
let mut max_diff = 0i32;
let mut total_diff = 0u64;
for (i, (&a, &b)) in scanline_pixels
.iter()
.zip(decoded.pixels_u8().unwrap().iter())
.enumerate()
{
let diff = (a as i32 - b as i32).abs();
max_diff = max_diff.max(diff);
total_diff += diff as u64;
if diff > 10 {
panic!(
"Pixel at index {} differs by {} (scanline={}, regular={})",
i, diff, a, b
);
}
}
let avg_diff = total_diff as f64 / scanline_pixels.len() as f64;
assert!(
avg_diff < 3.0,
"Average pixel difference {} too high (max diff: {})",
avg_diff,
max_diff
);
}
#[test]
fn test_scanline_reader_420_non_mcu_aligned() {
use crate::decode::Decoder;
use crate::encode::v2::ChromaSubsampling;
let width = 37u32;
let height = 29u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 7) as u8;
pixels[idx + 1] = (y * 9) as u8;
pixels[idx + 2] = ((x + y) * 3) as u8;
}
}
let jpeg = encode_rgb_subsampled(width, height, &pixels, 90.0, ChromaSubsampling::Quarter);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let stride = (width * 3) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_rgb8(output).expect("read failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
assert_eq!(
scanline_pixels.len(),
decoded.pixels_u8().unwrap().len(),
"output size mismatch"
);
let mut max_diff = 0i32;
let mut total_diff = 0u64;
for (i, (&a, &b)) in scanline_pixels
.iter()
.zip(decoded.pixels_u8().unwrap().iter())
.enumerate()
{
let diff = (a as i32 - b as i32).abs();
max_diff = max_diff.max(diff);
total_diff += diff as u64;
if diff > 10 {
panic!(
"Pixel at index {} differs by {} (scanline={}, regular={})",
i, diff, a, b
);
}
}
let avg_diff = total_diff as f64 / scanline_pixels.len() as f64;
assert!(
avg_diff < 3.0,
"Average pixel difference {} too high (max diff: {})",
avg_diff,
max_diff
);
}
fn encode_grayscale(width: u32, height: u32, pixels: &[u8], quality: f32) -> Vec<u8> {
use crate::encode::v2::{EncoderConfig, PixelLayout};
use enough::Unstoppable;
let config = EncoderConfig::grayscale(quality);
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Gray8Srgb)
.unwrap();
enc.push_packed(pixels, Unstoppable).unwrap();
enc.finish().unwrap()
}
#[test]
fn test_scanline_reader_grayscale_basic() {
use crate::decode::Decoder;
use crate::types::PixelFormat;
let width = 64u32;
let height = 48u32;
let mut pixels = vec![0u8; (width * height) as usize];
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
pixels[idx] = ((x + y) * 2) as u8;
}
}
let jpeg = encode_grayscale(width, height, &pixels, 95.0);
let decoder = Decoder::new().output_format(PixelFormat::Gray);
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = Decoder::new()
.scanline_reader(&jpeg)
.expect("scanline_reader failed for grayscale");
assert_eq!(reader.width(), width);
assert_eq!(reader.height(), height);
assert!(reader.is_grayscale());
assert_eq!(reader.num_components(), 1);
let mut scanline_pixels = vec![0u8; (width * height) as usize];
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let stride = width as usize;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader
.read_rows_gray8(output)
.expect("read_rows_gray8 failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
assert_eq!(
scanline_pixels.len(),
decoded.pixels_u8().unwrap().len(),
"output size mismatch"
);
let (max_diff, diff_count, _) =
compare_u8_slices(&scanline_pixels, decoded.pixels_u8().unwrap());
assert!(
max_diff <= 2,
"grayscale scanline reader max_diff {} > 2 (diff_count: {})",
max_diff,
diff_count
);
}
#[test]
fn test_scanline_reader_grayscale_non_mcu_aligned() {
use crate::decode::Decoder;
use crate::types::PixelFormat;
let width = 37u32;
let height = 29u32;
let mut pixels = vec![0u8; (width * height) as usize];
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
pixels[idx] = (x * 7 + y * 3) as u8;
}
}
let jpeg = encode_grayscale(width, height, &pixels, 90.0);
let decoder = Decoder::new().output_format(PixelFormat::Gray);
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let mut reader = Decoder::new()
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
assert!(reader.is_grayscale());
let mut scanline_pixels = vec![0u8; (width * height) as usize];
let stride = width as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_gray8(output).expect("read failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
let (max_diff, _, _) = compare_u8_slices(&scanline_pixels, decoded.pixels_u8().unwrap());
assert!(
max_diff <= 2,
"grayscale non-MCU-aligned max_diff {} > 2",
max_diff
);
}
#[test]
fn test_scanline_reader_grayscale_f32() {
use crate::decode::Decoder;
let width = 32u32;
let height = 24u32;
let mut pixels = vec![0u8; (width * height) as usize];
for i in 0..pixels.len() {
pixels[i] = ((i * 13) % 256) as u8;
}
let jpeg = encode_grayscale(width, height, &pixels, 90.0);
let decoder = Decoder::new();
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut gray_pixels = vec![0.0f32; (width * height) as usize];
let stride = width as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output = imgref::ImgRefMut::new(&mut gray_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_gray_f32(output).expect("read failed");
total_rows += rows;
}
assert_eq!(total_rows, height as usize);
for (i, &val) in gray_pixels.iter().enumerate() {
assert!(
(0.0..=1.0).contains(&val),
"Value at {} should be in [0,1], got {}",
i,
val
);
}
}
#[test]
fn test_scanline_reader_grayscale_linear_f32() {
use crate::decode::Decoder;
let width = 16u32;
let height = 16u32;
let mut pixels = vec![0u8; (width * height) as usize];
for i in 0..pixels.len() {
pixels[i] = (i % 256) as u8;
}
let jpeg = encode_grayscale(width, height, &pixels, 95.0);
let decoder = Decoder::new();
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut linear_pixels = vec![0.0f32; (width * height) as usize];
let stride = width as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output = imgref::ImgRefMut::new(&mut linear_pixels[buf_start..], stride, remaining);
let rows = reader
.read_rows_gray_linear_f32(output)
.expect("read failed");
total_rows += rows;
}
for (i, &val) in linear_pixels.iter().enumerate() {
assert!(
(0.0..=1.0).contains(&val),
"Linear value at {} should be in [0,1], got {}",
i,
val
);
}
}
fn encode_progressive_rgb(width: u32, height: u32, pixels: &[u8], quality: f32) -> Vec<u8> {
use crate::encode::v2::{ChromaSubsampling, EncoderConfig, PixelLayout};
use enough::Unstoppable;
let config = EncoderConfig::ycbcr(quality, ChromaSubsampling::None).progressive(true);
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
.unwrap();
enc.push_packed(pixels, Unstoppable).unwrap();
enc.finish().unwrap()
}
#[test]
fn test_scanline_reader_progressive() {
let width = 16u32;
let height = 16u32;
let mut input_pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
input_pixels[idx] = ((x * 16) % 256) as u8; input_pixels[idx + 1] = ((y * 16) % 256) as u8; input_pixels[idx + 2] = 128; }
}
let progressive_jpeg = encode_progressive_rgb(width, height, &input_pixels, 95.0);
assert!(
progressive_jpeg.windows(2).any(|w| w == [0xFF, 0xC2]), "JPEG should be progressive (SOF2)"
);
let decoder = crate::decode::Decoder::new();
let mut reader = decoder
.scanline_reader(&progressive_jpeg)
.expect("scanline_reader should support progressive via buffered mode");
assert_eq!(reader.width(), width);
assert_eq!(reader.height(), height);
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let mut rows_read = 0;
while rows_read < height as usize {
let remaining = height as usize - rows_read;
let output = ImgRefMut::new(
&mut scanline_pixels[rows_read * width as usize * 3..],
width as usize * 3,
remaining,
);
let count = reader
.read_rows_rgb8(output)
.expect("read_rows_rgb8 failed");
if count == 0 {
break;
}
rows_read += count;
}
assert_eq!(rows_read, height as usize, "Should read all rows");
let decoded = decoder
.decode(&progressive_jpeg, enough::Unstoppable)
.expect("decode failed");
let (max_diff, diff_count, _) =
compare_u8_slices(&scanline_pixels, decoded.pixels_u8().unwrap());
assert_eq!(
max_diff, 0,
"Scanline reader should match full-frame decode for progressive JPEG (max diff={}, diff_count={})",
max_diff, diff_count
);
}
#[test]
fn test_scanline_reader_progressive_grayscale() {
let width = 16u32;
let height = 16u32;
let mut input_pixels = vec![0u8; (width * height) as usize];
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
input_pixels[idx] = ((x * 16 + y * 8) % 256) as u8;
}
}
use crate::encode::v2::{EncoderConfig, PixelLayout};
use enough::Unstoppable;
let config = EncoderConfig::grayscale(95.0).progressive(true);
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Gray8Srgb)
.unwrap();
enc.push_packed(&input_pixels, Unstoppable).unwrap();
let progressive_jpeg = enc.finish().unwrap();
assert!(
progressive_jpeg.windows(2).any(|w| w == [0xFF, 0xC2]),
"JPEG should be progressive (SOF2)"
);
let decoder = crate::decode::Decoder::new();
let mut reader = decoder
.scanline_reader(&progressive_jpeg)
.expect("scanline_reader should support progressive grayscale");
let mut scanline_pixels = vec![0u8; (width * height) as usize];
let mut rows_read = 0;
while rows_read < height as usize {
let remaining = height as usize - rows_read;
let output = ImgRefMut::new(
&mut scanline_pixels[rows_read * width as usize..],
width as usize,
remaining,
);
let count = reader
.read_rows_gray8(output)
.expect("read_rows_gray8 failed");
if count == 0 {
break;
}
rows_read += count;
}
assert_eq!(rows_read, height as usize, "Should read all rows");
let mean: f64 =
scanline_pixels.iter().map(|&x| x as f64).sum::<f64>() / scanline_pixels.len() as f64;
assert!(
mean > 50.0 && mean < 200.0,
"Grayscale mean should be reasonable: {}",
mean
);
}
#[test]
fn test_scanline_420_bottom_boundary_fixup() {
use crate::decode::Decoder;
use crate::encode::v2::ChromaSubsampling;
let width = 64u32;
let height = 64u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
if (y / 8) % 2 == 0 {
pixels[idx] = 255; pixels[idx + 1] = 0;
pixels[idx + 2] = 0;
} else {
pixels[idx] = 0;
pixels[idx + 1] = 0;
pixels[idx + 2] = 255; }
}
}
let jpeg = encode_rgb_subsampled(width, height, &pixels, 90.0, ChromaSubsampling::Quarter);
let decoder = Decoder::new();
let decoded = decoder
.decode(&jpeg, enough::Unstoppable)
.expect("decode failed");
let reference = decoded.pixels_u8().unwrap();
let mut reader = decoder
.scanline_reader(&jpeg)
.expect("scanline_reader failed");
let mut scanline_pixels = vec![0u8; (width * height * 3) as usize];
let stride = (width * 3) as usize;
let mut total_rows = 0;
while !reader.is_finished() {
let remaining = height as usize - total_rows;
let buf_start = total_rows * stride;
let output =
imgref::ImgRefMut::new(&mut scanline_pixels[buf_start..], stride, remaining);
let rows = reader.read_rows_rgb8(output).expect("read failed");
total_rows += rows;
}
let mcu_height = 16usize;
let mut boundary_max_diff = 0i32;
let mut interior_max_diff = 0i32;
for y in 0..height as usize {
for x in 0..width as usize {
for c in 0..3 {
let idx = (y * width as usize + x) * 3 + c;
let diff = (scanline_pixels[idx] as i32 - reference[idx] as i32).abs();
let is_boundary = y % mcu_height == mcu_height - 1;
if is_boundary {
boundary_max_diff = boundary_max_diff.max(diff);
} else {
interior_max_diff = interior_max_diff.max(diff);
}
}
}
}
assert!(
boundary_max_diff <= 4,
"Bottom boundary max diff {} too high (interior max diff: {}). \
Bottom boundary fixup may not be working.",
boundary_max_diff,
interior_max_diff
);
}
fn full_scanline_and_crop(jpeg: &[u8], cx: u32, cy: u32, cw: u32, ch: u32) -> Vec<u8> {
use crate::decode::DecodeConfig;
let mut reader = DecodeConfig::new().scanline_reader(jpeg).unwrap();
let width = reader.width() as usize;
let height = reader.height() as usize;
let mut full = vec![0u8; width * height * 3];
let mut rows_read = 0;
while !reader.is_finished() {
let remaining = height - rows_read;
let output =
imgref::ImgRefMut::new(&mut full[rows_read * width * 3..], width * 3, remaining);
rows_read += reader.read_rows_rgb8(output).unwrap();
}
let bpp = 3;
let mut cropped = vec![0u8; cw as usize * ch as usize * bpp];
for y in 0..ch as usize {
let src_off = ((cy as usize + y) * width + cx as usize) * bpp;
let dst_off = y * cw as usize * bpp;
let row_bytes = cw as usize * bpp;
cropped[dst_off..dst_off + row_bytes]
.copy_from_slice(&full[src_off..src_off + row_bytes]);
}
cropped
}
fn full_decode_and_crop(jpeg: &[u8], cx: u32, cy: u32, cw: u32, ch: u32) -> Vec<u8> {
use crate::decode::DecodeConfig;
let result = DecodeConfig::new()
.decode(jpeg, enough::Unstoppable)
.unwrap();
let width = result.width() as usize;
let pixels = result.pixels_u8().unwrap();
let bpp = 3; let mut cropped = vec![0u8; cw as usize * ch as usize * bpp];
for y in 0..ch as usize {
let src_off = ((cy as usize + y) * width + cx as usize) * bpp;
let dst_off = y * cw as usize * bpp;
let row_bytes = cw as usize * bpp;
cropped[dst_off..dst_off + row_bytes]
.copy_from_slice(&pixels[src_off..src_off + row_bytes]);
}
cropped
}
#[test]
fn test_crop_pixel_scanline_444() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 64u32;
let height = 64u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 4) as u8;
pixels[idx + 1] = (y * 4) as u8;
pixels[idx + 2] = 128;
}
}
let jpeg = encode_rgb(width, height, &pixels, 95.0);
let (cx, cy, cw, ch) = (10u32, 10u32, 20u32, 20u32);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let reference = full_decode_and_crop(&jpeg, cx, cy, cw, ch);
let mut reader = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.scanline_reader(&jpeg)
.unwrap();
let out_w = cw as usize;
let out_h = ch as usize;
let mut out = vec![0u8; out_w * out_h * 3];
let mut rows_read = 0;
while !reader.is_finished() {
let remaining = out_h - rows_read;
let output =
imgref::ImgRefMut::new(&mut out[rows_read * out_w * 3..], out_w * 3, remaining);
rows_read += reader.read_rows_rgb8(output).unwrap();
}
assert_eq!(rows_read, out_h);
assert_eq!(out, reference, "crop 444 mismatch at {perm}");
});
}
#[test]
fn test_crop_pixel_scanline_420() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 64u32;
let height = 64u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 4) as u8;
pixels[idx + 1] = (y * 4) as u8;
pixels[idx + 2] = 128;
}
}
let jpeg = encode_rgb_subsampled(
width,
height,
&pixels,
95.0,
crate::encode::v2::ChromaSubsampling::Quarter,
);
let (cx, cy, cw, ch) = (8u32, 16u32, 32u32, 24u32);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let reference = full_scanline_and_crop(&jpeg, cx, cy, cw, ch);
let mut reader = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.scanline_reader(&jpeg)
.unwrap();
let out_w = cw as usize;
let out_h = ch as usize;
let mut out = vec![0u8; out_w * out_h * 3];
let mut rows_read = 0;
while !reader.is_finished() {
let remaining = out_h - rows_read;
let output =
imgref::ImgRefMut::new(&mut out[rows_read * out_w * 3..], out_w * 3, remaining);
rows_read += reader.read_rows_rgb8(output).unwrap();
}
assert_eq!(rows_read, out_h);
assert_eq!(out, reference, "crop 420 mismatch at {perm}");
});
}
#[test]
fn test_crop_percent() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 100u32;
let height = 100u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = x as u8;
pixels[idx + 1] = y as u8;
pixels[idx + 2] = 64;
}
}
let jpeg = encode_rgb(width, height, &pixels, 95.0);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let result = DecodeConfig::new()
.crop(CropRegion::percent(0.25, 0.25, 0.5, 0.5))
.decode(&jpeg, enough::Unstoppable)
.unwrap();
assert_eq!(result.width(), 50);
assert_eq!(result.height(), 50);
let reference = full_decode_and_crop(&jpeg, 25, 25, 50, 50);
assert_eq!(
result.pixels_u8().unwrap(),
&reference[..],
"crop percent mismatch at {perm}"
);
});
}
#[test]
fn test_crop_full_image_is_noop() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 48u32;
let height = 48u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for i in 0..pixels.len() {
pixels[i] = (i * 7) as u8;
}
let jpeg = encode_rgb(width, height, &pixels, 90.0);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let full = DecodeConfig::new()
.decode(&jpeg, enough::Unstoppable)
.unwrap();
let cropped = DecodeConfig::new()
.crop(CropRegion::pixels(0, 0, width, height))
.decode(&jpeg, enough::Unstoppable)
.unwrap();
assert_eq!(full.width(), cropped.width());
assert_eq!(full.height(), cropped.height());
assert_eq!(
full.pixels_u8().unwrap(),
cropped.pixels_u8().unwrap(),
"full-image crop noop mismatch at {perm}"
);
});
}
#[test]
fn test_crop_streaming_dimensions() {
let width = 64u32;
let height = 64u32;
let pixels = vec![128u8; (width * height * 3) as usize];
let jpeg = encode_rgb(width, height, &pixels, 90.0);
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
let mut reader = DecodeConfig::new()
.crop(CropRegion::pixels(10, 20, 30, 15))
.scanline_reader(&jpeg)
.unwrap();
assert_eq!(reader.width(), 30);
assert_eq!(reader.height(), 15);
assert_eq!(reader.current_row(), 0);
assert!(!reader.is_finished());
let out_w = 30usize;
let mut out = vec![0u8; out_w * 15 * 3];
let output = imgref::ImgRefMut::new(&mut out, out_w * 3, 15);
let n = reader.read_rows_rgb8(output).unwrap();
assert_eq!(n, 15);
assert!(reader.is_finished());
assert_eq!(reader.current_row(), 15);
}
#[test]
fn test_crop_buffered_progressive() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 64u32;
let height = 64u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = (x * 4) as u8;
pixels[idx + 1] = (y * 4) as u8;
pixels[idx + 2] = 100;
}
}
use crate::encode::v2::{ChromaSubsampling, EncoderConfig, PixelLayout};
let config = EncoderConfig::ycbcr(90.0, ChromaSubsampling::None).progressive(true);
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
.unwrap();
enc.push_packed(&pixels, enough::Unstoppable).unwrap();
let jpeg = enc.finish().unwrap();
let (cx, cy, cw, ch) = (8u32, 8u32, 32u32, 32u32);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let reference = full_decode_and_crop(&jpeg, cx, cy, cw, ch);
let result = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.decode(&jpeg, enough::Unstoppable)
.unwrap();
assert_eq!(result.width(), cw);
assert_eq!(result.height(), ch);
assert_eq!(
result.pixels_u8().unwrap(),
&reference[..],
"progressive crop decode() mismatch at {perm}"
);
let mut reader = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.scanline_reader(&jpeg)
.unwrap();
let out_w = cw as usize;
let out_h = ch as usize;
let mut out = vec![0u8; out_w * out_h * 3];
let mut rows_read = 0;
while !reader.is_finished() {
let remaining = out_h - rows_read;
let output =
imgref::ImgRefMut::new(&mut out[rows_read * out_w * 3..], out_w * 3, remaining);
rows_read += reader.read_rows_rgb8(output).unwrap();
}
assert_eq!(
out, reference,
"progressive crop scanline mismatch at {perm}"
);
});
}
#[test]
fn test_crop_grayscale() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 48u32;
let height = 48u32;
let mut pixels = vec![0u8; (width * height) as usize];
for y in 0..height {
for x in 0..width {
pixels[(y * width + x) as usize] = ((x + y) * 3) as u8;
}
}
let jpeg = encode_grayscale(width, height, &pixels, 95.0);
let (cx, cy, cw, ch) = (4u32, 4u32, 24u32, 24u32);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let full = DecodeConfig::new()
.decode(&jpeg, enough::Unstoppable)
.unwrap();
let full_pix = full.pixels_u8().unwrap();
let full_w = full.width() as usize;
let bpp = full.format().bytes_per_pixel();
let mut reference = vec![0u8; cw as usize * ch as usize * bpp];
for y in 0..ch as usize {
let src_off = ((cy as usize + y) * full_w + cx as usize) * bpp;
let dst_off = y * cw as usize * bpp;
let row_bytes = cw as usize * bpp;
reference[dst_off..dst_off + row_bytes]
.copy_from_slice(&full_pix[src_off..src_off + row_bytes]);
}
let cropped = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.decode(&jpeg, enough::Unstoppable)
.unwrap();
assert_eq!(cropped.width(), cw);
assert_eq!(cropped.height(), ch);
assert_eq!(
cropped.pixels_u8().unwrap(),
&reference[..],
"grayscale crop mismatch at {perm}"
);
});
}
#[test]
fn test_crop_gray8_scanline() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 48u32;
let height = 48u32;
let mut pixels = vec![0u8; (width * height) as usize];
for y in 0..height {
for x in 0..width {
pixels[(y * width + x) as usize] = ((x + y) * 3) as u8;
}
}
let jpeg = encode_grayscale(width, height, &pixels, 95.0);
let (cx, cy, cw, ch) = (4u32, 4u32, 24u32, 24u32);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let mut full_reader = DecodeConfig::new().scanline_reader(&jpeg).unwrap();
let fw = full_reader.width() as usize;
let fh = full_reader.height() as usize;
let mut full_gray = vec![0u8; fw * fh];
let mut rows_read = 0;
while !full_reader.is_finished() {
let remaining = fh - rows_read;
let output =
imgref::ImgRefMut::new(&mut full_gray[rows_read * fw..], fw, remaining);
rows_read += full_reader.read_rows_gray8(output).unwrap();
}
let mut ref_gray = vec![0u8; cw as usize * ch as usize];
for y in 0..ch as usize {
let src_off = (cy as usize + y) * fw + cx as usize;
let dst_off = y * cw as usize;
ref_gray[dst_off..dst_off + cw as usize]
.copy_from_slice(&full_gray[src_off..src_off + cw as usize]);
}
let mut reader = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.scanline_reader(&jpeg)
.unwrap();
let out_w = cw as usize;
let out_h = ch as usize;
let mut out = vec![0u8; out_w * out_h];
let mut rows_read = 0;
while !reader.is_finished() {
let remaining = out_h - rows_read;
let output =
imgref::ImgRefMut::new(&mut out[rows_read * out_w..], out_w, remaining);
rows_read += reader.read_rows_gray8(output).unwrap();
}
assert_eq!(out, ref_gray, "gray8 crop scanline mismatch at {perm}");
});
}
#[test]
fn test_crop_validation_errors() {
let jpeg = encode_rgb(64, 64, &vec![128u8; 64 * 64 * 3], 90.0);
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
let err = DecodeConfig::new()
.crop(CropRegion::pixels(50, 50, 20, 20))
.decode(&jpeg, enough::Unstoppable);
assert!(err.is_err(), "Crop exceeding bounds should fail");
let err = DecodeConfig::new()
.crop(CropRegion::pixels(0, 0, 0, 10))
.decode(&jpeg, enough::Unstoppable);
assert!(err.is_err(), "Zero width crop should fail");
let err = DecodeConfig::new()
.crop(CropRegion::pixels(0, 0, 10, 0))
.decode(&jpeg, enough::Unstoppable);
assert!(err.is_err(), "Zero height crop should fail");
let err = DecodeConfig::new()
.crop(CropRegion::percent(0.0, 0.0, 1.5, 1.0))
.decode(&jpeg, enough::Unstoppable);
assert!(err.is_err(), "Percent > 1.0 should fail");
}
#[test]
fn test_crop_non_mcu_boundary() {
use crate::decode::DecodeConfig;
use crate::decode::config::CropRegion;
use archmage::testing::{CompileTimePolicy, for_each_token_permutation};
let width = 96u32;
let height = 96u32;
let mut pixels = vec![0u8; (width * height * 3) as usize];
for y in 0..height {
for x in 0..width {
let idx = ((y * width + x) * 3) as usize;
pixels[idx] = x as u8;
pixels[idx + 1] = y as u8;
pixels[idx + 2] = ((x + y) / 2) as u8;
}
}
let jpeg = encode_rgb_subsampled(
width,
height,
&pixels,
95.0,
crate::encode::v2::ChromaSubsampling::Quarter,
);
let (cx, cy, cw, ch) = (5u32, 7u32, 37u32, 29u32);
let _ = for_each_token_permutation(CompileTimePolicy::Warn, |perm| {
let reference = full_scanline_and_crop(&jpeg, cx, cy, cw, ch);
let mut reader = DecodeConfig::new()
.crop(CropRegion::pixels(cx, cy, cw, ch))
.scanline_reader(&jpeg)
.unwrap();
assert_eq!(reader.width(), cw);
assert_eq!(reader.height(), ch);
let out_w = cw as usize;
let out_h = ch as usize;
let mut out = vec![0u8; out_w * out_h * 3];
let mut rows_read = 0;
while !reader.is_finished() {
let remaining = out_h - rows_read;
let output =
imgref::ImgRefMut::new(&mut out[rows_read * out_w * 3..], out_w * 3, remaining);
rows_read += reader.read_rows_rgb8(output).unwrap();
}
assert_eq!(
out, reference,
"Non-MCU-aligned crop differs from reference at {perm}"
);
});
}
}