use super::idct_int::idct_int_tiered;
use crate::color::{ycbcr_planes_i16_to_rgb_u8, ycbcr_to_rgb};
use crate::entropy::{EntropyDecoder, EntropyDecoderState};
use crate::error::{Error, Result, ScanRead};
use crate::foundation::alloc::try_alloc_maybeuninit;
use crate::foundation::consts::{DCT_BLOCK_SIZE, MAX_HUFFMAN_TABLES};
use crate::huffman::HuffmanDecodeTable;
use crate::quant::dequantize_unzigzag_i32_into;
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: &'a [u8],
width: u32,
height: u32,
num_components: u8,
#[allow(dead_code)]
mcu_rows: usize,
mcu_cols: usize,
strip_width: usize,
mcu_height: usize,
h_samp: [u8; 3],
v_samp: [u8; 3],
max_h_samp: u8,
#[allow(dead_code)]
max_v_samp: u8,
subsampling: Subsampling,
current_row: usize, current_mcu_row: usize, row_in_mcu: usize, mcu_row_decoded: bool,
y_strip: Vec<i16>,
cb_strip: Vec<i16>,
cr_strip: Vec<i16>,
chroma_strip_width: usize,
chroma_strip_height: usize,
cb_upsampled: Vec<i16>,
cr_upsampled: Vec<i16>,
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,
dequant_buf: [i32; DCT_BLOCK_SIZE],
coeffs_buf: [i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [u8; 4],
is_xyb: bool,
}
impl<'a> ScanlineReader<'a> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
data: &'a [u8],
width: u32,
height: u32,
num_components: u8,
h_samp: [u8; 3],
v_samp: [u8; 3],
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,
restart_interval: u16,
is_xyb: bool,
) -> Result<Self> {
let max_h_samp = h_samp.iter().copied().max().unwrap_or(1);
let max_v_samp = v_samp.iter().copied().max().unwrap_or(1);
let subsampling = match (max_h_samp, max_v_samp) {
(1, 1) => Subsampling::S444,
(2, 1) => Subsampling::S422,
(2, 2) => Subsampling::S420,
(1, 2) => Subsampling::S440,
_ => Subsampling::S420,
};
let mcu_width = max_h_samp as usize * 8;
let mcu_height = max_v_samp as usize * 8;
let mcu_cols = (width as usize + mcu_width - 1) / mcu_width;
let mcu_rows = (height as usize + mcu_height - 1) / mcu_height;
let strip_width = mcu_cols * mcu_width;
let y_strip_size = strip_width * mcu_height;
let chroma_strip_width = mcu_cols * 8; let chroma_strip_height = 8; let chroma_strip_size = chroma_strip_width * chroma_strip_height;
let y_strip = try_alloc_maybeuninit(y_strip_size, "Y strip buffer")?;
let cb_strip = try_alloc_maybeuninit(chroma_strip_size, "Cb strip buffer")?;
let cr_strip = try_alloc_maybeuninit(chroma_strip_size, "Cr strip buffer")?;
let (cb_upsampled, cr_upsampled) = if subsampling != Subsampling::S444 {
let upsampled_size = strip_width * mcu_height;
(
try_alloc_maybeuninit(upsampled_size, "Cb upsampled buffer")?,
try_alloc_maybeuninit(upsampled_size, "Cr upsampled buffer")?,
)
} else {
(Vec::new(), Vec::new())
};
Ok(Self {
data,
width,
height,
num_components,
mcu_rows,
mcu_cols,
strip_width,
mcu_height,
h_samp,
v_samp,
max_h_samp,
max_v_samp,
subsampling,
current_row: 0,
current_mcu_row: 0,
row_in_mcu: 0,
mcu_row_decoded: false,
y_strip,
cb_strip,
cr_strip,
chroma_strip_width,
chroma_strip_height,
cb_upsampled,
cr_upsampled,
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,
dequant_buf: [0i32; DCT_BLOCK_SIZE],
coeffs_buf: [0i16; DCT_BLOCK_SIZE],
prev_coeff_counts: [64; 4], is_xyb,
})
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.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.subsampling,
}
}
#[inline]
pub fn subsampling(&self) -> Subsampling {
self.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
}
fn decode_mcu_row(&mut self) -> Result<()> {
if self.mcu_row_decoded {
return Ok(());
}
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);
}
for mcu_x in 0..self.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.h_samp[comp_idx] as usize;
let v_blocks = self.v_samp[comp_idx] as usize;
let (dc_idx, ac_idx) = self.table_mapping[comp_idx];
let quant_idx = self.quant_indices[comp_idx];
let quant = self.quant_tables[quant_idx]
.as_ref()
.ok_or(Error::internal("missing quantization table"))?;
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);
dequantize_unzigzag_i32_into(
&self.coeffs_buf,
quant,
&mut self.dequant_buf,
);
let (strip, stride) = match comp_idx {
0 => {
let x_offset = mcu_x * self.max_h_samp as usize * 8 + h * 8;
let y_offset = v * 8 * self.strip_width;
(&mut self.y_strip[y_offset + x_offset..], self.strip_width)
}
1 => {
let x_offset = mcu_x * 8;
(&mut self.cb_strip[x_offset..], self.chroma_strip_width)
}
_ => {
let x_offset = mcu_x * 8;
(&mut self.cr_strip[x_offset..], self.chroma_strip_width)
}
};
idct_int_tiered(&mut self.dequant_buf, strip, stride, coeff_count);
}
}
}
self.mcu_count += 1;
}
self.decoder_state = Some(decoder.save_state());
if self.subsampling != Subsampling::S444 {
self.upsample_chroma();
}
self.mcu_row_decoded = true;
Ok(())
}
fn upsample_chroma(&mut self) {
match self.subsampling {
Subsampling::S444 => {} Subsampling::S422 => self.upsample_h2v1(),
Subsampling::S420 => self.upsample_h2v2(),
Subsampling::S440 => self.upsample_h1v2(),
}
}
fn upsample_h2v1(&mut self) {
let in_width = self.chroma_strip_width;
let out_width = self.strip_width;
let height = self.mcu_height;
for y in 0..height {
let in_row = y.min(self.chroma_strip_height - 1);
for out_x in 0..out_width {
let in_x = out_x / 2;
let in_idx = in_row * in_width + in_x.min(in_width - 1);
let cb_curr = self.cb_strip[in_idx] as i32;
let cr_curr = self.cr_strip[in_idx] as i32;
let (cb_val, cr_val) = if out_x % 2 == 0 {
let left_idx = in_row * in_width + in_x.saturating_sub(1);
let cb_left = self.cb_strip[left_idx] as i32;
let cr_left = self.cr_strip[left_idx] as i32;
(
((3 * cb_curr + cb_left + 2) >> 2) as i16,
((3 * cr_curr + cr_left + 2) >> 2) as i16,
)
} else {
let right_idx = in_row * in_width + (in_x + 1).min(in_width - 1);
let cb_right = self.cb_strip[right_idx] as i32;
let cr_right = self.cr_strip[right_idx] as i32;
(
((3 * cb_curr + cb_right + 2) >> 2) as i16,
((3 * cr_curr + cr_right + 2) >> 2) as i16,
)
};
let out_idx = y * out_width + out_x;
self.cb_upsampled[out_idx] = cb_val;
self.cr_upsampled[out_idx] = cr_val;
}
}
}
fn upsample_h1v2(&mut self) {
let in_width = self.chroma_strip_width;
let in_height = self.chroma_strip_height;
let out_width = self.strip_width;
let out_height = self.mcu_height;
for out_y in 0..out_height {
let in_y = out_y / 2;
let is_top = out_y % 2 == 0;
for out_x in 0..out_width {
let in_x = out_x.min(in_width - 1);
let in_y_clamped = in_y.min(in_height - 1);
let curr_idx = in_y_clamped * in_width + in_x;
let cb_curr = self.cb_strip[curr_idx] as i32;
let cr_curr = self.cr_strip[curr_idx] as i32;
let neighbor_y = if is_top {
in_y_clamped.saturating_sub(1)
} else {
(in_y + 1).min(in_height - 1)
};
let neighbor_idx = neighbor_y * in_width + in_x;
let cb_neighbor = self.cb_strip[neighbor_idx] as i32;
let cr_neighbor = self.cr_strip[neighbor_idx] as i32;
let cb_val = ((3 * cb_curr + cb_neighbor + 2) >> 2) as i16;
let cr_val = ((3 * cr_curr + cr_neighbor + 2) >> 2) as i16;
let out_idx = out_y * out_width + out_x;
self.cb_upsampled[out_idx] = cb_val;
self.cr_upsampled[out_idx] = cr_val;
}
}
}
fn upsample_h2v2(&mut self) {
let in_width = self.chroma_strip_width;
let in_height = self.chroma_strip_height;
let out_width = self.strip_width;
let out_height = self.mcu_height;
for out_y in 0..out_height {
let in_y = out_y / 2;
let is_top = out_y % 2 == 0;
for out_x in 0..out_width {
let in_x = out_x / 2;
let is_left = out_x % 2 == 0;
let in_x_clamped = in_x.min(in_width - 1);
let in_y_clamped = in_y.min(in_height - 1);
let curr_idx = in_y_clamped * in_width + in_x_clamped;
let cb_curr = self.cb_strip[curr_idx] as i32;
let cr_curr = self.cr_strip[curr_idx] as i32;
let v_neighbor_y = if is_top {
in_y_clamped.saturating_sub(1)
} else {
(in_y + 1).min(in_height - 1)
};
let v_idx = v_neighbor_y * in_width + in_x_clamped;
let cb_v = self.cb_strip[v_idx] as i32;
let cr_v = self.cr_strip[v_idx] as i32;
let h_neighbor_x = if is_left {
in_x_clamped.saturating_sub(1)
} else {
(in_x + 1).min(in_width - 1)
};
let h_idx = in_y_clamped * in_width + h_neighbor_x;
let cb_h = self.cb_strip[h_idx] as i32;
let cr_h = self.cr_strip[h_idx] as i32;
let d_idx = v_neighbor_y * in_width + h_neighbor_x;
let cb_d = self.cb_strip[d_idx] as i32;
let cr_d = self.cr_strip[d_idx] as i32;
let cb_val = ((9 * cb_curr + 3 * cb_h + 3 * cb_v + cb_d + 8) >> 4) as i16;
let cr_val = ((9 * cr_curr + 3 * cr_h + 3 * cr_v + cr_d + 8) >> 4) as i16;
let out_idx = out_y * out_width + out_x;
self.cb_upsampled[out_idx] = cb_val;
self.cr_upsampled[out_idx] = cr_val;
}
}
}
fn advance_mcu_row(&mut self) {
self.current_mcu_row += 1;
self.row_in_mcu = 0;
self.mcu_row_decoded = false;
}
pub fn read_rows_rgb8(&mut self, mut output: ImgRefMut<'_, u8>) -> Result<usize> {
let max_rows = output.height();
let width = self.width as usize;
if output.width() < width * 3 {
return Err(Error::internal("output buffer too narrow for RGB8"));
}
let mut rows_written = 0;
while rows_written < max_rows && self.current_row < self.height as usize {
self.decode_mcu_row()?;
let strip_row = self.row_in_mcu;
let strip_offset = strip_row * self.strip_width;
let cols = width.min(self.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
let (cb_slice, cr_slice) = if self.subsampling == Subsampling::S444 {
(
&self.cb_strip[strip_offset..strip_offset + cols],
&self.cr_strip[strip_offset..strip_offset + cols],
)
} else {
(
&self.cb_upsampled[strip_offset..strip_offset + cols],
&self.cr_upsampled[strip_offset..strip_offset + cols],
)
};
ycbcr_planes_i16_to_rgb_u8(
&self.y_strip[strip_offset..strip_offset + cols],
cb_slice,
cr_slice,
out_row,
);
rows_written += 1;
self.current_row += 1;
self.row_in_mcu += 1;
if self.row_in_mcu >= self.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
pub fn read_rows_rgbx8(&mut self, mut output: ImgRefMut<'_, u8>) -> Result<usize> {
let max_rows = output.height();
let width = self.width as usize;
if output.width() < width * 4 {
return Err(Error::internal("output buffer too narrow for RGBX8"));
}
let mut rows_written = 0;
while rows_written < max_rows && self.current_row < self.height as usize {
self.decode_mcu_row()?;
let strip_row = self.row_in_mcu;
let strip_offset = strip_row * self.strip_width;
let cols = width.min(self.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
let (cb_buf, cr_buf): (&[i16], &[i16]) = if self.subsampling == Subsampling::S444 {
(&self.cb_strip, &self.cr_strip)
} else {
(&self.cb_upsampled, &self.cr_upsampled)
};
for x in 0..cols {
let y = self.y_strip[strip_offset + x];
let cb = cb_buf[strip_offset + x];
let cr = cr_buf[strip_offset + x];
let (r, g, b) = ycbcr_to_rgb(
y.clamp(0, 255) as u8,
cb.clamp(0, 255) as u8,
cr.clamp(0, 255) as u8,
);
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.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 width = self.width as usize;
if output.width() < width * 4 {
return Err(Error::internal("output buffer too narrow for RGBA f32"));
}
let mut rows_written = 0;
while rows_written < max_rows && self.current_row < self.height as usize {
self.decode_mcu_row()?;
let strip_row = self.row_in_mcu;
let strip_offset = strip_row * self.strip_width;
let cols = width.min(self.strip_width);
let out_row = output.rows_mut().nth(rows_written).unwrap();
let (cb_buf, cr_buf): (&[i16], &[i16]) = if self.subsampling == Subsampling::S444 {
(&self.cb_strip, &self.cr_strip)
} else {
(&self.cb_upsampled, &self.cr_upsampled)
};
for x in 0..cols {
let y = self.y_strip[strip_offset + x];
let cb = cb_buf[strip_offset + x];
let cr = cr_buf[strip_offset + x];
let (r, g, b) = ycbcr_to_rgb(
y.clamp(0, 255) as u8,
cb.clamp(0, 255) as u8,
cr.clamp(0, 255) as u8,
);
out_row[x * 4] = srgb_to_linear(r);
out_row[x * 4 + 1] = srgb_to_linear(g);
out_row[x * 4 + 2] = srgb_to_linear(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.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
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> {
let width = self.width as usize;
if stride < width {
return Err(Error::internal("stride too small for image width"));
}
let mut rows_written = 0;
while rows_written < max_rows && self.current_row < self.height as usize {
self.decode_mcu_row()?;
let strip_row = self.row_in_mcu;
let strip_offset = strip_row * self.strip_width;
let cols = width.min(self.strip_width);
let out_offset = rows_written * stride;
let (cb_buf, cr_buf): (&[i16], &[i16]) = if self.subsampling == Subsampling::S444 {
(&self.cb_strip, &self.cr_strip)
} else {
(&self.cb_upsampled, &self.cr_upsampled)
};
for x in 0..cols {
y_plane[out_offset + x] = self.y_strip[strip_offset + x] as f32 / 255.0;
cb_plane[out_offset + x] = (cb_buf[strip_offset + x] as f32 - 128.0) / 255.0;
cr_plane[out_offset + x] = (cr_buf[strip_offset + 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.mcu_height {
self.advance_mcu_row();
}
}
Ok(rows_written)
}
}
#[inline]
fn srgb_to_linear(srgb: u8) -> f32 {
let s = srgb as f32 / 255.0;
if s <= 0.04045 {
s / 12.92
} else {
((s + 0.055) / 1.055).powf(2.4)
}
}
#[cfg(test)]
mod tests {
use super::*;
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);
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).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.data.len(),
"output size mismatch"
);
assert_slices_equal_u8(&scanline_pixels, &decoded.data, "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).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.data,
"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).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.data[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).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.data[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_planes(
&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).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.data,
"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).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.data.len(),
"output size mismatch"
);
let mut max_diff = 0i32;
let mut total_diff = 0u64;
for (i, (&a, &b)) in scanline_pixels.iter().zip(decoded.data.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).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.data.len(),
"output size mismatch"
);
let mut max_diff = 0i32;
let mut total_diff = 0u64;
for (i, (&a, &b)) in scanline_pixels.iter().zip(decoded.data.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
);
}
}