use crate::api::encoder::HuffmanTableDef;
use crate::common::error::{JpegError, Result};
use crate::common::types::{DctMethod, PixelFormat, SavedMarker, ScanScript, Subsampling};
use crate::encode::color;
use crate::encode::huffman_encode::{build_huff_table, BitWriter, HuffTable, HuffmanEncoder};
use crate::encode::marker_writer;
use crate::encode::progressive::ProgressiveScan;
use crate::encode::tables;
use crate::simd::QuantDivisors;
pub fn compress(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
dct_method: DctMethod,
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
if pixel_format == PixelFormat::Cmyk {
return compress_cmyk(pixels, width, height, quality);
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let luma_quant = tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let dc_luma_table = build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_luma_table = build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let dc_chroma_table =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let ac_chroma_table =
build_huff_table(&tables::AC_CHROMINANCE_BITS, &tables::AC_CHROMINANCE_VALUES);
let enc_simd = crate::simd::detect_encoder();
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x: usize = width.div_ceil(mcu_w);
let mcus_y: usize = height.div_ceil(mcu_h);
let fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]) =
if dct_method == DctMethod::IsLow {
enc_simd.fdct_quantize
} else {
crate::simd::scalar::scalar_fdct_quantize
};
let mut bit_writer = BitWriter::new(width * height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
if pixel_format == PixelFormat::Rgb && !is_grayscale {
let rgb_to_ycbcr_fn = enc_simd.rgb_to_ycbcr_row;
let row_buf_size: usize = width * mcu_h;
let mut y_buf: Vec<u8> = vec![0u8; row_buf_size];
let mut cb_buf: Vec<u8> = vec![0u8; row_buf_size];
let mut cr_buf: Vec<u8> = vec![0u8; row_buf_size];
for mcu_row in 0..mcus_y {
let y0: usize = mcu_row * mcu_h;
let rows_available: usize = (height - y0).min(mcu_h);
for row in 0..rows_available {
let src_row: usize = y0 + row;
let src_offset: usize = src_row * width * 3;
let dst_offset: usize = row * width;
rgb_to_ycbcr_fn(
&pixels[src_offset..src_offset + width * 3],
&mut y_buf[dst_offset..dst_offset + width],
&mut cb_buf[dst_offset..dst_offset + width],
&mut cr_buf[dst_offset..dst_offset + width],
width,
);
}
for row in rows_available..mcu_h {
let dst_offset: usize = row * width;
let src_offset: usize = (rows_available - 1) * width;
y_buf.copy_within(src_offset..src_offset + width, dst_offset);
cb_buf.copy_within(src_offset..src_offset + width, dst_offset);
cr_buf.copy_within(src_offset..src_offset + width, dst_offset);
}
for mcu_col in 0..mcus_x {
let x0: usize = mcu_col * mcu_w;
encode_color_mcu(
&y_buf,
&cb_buf,
&cr_buf,
width,
mcu_h,
x0,
0,
subsampling,
&luma_divisors,
&chroma_divisors,
&dc_luma_table,
&ac_luma_table,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_y,
&mut prev_dc_cb,
&mut prev_dc_cr,
fdct_quantize_fn,
);
}
}
} else {
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0: usize = mcu_col * mcu_w;
let y0: usize = mcu_row * mcu_h;
if is_grayscale {
encode_single_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
fdct_quantize_fn,
);
} else {
encode_color_mcu(
&y_plane,
&cb_plane,
&cr_plane,
width,
height,
x0,
y0,
subsampling,
&luma_divisors,
&chroma_divisors,
&dc_luma_table,
&ac_luma_table,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_y,
&mut prev_dc_cb,
&mut prev_dc_cr,
fdct_quantize_fn,
);
}
}
}
}
bit_writer.flush();
let mut output = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
} else {
let (h_samp, v_samp) = subsampling.sampling_factors();
let components = vec![
(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1), ];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
if !is_grayscale {
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
1,
&tables::AC_CHROMINANCE_BITS,
&tables::AC_CHROMINANCE_VALUES,
);
}
if is_grayscale {
let scan_components = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components = vec![
(1, 0, 0), (2, 1, 1), (3, 1, 1), ];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
#[allow(clippy::too_many_arguments)]
pub fn compress_custom_huffman(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
custom_dc: &[Option<HuffmanTableDef>; 4],
custom_ac: &[Option<HuffmanTableDef>; 4],
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
if pixel_format == PixelFormat::Cmyk {
return compress_cmyk(pixels, width, height, quality);
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let luma_quant = tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let dc_luma_bits: [u8; 17] = custom_dc[0]
.as_ref()
.map(|t| t.bits)
.unwrap_or(tables::DC_LUMINANCE_BITS);
let dc_luma_vals: Vec<u8> = custom_dc[0]
.as_ref()
.map(|t| t.values.clone())
.unwrap_or_else(|| tables::DC_LUMINANCE_VALUES.to_vec());
let ac_luma_bits: [u8; 17] = custom_ac[0]
.as_ref()
.map(|t| t.bits)
.unwrap_or(tables::AC_LUMINANCE_BITS);
let ac_luma_vals: Vec<u8> = custom_ac[0]
.as_ref()
.map(|t| t.values.clone())
.unwrap_or_else(|| tables::AC_LUMINANCE_VALUES.to_vec());
let dc_chroma_bits: [u8; 17] = custom_dc[1]
.as_ref()
.map(|t| t.bits)
.unwrap_or(tables::DC_CHROMINANCE_BITS);
let dc_chroma_vals: Vec<u8> = custom_dc[1]
.as_ref()
.map(|t| t.values.clone())
.unwrap_or_else(|| tables::DC_CHROMINANCE_VALUES.to_vec());
let ac_chroma_bits: [u8; 17] = custom_ac[1]
.as_ref()
.map(|t| t.bits)
.unwrap_or(tables::AC_CHROMINANCE_BITS);
let ac_chroma_vals: Vec<u8> = custom_ac[1]
.as_ref()
.map(|t| t.values.clone())
.unwrap_or_else(|| tables::AC_CHROMINANCE_VALUES.to_vec());
let dc_luma_table = build_huff_table(&dc_luma_bits, &dc_luma_vals);
let ac_luma_table = build_huff_table(&ac_luma_bits, &ac_luma_vals);
let dc_chroma_table = build_huff_table(&dc_chroma_bits, &dc_chroma_vals);
let ac_chroma_table = build_huff_table(&ac_chroma_bits, &ac_chroma_vals);
let enc_simd = crate::simd::detect_encoder();
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x = width.div_ceil(mcu_w);
let mcus_y = height.div_ceil(mcu_h);
let mut bit_writer = BitWriter::new(width * height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0 = mcu_col * mcu_w;
let y0 = mcu_row * mcu_h;
if is_grayscale {
encode_single_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
enc_simd.fdct_quantize,
);
} else {
encode_color_mcu(
&y_plane,
&cb_plane,
&cr_plane,
width,
height,
x0,
y0,
subsampling,
&luma_divisors,
&chroma_divisors,
&dc_luma_table,
&ac_luma_table,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_y,
&mut prev_dc_cb,
&mut prev_dc_cr,
enc_simd.fdct_quantize,
);
}
}
}
bit_writer.flush();
let mut output = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
} else {
let (h_samp, v_samp) = subsampling.sampling_factors();
let components = vec![
(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1), ];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(&mut output, 0, 0, &dc_luma_bits, &dc_luma_vals);
marker_writer::write_dht(&mut output, 1, 0, &ac_luma_bits, &ac_luma_vals);
if !is_grayscale {
marker_writer::write_dht(&mut output, 0, 1, &dc_chroma_bits, &dc_chroma_vals);
marker_writer::write_dht(&mut output, 1, 1, &ac_chroma_bits, &ac_chroma_vals);
}
if is_grayscale {
let scan_components = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components = vec![
(1, 0, 0), (2, 1, 1), (3, 1, 1), ];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_custom_quant(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
custom_quant: &[Option<[u16; 64]>; 4],
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
if pixel_format == PixelFormat::Cmyk {
return compress_cmyk(pixels, width, height, quality);
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let luma_quant = match custom_quant[0] {
Some(table) => table,
None => tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality),
};
let chroma_quant = match custom_quant[1] {
Some(table) => table,
None => tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality),
};
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let dc_luma_table = build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_luma_table = build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let dc_chroma_table =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let ac_chroma_table =
build_huff_table(&tables::AC_CHROMINANCE_BITS, &tables::AC_CHROMINANCE_VALUES);
let enc_simd = crate::simd::detect_encoder();
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x = width.div_ceil(mcu_w);
let mcus_y = height.div_ceil(mcu_h);
let mut bit_writer = BitWriter::new(width * height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0 = mcu_col * mcu_w;
let y0 = mcu_row * mcu_h;
if is_grayscale {
encode_single_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
enc_simd.fdct_quantize,
);
} else {
encode_color_mcu(
&y_plane,
&cb_plane,
&cr_plane,
width,
height,
x0,
y0,
subsampling,
&luma_divisors,
&chroma_divisors,
&dc_luma_table,
&ac_luma_table,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_y,
&mut prev_dc_cb,
&mut prev_dc_cr,
enc_simd.fdct_quantize,
);
}
}
}
bit_writer.flush();
let mut output = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
} else {
let (h_samp, v_samp) = subsampling.sampling_factors();
let components = vec![
(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1), ];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
if !is_grayscale {
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
1,
&tables::AC_CHROMINANCE_BITS,
&tables::AC_CHROMINANCE_VALUES,
);
}
if is_grayscale {
let scan_components = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components = vec![
(1, 0, 0), (2, 1, 1), (3, 1, 1), ];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_with_restart(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
restart_interval: u16,
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
if pixel_format == PixelFormat::Cmyk {
return compress_cmyk(pixels, width, height, quality);
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let luma_quant = tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let dc_luma_table = build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_luma_table = build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let dc_chroma_table =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let ac_chroma_table =
build_huff_table(&tables::AC_CHROMINANCE_BITS, &tables::AC_CHROMINANCE_VALUES);
let enc_simd = crate::simd::detect_encoder();
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x = width.div_ceil(mcu_w);
let mcus_y = height.div_ceil(mcu_h);
let mut bit_writer = BitWriter::new(width * height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
let mut mcu_count: u32 = 0;
let mut rst_count: u8 = 0;
let ri = restart_interval as u32;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
if ri > 0 && mcu_count > 0 && mcu_count.is_multiple_of(ri) {
bit_writer.flush_restart();
bit_writer.write_restart_marker(rst_count);
rst_count = rst_count.wrapping_add(1);
prev_dc_y = 0;
prev_dc_cb = 0;
prev_dc_cr = 0;
}
let x0 = mcu_col * mcu_w;
let y0 = mcu_row * mcu_h;
if is_grayscale {
encode_single_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
enc_simd.fdct_quantize,
);
} else {
encode_color_mcu(
&y_plane,
&cb_plane,
&cr_plane,
width,
height,
x0,
y0,
subsampling,
&luma_divisors,
&chroma_divisors,
&dc_luma_table,
&ac_luma_table,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_y,
&mut prev_dc_cb,
&mut prev_dc_cr,
enc_simd.fdct_quantize,
);
}
mcu_count += 1;
}
}
bit_writer.flush();
let mut output = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
if restart_interval > 0 {
marker_writer::write_dri(&mut output, restart_interval);
}
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
} else {
let (h_samp, v_samp) = subsampling.sampling_factors();
let components = vec![
(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1), ];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
if !is_grayscale {
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
1,
&tables::AC_CHROMINANCE_BITS,
&tables::AC_CHROMINANCE_VALUES,
);
}
if is_grayscale {
let scan_components = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components = vec![
(1, 0, 0), (2, 1, 1), (3, 1, 1), ];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
#[allow(clippy::too_many_arguments)]
pub fn compress_with_metadata(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
icc_profile: Option<&[u8]>,
exif_data: Option<&[u8]>,
) -> Result<Vec<u8>> {
let base = compress(
pixels,
width,
height,
pixel_format,
quality,
subsampling,
DctMethod::IsLow,
)?;
inject_metadata(&base, icc_profile, exif_data)
}
pub fn inject_metadata(
base: &[u8],
icc_profile: Option<&[u8]>,
exif_data: Option<&[u8]>,
) -> Result<Vec<u8>> {
if icc_profile.is_none() && exif_data.is_none() {
return Ok(base.to_vec());
}
let insert_pos = if base.len() >= 4 && base[2] == 0xFF && base[3] == 0xE0 {
let app0_len = u16::from_be_bytes([base[4], base[5]]) as usize;
2 + 2 + app0_len } else {
2 };
let extra_cap =
icc_profile.map_or(0, |p| p.len() + 100) + exif_data.map_or(0, |e| e.len() + 20);
let mut out = Vec::with_capacity(base.len() + extra_cap);
out.extend_from_slice(&base[..insert_pos]);
if let Some(exif) = exif_data {
marker_writer::write_app1_exif(&mut out, exif);
}
if let Some(icc) = icc_profile {
marker_writer::write_app2_icc(&mut out, icc);
}
out.extend_from_slice(&base[insert_pos..]);
Ok(out)
}
pub fn inject_comment(base: &[u8], text: &str) -> Vec<u8> {
let insert_pos = if base.len() >= 4 && base[2] == 0xFF && base[3] == 0xE0 {
let app0_len = u16::from_be_bytes([base[4], base[5]]) as usize;
2 + 2 + app0_len } else {
2 };
let mut out = Vec::with_capacity(base.len() + text.len() + 6);
out.extend_from_slice(&base[..insert_pos]);
marker_writer::write_com(&mut out, text);
out.extend_from_slice(&base[insert_pos..]);
out
}
pub fn inject_saved_markers(base: &[u8], markers: &[SavedMarker]) -> Vec<u8> {
if markers.is_empty() {
return base.to_vec();
}
let insert_pos: usize = if base.len() >= 4 && base[2] == 0xFF && base[3] == 0xE0 {
let app0_len: usize = u16::from_be_bytes([base[4], base[5]]) as usize;
2 + 2 + app0_len
} else {
2
};
let extra: usize = markers.iter().map(|m| m.data.len() + 4).sum();
let mut out: Vec<u8> = Vec::with_capacity(base.len() + extra);
out.extend_from_slice(&base[..insert_pos]);
for marker in markers {
marker_writer::write_marker(&mut out, marker.code, &marker.data);
}
out.extend_from_slice(&base[insert_pos..]);
out
}
fn compress_cmyk(pixels: &[u8], width: usize, height: usize, quality: u8) -> Result<Vec<u8>> {
let quant_table =
tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let divisors = scale_quant_for_fdct(&quant_table);
let dc_table = build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_table = build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let num_pixels = width * height;
let mut planes: [Vec<u8>; 4] = [
vec![0u8; num_pixels],
vec![0u8; num_pixels],
vec![0u8; num_pixels],
vec![0u8; num_pixels],
];
for i in 0..num_pixels {
planes[0][i] = pixels[i * 4];
planes[1][i] = pixels[i * 4 + 1];
planes[2][i] = pixels[i * 4 + 2];
planes[3][i] = pixels[i * 4 + 3];
}
let mcus_x = width.div_ceil(8);
let mcus_y = height.div_ceil(8);
let enc_simd = crate::simd::detect_encoder();
let mut bit_writer = BitWriter::new(width * height);
let mut prev_dc = [0i16; 4];
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0 = mcu_col * 8;
let y0 = mcu_row * 8;
for c in 0..4 {
encode_single_block(
&planes[c],
width,
height,
x0,
y0,
&divisors,
&dc_table,
&ac_table,
&mut bit_writer,
&mut prev_dc[c],
enc_simd.fdct_quantize,
);
}
}
}
bit_writer.flush();
let mut output = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_app14_adobe(&mut output, 0);
marker_writer::write_dqt(&mut output, 0, &quant_table);
let components = vec![(1, 1, 1, 0), (2, 1, 1, 0), (3, 1, 1, 0), (4, 1, 1, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
let scan_components = vec![(1, 0, 0), (2, 0, 0), (3, 0, 0), (4, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_lossless(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
) -> Result<Vec<u8>> {
compress_lossless_extended(pixels, width, height, pixel_format, 1, 0)
}
pub fn compress_lossless_extended(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
predictor: u8,
point_transform: u8,
) -> Result<Vec<u8>> {
if !(1..=7).contains(&predictor) {
return Err(JpegError::Unsupported(format!(
"lossless predictor must be 1-7, got {}",
predictor
)));
}
if point_transform >= 8 {
return Err(JpegError::Unsupported(format!(
"point transform must be 0-7 for 8-bit precision, got {}",
point_transform
)));
}
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp: usize = pixel_format.bytes_per_pixel();
let expected_size: usize = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
match pixel_format {
PixelFormat::Grayscale => {
compress_lossless_grayscale(pixels, width, height, predictor, point_transform)
}
PixelFormat::Rgb => {
compress_lossless_rgb(pixels, width, height, predictor, point_transform)
}
_ => Err(JpegError::Unsupported(format!(
"lossless encoding does not support {:?}, use Grayscale or Rgb",
pixel_format
))),
}
}
#[allow(clippy::too_many_arguments)]
fn lossless_diff(
pixel: i32,
x: usize,
y: usize,
plane: &[u8],
width: usize,
predictor: u8,
point_transform: u8,
precision: u8,
) -> i16 {
let mask: i32 = (1i32 << precision) - 1;
let initial_pred: i32 = 1 << (precision as i32 - point_transform as i32 - 1);
let sample: i32 = pixel >> point_transform as i32;
let prediction: i32 = if y == 0 && x == 0 {
initial_pred
} else if y == 0 {
(plane[y * width + x - 1] as i32) >> point_transform as i32
} else if x == 0 {
(plane[(y - 1) * width + x] as i32) >> point_transform as i32
} else {
let ra: i32 = (plane[y * width + x - 1] as i32) >> point_transform as i32;
let rb: i32 = (plane[(y - 1) * width + x] as i32) >> point_transform as i32;
let rc: i32 = (plane[(y - 1) * width + x - 1] as i32) >> point_transform as i32;
crate::decode::lossless::predict(predictor, ra, rb, rc)
};
let diff: i32 = (sample - prediction) & mask;
if diff >= (1 << (precision - 1)) {
(diff - (1 << precision)) as i16
} else {
diff as i16
}
}
fn compress_lossless_grayscale(
pixels: &[u8],
width: usize,
height: usize,
predictor: u8,
point_transform: u8,
) -> Result<Vec<u8>> {
let precision: u8 = 8;
let mut bit_writer: BitWriter = BitWriter::new(width * height);
let dc_table: HuffTable =
build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
for y in 0..height {
for x in 0..width {
let pixel: i32 = pixels[y * width + x] as i32;
let signed_diff: i16 = lossless_diff(
pixel,
x,
y,
pixels,
width,
predictor,
point_transform,
precision,
);
HuffmanEncoder::encode_dc_only(&mut bit_writer, signed_diff, &dc_table);
}
}
bit_writer.flush();
let mut output: Vec<u8> = Vec::with_capacity(bit_writer.data().len() + 256);
marker_writer::write_soi(&mut output);
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
let components: Vec<(u8, u8, u8, u8)> = vec![(1, 1, 1, 0)];
marker_writer::write_sof3(
&mut output,
width as u16,
height as u16,
precision,
&components,
);
let scan_components: Vec<(u8, u8)> = vec![(1, 0)];
marker_writer::write_sos_lossless(&mut output, &scan_components, predictor, point_transform);
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
fn compress_lossless_rgb(
pixels: &[u8],
width: usize,
height: usize,
predictor: u8,
point_transform: u8,
) -> Result<Vec<u8>> {
let precision: u8 = 8;
let num_pixels: usize = width * height;
let mut y_plane: Vec<u8> = vec![0u8; num_pixels];
let mut cb_plane: Vec<u8> = vec![0u8; num_pixels];
let mut cr_plane: Vec<u8> = vec![0u8; num_pixels];
for row in 0..height {
let row_start: usize = row * width * 3;
let plane_start: usize = row * width;
color::rgb_to_ycbcr_row(
&pixels[row_start..row_start + width * 3],
&mut y_plane[plane_start..plane_start + width],
&mut cb_plane[plane_start..plane_start + width],
&mut cr_plane[plane_start..plane_start + width],
width,
);
}
let planes: [&[u8]; 3] = [&y_plane, &cb_plane, &cr_plane];
let dc_table_luma: HuffTable =
build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let dc_table_chroma: HuffTable =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let dc_tables: [&HuffTable; 3] = [&dc_table_luma, &dc_table_chroma, &dc_table_chroma];
let mut bit_writer: BitWriter = BitWriter::new(num_pixels * 3);
for y in 0..height {
for x in 0..width {
for c in 0..3 {
let pixel: i32 = planes[c][y * width + x] as i32;
let signed_diff: i16 = lossless_diff(
pixel,
x,
y,
planes[c],
width,
predictor,
point_transform,
precision,
);
HuffmanEncoder::encode_dc_only(&mut bit_writer, signed_diff, dc_tables[c]);
}
}
}
bit_writer.flush();
let mut output: Vec<u8> = Vec::with_capacity(bit_writer.data().len() + 512);
marker_writer::write_soi(&mut output);
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
let components: Vec<(u8, u8, u8, u8)> = vec![
(1, 1, 1, 0), (2, 1, 1, 0), (3, 1, 1, 0), ];
marker_writer::write_sof3(
&mut output,
width as u16,
height as u16,
precision,
&components,
);
let scan_components: Vec<(u8, u8)> = vec![
(1, 0), (2, 1), (3, 1), ];
marker_writer::write_sos_lossless(&mut output, &scan_components, predictor, point_transform);
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_lossless_arithmetic(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
predictor: u8,
point_transform: u8,
) -> Result<Vec<u8>> {
if !(1..=7).contains(&predictor) {
return Err(JpegError::Unsupported(format!(
"lossless predictor must be 1-7, got {}",
predictor
)));
}
if point_transform >= 8 {
return Err(JpegError::Unsupported(format!(
"point transform must be 0-7 for 8-bit precision, got {}",
point_transform
)));
}
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp: usize = pixel_format.bytes_per_pixel();
let expected_size: usize = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
match pixel_format {
PixelFormat::Grayscale => compress_lossless_arithmetic_grayscale(
pixels,
width,
height,
predictor,
point_transform,
),
PixelFormat::Rgb => {
compress_lossless_arithmetic_rgb(pixels, width, height, predictor, point_transform)
}
_ => Err(JpegError::Unsupported(format!(
"lossless arithmetic encoding does not support {:?}, use Grayscale or Rgb",
pixel_format
))),
}
}
fn compress_lossless_arithmetic_grayscale(
pixels: &[u8],
width: usize,
height: usize,
predictor: u8,
point_transform: u8,
) -> Result<Vec<u8>> {
use crate::encode::arithmetic::ArithEncoder;
let precision: u8 = 8;
let mut arith_enc: ArithEncoder = ArithEncoder::new(width * height);
for y in 0..height {
for x in 0..width {
let pixel: i32 = pixels[y * width + x] as i32;
let signed_diff: i16 = lossless_diff(
pixel,
x,
y,
pixels,
width,
predictor,
point_transform,
precision,
);
let mut block: [i16; 64] = [0i16; 64];
block[0] = signed_diff.wrapping_add(arith_enc.last_dc_val[0] as i16);
arith_enc.encode_dc_sequential(&block, 0, 0);
}
}
arith_enc.finish();
let mut output: Vec<u8> = Vec::with_capacity(arith_enc.data().len() + 256);
marker_writer::write_soi(&mut output);
let components: Vec<(u8, u8, u8, u8)> = vec![(1, 1, 1, 0)];
marker_writer::write_sof11(
&mut output,
width as u16,
height as u16,
precision,
&components,
);
let dc_params: [(u8, u8); 2] = [(0u8, 1u8), (0, 1)];
let ac_params: [u8; 2] = [5u8, 5];
marker_writer::write_dac(&mut output, 1, &dc_params, 0, &ac_params);
let scan_components: Vec<(u8, u8)> = vec![(1, 0)];
marker_writer::write_sos_lossless(&mut output, &scan_components, predictor, point_transform);
output.extend_from_slice(arith_enc.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
fn compress_lossless_arithmetic_rgb(
pixels: &[u8],
width: usize,
height: usize,
predictor: u8,
point_transform: u8,
) -> Result<Vec<u8>> {
use crate::encode::arithmetic::ArithEncoder;
let precision: u8 = 8;
let num_pixels: usize = width * height;
let mut y_plane: Vec<u8> = vec![0u8; num_pixels];
let mut cb_plane: Vec<u8> = vec![0u8; num_pixels];
let mut cr_plane: Vec<u8> = vec![0u8; num_pixels];
for row in 0..height {
let row_start: usize = row * width * 3;
let plane_start: usize = row * width;
color::rgb_to_ycbcr_row(
&pixels[row_start..row_start + width * 3],
&mut y_plane[plane_start..plane_start + width],
&mut cb_plane[plane_start..plane_start + width],
&mut cr_plane[plane_start..plane_start + width],
width,
);
}
let planes: [&[u8]; 3] = [&y_plane, &cb_plane, &cr_plane];
let dc_tbls: [usize; 3] = [0, 1, 1];
let mut arith_enc: ArithEncoder = ArithEncoder::new(num_pixels * 3);
for y in 0..height {
for x in 0..width {
for c in 0..3 {
let pixel: i32 = planes[c][y * width + x] as i32;
let signed_diff: i16 = lossless_diff(
pixel,
x,
y,
planes[c],
width,
predictor,
point_transform,
precision,
);
let mut block: [i16; 64] = [0i16; 64];
block[0] = signed_diff.wrapping_add(arith_enc.last_dc_val[c] as i16);
arith_enc.encode_dc_sequential(&block, c, dc_tbls[c]);
}
}
}
arith_enc.finish();
let mut output: Vec<u8> = Vec::with_capacity(arith_enc.data().len() + 512);
marker_writer::write_soi(&mut output);
let components: Vec<(u8, u8, u8, u8)> = vec![
(1, 1, 1, 0), (2, 1, 1, 0), (3, 1, 1, 0), ];
marker_writer::write_sof11(
&mut output,
width as u16,
height as u16,
precision,
&components,
);
let dc_params: [(u8, u8); 2] = [(0u8, 1u8), (0, 1)];
let ac_params: [u8; 2] = [5u8, 5];
marker_writer::write_dac(&mut output, 2, &dc_params, 0, &ac_params);
let scan_components: Vec<(u8, u8)> = vec![
(1, 0), (2, 1), (3, 1), ];
marker_writer::write_sos_lossless(&mut output, &scan_components, predictor, point_transform);
output.extend_from_slice(arith_enc.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
struct CompLayout {
blocks_x: usize,
blocks_y: usize,
h_blocks: usize,
v_blocks: usize,
}
pub fn compress_progressive(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
) -> Result<Vec<u8>> {
use crate::encode::progressive::simple_progression;
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let num_components = if is_grayscale { 1 } else { 3 };
let scans = simple_progression(num_components);
compress_progressive_with_scans(
pixels,
width,
height,
pixel_format,
quality,
subsampling,
&scans,
)
}
pub fn compress_progressive_custom(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
script: &[ScanScript],
) -> Result<Vec<u8>> {
let scans: Vec<ProgressiveScan> = script
.iter()
.map(|s| ProgressiveScan {
component_indices: s.components.iter().map(|&c| c as usize).collect(),
ss: s.ss,
se: s.se,
ah: s.ah,
al: s.al,
})
.collect();
compress_progressive_with_scans(
pixels,
width,
height,
pixel_format,
quality,
subsampling,
&scans,
)
}
fn compress_progressive_with_scans(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
scans: &[ProgressiveScan],
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let enc_simd = crate::simd::detect_encoder();
let fdct_quantize_fn = enc_simd.fdct_quantize;
let luma_quant = tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x = width.div_ceil(mcu_w);
let mcus_y = height.div_ceil(mcu_h);
let (h_samp, v_samp) = if is_grayscale {
(1usize, 1usize)
} else {
let (h, v) = subsampling.sampling_factors();
(h as usize, v as usize)
};
let comp_layouts: Vec<CompLayout> = if is_grayscale {
vec![CompLayout {
blocks_x: mcus_x,
blocks_y: mcus_y,
h_blocks: 1,
v_blocks: 1,
}]
} else {
vec![
CompLayout {
blocks_x: mcus_x * h_samp,
blocks_y: mcus_y * v_samp,
h_blocks: h_samp,
v_blocks: v_samp,
},
CompLayout {
blocks_x: mcus_x,
blocks_y: mcus_y,
h_blocks: 1,
v_blocks: 1,
},
CompLayout {
blocks_x: mcus_x,
blocks_y: mcus_y,
h_blocks: 1,
v_blocks: 1,
},
]
};
let mut coeff_bufs: Vec<Vec<[i16; 64]>> = comp_layouts
.iter()
.map(|cl| vec![[0i16; 64]; cl.blocks_x * cl.blocks_y])
.collect();
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
let x0: usize = mcu_x * mcu_w;
let y0: usize = mcu_y * mcu_h;
if is_grayscale {
let bx: usize = mcu_x;
let by: usize = mcu_y;
progressive_fdct_y_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
fdct_quantize_fn,
&mut coeff_bufs[0][by * mcus_x + bx],
);
} else {
let blocks_x: usize = comp_layouts[0].blocks_x;
for bv in 0..v_samp {
for bh in 0..h_samp {
let bx: usize = mcu_x * h_samp + bh;
let by: usize = mcu_y * v_samp + bv;
progressive_fdct_y_block(
&y_plane,
width,
height,
x0 + bh * 8,
y0 + bv * 8,
&luma_divisors,
fdct_quantize_fn,
&mut coeff_bufs[0][by * blocks_x + bx],
);
}
}
for (comp_idx, plane) in [(1usize, &cb_plane), (2usize, &cr_plane)] {
let bx: usize = mcu_x;
let by: usize = mcu_y;
progressive_fdct_chroma_block(
plane,
width,
height,
x0,
y0,
h_samp,
v_samp,
&chroma_divisors,
fdct_quantize_fn,
&mut coeff_bufs[comp_idx][by * mcus_x + bx],
);
}
}
}
}
let dc_luma_table = build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_luma_table = build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let dc_chroma_table =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let ac_chroma_table =
build_huff_table(&tables::AC_CHROMINANCE_BITS, &tables::AC_CHROMINANCE_VALUES);
let mut output = Vec::with_capacity(width * height * 2);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof2(&mut output, width as u16, height as u16, &components);
} else {
let components = vec![
(1, h_samp as u8, v_samp as u8, 0),
(2, 1, 1, 1),
(3, 1, 1, 1),
];
marker_writer::write_sof2(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
if !is_grayscale {
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
1,
&tables::AC_CHROMINANCE_BITS,
&tables::AC_CHROMINANCE_VALUES,
);
}
for scan in scans {
let sos_comps: Vec<(u8, u8, u8)> = scan
.component_indices
.iter()
.map(|&ci| {
let comp_id = (ci + 1) as u8;
let (dc_tbl, ac_tbl) = if ci == 0 { (0, 0) } else { (1, 1) };
(comp_id, dc_tbl, ac_tbl)
})
.collect();
marker_writer::write_sos_progressive(
&mut output,
&sos_comps,
scan.ss,
scan.se,
scan.ah,
scan.al,
);
let mut bit_writer = BitWriter::new(width * height / 4);
if scan.ss == 0 && scan.se == 0 {
encode_progressive_dc_scan(
&coeff_bufs,
&comp_layouts,
scan,
mcus_x,
mcus_y,
&dc_luma_table,
&dc_chroma_table,
&mut bit_writer,
);
} else {
encode_progressive_ac_scan(
&coeff_bufs,
&comp_layouts,
scan,
mcus_x,
mcus_y,
&ac_luma_table,
&ac_chroma_table,
&mut bit_writer,
);
}
bit_writer.flush();
output.extend_from_slice(bit_writer.data());
}
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_arithmetic(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
) -> Result<Vec<u8>> {
use crate::encode::arithmetic::ArithEncoder;
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let enc_simd = crate::simd::detect_encoder();
let luma_quant = tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x = width.div_ceil(mcu_w);
let mcus_y = height.div_ceil(mcu_h);
let fdct_quantize_fn = crate::simd::detect_encoder().fdct_quantize;
let mut all_blocks: Vec<[i16; 64]> = Vec::new();
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0 = mcu_col * mcu_w;
let y0 = mcu_row * mcu_h;
if is_grayscale {
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0, y0, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &luma_divisors, &mut q);
all_blocks.push(q);
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => {
for (plane, divisors) in [
(&y_plane, &luma_divisors),
(&cb_plane, &chroma_divisors),
(&cr_plane, &chroma_divisors),
] {
let mut block = [0i16; 64];
extract_block(plane, width, height, x0, y0, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, divisors, &mut q);
all_blocks.push(q);
}
}
Subsampling::S422 => {
for dx in [0, 8] {
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0 + dx, y0, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &luma_divisors, &mut q);
all_blocks.push(q);
}
for plane in [&cb_plane, &cr_plane] {
let mut block = [0i16; 64];
downsample_chroma_block(plane, width, height, x0, y0, 2, 1, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &chroma_divisors, &mut q);
all_blocks.push(q);
}
}
Subsampling::S420 => {
for (dx, dy) in [(0, 0), (8, 0), (0, 8), (8, 8)] {
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0 + dx, y0 + dy, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &luma_divisors, &mut q);
all_blocks.push(q);
}
for plane in [&cb_plane, &cr_plane] {
let mut block = [0i16; 64];
downsample_chroma_block(plane, width, height, x0, y0, 2, 2, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &chroma_divisors, &mut q);
all_blocks.push(q);
}
}
Subsampling::S440 => {
for dy in [0, 8] {
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0, y0 + dy, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &luma_divisors, &mut q);
all_blocks.push(q);
}
for plane in [&cb_plane, &cr_plane] {
let mut block = [0i16; 64];
downsample_chroma_block(plane, width, height, x0, y0, 1, 2, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &chroma_divisors, &mut q);
all_blocks.push(q);
}
}
Subsampling::S411 => {
for dx in [0, 8, 16, 24] {
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0 + dx, y0, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &luma_divisors, &mut q);
all_blocks.push(q);
}
for plane in [&cb_plane, &cr_plane] {
let mut block = [0i16; 64];
downsample_chroma_block(plane, width, height, x0, y0, 4, 1, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &chroma_divisors, &mut q);
all_blocks.push(q);
}
}
Subsampling::S441 => {
for dy in [0, 8, 16, 24] {
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0, y0 + dy, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &luma_divisors, &mut q);
all_blocks.push(q);
}
for plane in [&cb_plane, &cr_plane] {
let mut block = [0i16; 64];
downsample_chroma_block(plane, width, height, x0, y0, 1, 4, &mut block);
let mut q = [0i16; 64];
fdct_quantize_fn(&mut block, &chroma_divisors, &mut q);
all_blocks.push(q);
}
}
}
}
}
}
let mut arith_enc = ArithEncoder::new(width * height);
let mut block_idx = 0;
for _mcu_row in 0..mcus_y {
for _mcu_col in 0..mcus_x {
if is_grayscale {
arith_enc.encode_dc_sequential(&all_blocks[block_idx], 0, 0);
arith_enc.encode_ac_sequential(&all_blocks[block_idx], 0);
block_idx += 1;
} else {
let y_blocks = match subsampling {
Subsampling::S444 | Subsampling::Unknown => 1,
Subsampling::S422 => 2,
Subsampling::S420 => 4,
Subsampling::S440 => 2,
Subsampling::S411 | Subsampling::S441 => 4,
};
for _ in 0..y_blocks {
arith_enc.encode_dc_sequential(&all_blocks[block_idx], 0, 0);
arith_enc.encode_ac_sequential(&all_blocks[block_idx], 0);
block_idx += 1;
}
arith_enc.encode_dc_sequential(&all_blocks[block_idx], 1, 1);
arith_enc.encode_ac_sequential(&all_blocks[block_idx], 1);
block_idx += 1;
arith_enc.encode_dc_sequential(&all_blocks[block_idx], 2, 1);
arith_enc.encode_ac_sequential(&all_blocks[block_idx], 1);
block_idx += 1;
}
}
}
arith_enc.finish();
let mut output = Vec::with_capacity(arith_enc.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof9(&mut output, width as u16, height as u16, &components);
} else {
let (h_samp, v_samp) = subsampling.sampling_factors();
let components = vec![(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1)];
marker_writer::write_sof9(&mut output, width as u16, height as u16, &components);
}
let dc_params = [(0u8, 1u8), (0, 1)];
let ac_params = [5u8, 5];
let num_dc = if is_grayscale { 1 } else { 2 };
let num_ac = if is_grayscale { 1 } else { 2 };
marker_writer::write_dac(&mut output, num_dc, &dc_params, num_ac, &ac_params);
if is_grayscale {
let scan_components = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components = vec![(1, 0, 0), (2, 1, 1), (3, 1, 1)];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(arith_enc.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_arithmetic_progressive(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
) -> Result<Vec<u8>> {
use crate::encode::arithmetic::ArithEncoder;
use crate::encode::progressive::simple_progression;
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp: usize = pixel_format.bytes_per_pixel();
let expected_size: usize = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
let is_grayscale: bool = pixel_format == PixelFormat::Grayscale;
let num_components: usize = if is_grayscale { 1 } else { 3 };
let enc_simd = crate::simd::detect_encoder();
let luma_quant: [u16; 64] =
tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant: [u16; 64] =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors: QuantDivisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors: QuantDivisors = scale_quant_for_fdct(&chroma_quant);
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h): (usize, usize) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x: usize = width.div_ceil(mcu_w);
let mcus_y: usize = height.div_ceil(mcu_h);
let (h_samp, v_samp): (usize, usize) = if is_grayscale {
(1, 1)
} else {
let (h, v) = subsampling.sampling_factors();
(h as usize, v as usize)
};
let comp_layouts: Vec<CompLayout> = if is_grayscale {
vec![CompLayout {
blocks_x: mcus_x,
blocks_y: mcus_y,
h_blocks: 1,
v_blocks: 1,
}]
} else {
vec![
CompLayout {
blocks_x: mcus_x * h_samp,
blocks_y: mcus_y * v_samp,
h_blocks: h_samp,
v_blocks: v_samp,
},
CompLayout {
blocks_x: mcus_x,
blocks_y: mcus_y,
h_blocks: 1,
v_blocks: 1,
},
CompLayout {
blocks_x: mcus_x,
blocks_y: mcus_y,
h_blocks: 1,
v_blocks: 1,
},
]
};
let mut coeff_bufs: Vec<Vec<[i16; 64]>> = comp_layouts
.iter()
.map(|cl| vec![[0i16; 64]; cl.blocks_x * cl.blocks_y])
.collect();
let fdct_quantize_fn = crate::simd::detect_encoder().fdct_quantize;
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
let x0: usize = mcu_x * mcu_w;
let y0: usize = mcu_y * mcu_h;
if is_grayscale {
let bx: usize = mcu_x;
let by: usize = mcu_y;
let mut block = [0i16; 64];
extract_block(&y_plane, width, height, x0, y0, &mut block);
fdct_quantize_fn(
&mut block,
&luma_divisors,
&mut coeff_bufs[0][by * mcus_x + bx],
);
} else {
for bv in 0..v_samp {
for bh in 0..h_samp {
let bx: usize = mcu_x * h_samp + bh;
let by: usize = mcu_y * v_samp + bv;
let mut block = [0i16; 64];
extract_block(
&y_plane,
width,
height,
x0 + bh * 8,
y0 + bv * 8,
&mut block,
);
let blocks_x: usize = comp_layouts[0].blocks_x;
fdct_quantize_fn(
&mut block,
&luma_divisors,
&mut coeff_bufs[0][by * blocks_x + bx],
);
}
}
{
let bx: usize = mcu_x;
let by: usize = mcu_y;
let mut block = [0i16; 64];
let hf: usize = if h_samp > 1 { 2 } else { 1 };
let vf: usize = if v_samp > 1 { 2 } else { 1 };
if hf == 1 && vf == 1 {
extract_block(&cb_plane, width, height, x0, y0, &mut block);
} else {
downsample_chroma_block(
&cb_plane, width, height, x0, y0, hf, vf, &mut block,
);
}
fdct_quantize_fn(
&mut block,
&chroma_divisors,
&mut coeff_bufs[1][by * mcus_x + bx],
);
}
{
let bx: usize = mcu_x;
let by: usize = mcu_y;
let mut block = [0i16; 64];
let hf: usize = if h_samp > 1 { 2 } else { 1 };
let vf: usize = if v_samp > 1 { 2 } else { 1 };
if hf == 1 && vf == 1 {
extract_block(&cr_plane, width, height, x0, y0, &mut block);
} else {
downsample_chroma_block(
&cr_plane, width, height, x0, y0, hf, vf, &mut block,
);
}
fdct_quantize_fn(
&mut block,
&chroma_divisors,
&mut coeff_bufs[2][by * mcus_x + bx],
);
}
}
}
}
let scans = simple_progression(num_components);
let mut output: Vec<u8> = Vec::with_capacity(width * height * 2);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof10(&mut output, width as u16, height as u16, &components);
} else {
let components = vec![
(1, h_samp as u8, v_samp as u8, 0),
(2, 1, 1, 1),
(3, 1, 1, 1),
];
marker_writer::write_sof10(&mut output, width as u16, height as u16, &components);
}
let dc_params: [(u8, u8); 2] = [(0u8, 1u8), (0, 1)];
let ac_params: [u8; 2] = [5u8, 5];
let num_dc: usize = if is_grayscale { 1 } else { 2 };
let num_ac: usize = if is_grayscale { 1 } else { 2 };
marker_writer::write_dac(&mut output, num_dc, &dc_params, num_ac, &ac_params);
let mut arith_enc: ArithEncoder = ArithEncoder::new(width * height / 4);
for scan in &scans {
arith_enc.reset();
let sos_comps: Vec<(u8, u8, u8)> = scan
.component_indices
.iter()
.map(|&ci| {
let comp_id: u8 = (ci + 1) as u8;
let (dc_tbl, ac_tbl): (u8, u8) = if ci == 0 { (0, 0) } else { (1, 1) };
(comp_id, dc_tbl, ac_tbl)
})
.collect();
marker_writer::write_sos_progressive(
&mut output,
&sos_comps,
scan.ss,
scan.se,
scan.ah,
scan.al,
);
let is_dc_scan: bool = scan.ss == 0 && scan.se == 0;
if is_dc_scan {
if scan.ah == 0 {
encode_arith_dc_first_scan(
&coeff_bufs,
&comp_layouts,
scan,
mcus_x,
mcus_y,
&mut arith_enc,
);
} else {
encode_arith_dc_refine_scan(
&coeff_bufs,
&comp_layouts,
scan,
mcus_x,
mcus_y,
&mut arith_enc,
);
}
} else if scan.ah == 0 {
encode_arith_ac_first_scan(
&coeff_bufs,
&comp_layouts,
scan,
mcus_x,
mcus_y,
&mut arith_enc,
);
} else {
encode_arith_ac_refine_scan(
&coeff_bufs,
&comp_layouts,
scan,
mcus_x,
mcus_y,
&mut arith_enc,
);
}
arith_enc.finish();
output.extend_from_slice(arith_enc.data());
}
marker_writer::write_eoi(&mut output);
Ok(output)
}
fn encode_arith_dc_first_scan(
coeff_bufs: &[Vec<[i16; 64]>],
comp_layouts: &[CompLayout],
scan: &crate::encode::progressive::ProgressiveScan,
mcus_x: usize,
mcus_y: usize,
arith_enc: &mut crate::encode::arithmetic::ArithEncoder,
) {
let al: u8 = scan.al;
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
for &ci in &scan.component_indices {
let layout: &CompLayout = &comp_layouts[ci];
let dc_tbl: usize = if ci == 0 { 0 } else { 1 };
for bv in 0..layout.v_blocks {
for bh in 0..layout.h_blocks {
let bx: usize = mcu_x * layout.h_blocks + bh;
let by: usize = mcu_y * layout.v_blocks + bv;
let block: &[i16; 64] = &coeff_bufs[ci][by * layout.blocks_x + bx];
arith_enc.encode_dc_first(block, ci, dc_tbl, al);
}
}
}
}
}
}
fn encode_arith_dc_refine_scan(
coeff_bufs: &[Vec<[i16; 64]>],
comp_layouts: &[CompLayout],
scan: &crate::encode::progressive::ProgressiveScan,
mcus_x: usize,
mcus_y: usize,
arith_enc: &mut crate::encode::arithmetic::ArithEncoder,
) {
let al: u8 = scan.al;
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
for &ci in &scan.component_indices {
let layout: &CompLayout = &comp_layouts[ci];
for bv in 0..layout.v_blocks {
for bh in 0..layout.h_blocks {
let bx: usize = mcu_x * layout.h_blocks + bh;
let by: usize = mcu_y * layout.v_blocks + bv;
let block: &[i16; 64] = &coeff_bufs[ci][by * layout.blocks_x + bx];
arith_enc.encode_dc_refine(block, al);
}
}
}
}
}
}
fn encode_arith_ac_first_scan(
coeff_bufs: &[Vec<[i16; 64]>],
comp_layouts: &[CompLayout],
scan: &crate::encode::progressive::ProgressiveScan,
mcus_x: usize,
mcus_y: usize,
arith_enc: &mut crate::encode::arithmetic::ArithEncoder,
) {
let ci: usize = scan.component_indices[0]; let layout: &CompLayout = &comp_layouts[ci];
let ac_tbl: usize = if ci == 0 { 0 } else { 1 };
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
for bv in 0..layout.v_blocks {
for bh in 0..layout.h_blocks {
let bx: usize = mcu_x * layout.h_blocks + bh;
let by: usize = mcu_y * layout.v_blocks + bv;
let block: &[i16; 64] = &coeff_bufs[ci][by * layout.blocks_x + bx];
arith_enc.encode_ac_first(block, ac_tbl, scan.ss, scan.se, scan.al);
}
}
}
}
}
fn encode_arith_ac_refine_scan(
coeff_bufs: &[Vec<[i16; 64]>],
comp_layouts: &[CompLayout],
scan: &crate::encode::progressive::ProgressiveScan,
mcus_x: usize,
mcus_y: usize,
arith_enc: &mut crate::encode::arithmetic::ArithEncoder,
) {
let ci: usize = scan.component_indices[0]; let layout: &CompLayout = &comp_layouts[ci];
let ac_tbl: usize = if ci == 0 { 0 } else { 1 };
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
for bv in 0..layout.v_blocks {
for bh in 0..layout.h_blocks {
let bx: usize = mcu_x * layout.h_blocks + bh;
let by: usize = mcu_y * layout.v_blocks + bv;
let block: &[i16; 64] = &coeff_bufs[ci][by * layout.blocks_x + bx];
arith_enc.encode_ac_refine(block, ac_tbl, scan.ss, scan.se, scan.al, scan.ah);
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn encode_progressive_dc_scan(
coeff_bufs: &[Vec<[i16; 64]>],
comp_layouts: &[CompLayout],
scan: &crate::encode::progressive::ProgressiveScan,
mcus_x: usize,
mcus_y: usize,
dc_luma_table: &HuffTable,
dc_chroma_table: &HuffTable,
writer: &mut BitWriter,
) {
let al = scan.al;
let ah = scan.ah;
let mut prev_dc = vec![0i16; scan.component_indices.len()];
for mcu_y in 0..mcus_y {
for mcu_x in 0..mcus_x {
for (scan_ci, &ci) in scan.component_indices.iter().enumerate() {
let layout = &comp_layouts[ci];
let dc_table = if ci == 0 {
dc_luma_table
} else {
dc_chroma_table
};
for bv in 0..layout.v_blocks {
for bh in 0..layout.h_blocks {
let bx = mcu_x * layout.h_blocks + bh;
let by = mcu_y * layout.v_blocks + bv;
let block = &coeff_bufs[ci][by * layout.blocks_x + bx];
if ah == 0 {
let dc: i16 = block[0] >> al;
let diff: i16 = dc - prev_dc[scan_ci];
prev_dc[scan_ci] = dc;
if diff == 0 {
writer.write_bits(dc_table.ehufco[0], dc_table.ehufsi[0]);
} else {
let abs_diff: u16 = diff.unsigned_abs();
let category: u8 = 16 - abs_diff.leading_zeros() as u8;
let magnitude: u16 = if diff > 0 { diff as u16 } else { !abs_diff };
let huff_code: u32 = dc_table.ehufco[category as usize] as u32;
let huff_size: u8 = dc_table.ehufsi[category as usize];
let mag_masked: u32 = magnitude as u32 & ((1u32 << category) - 1);
let combined: u32 = (huff_code << category) | mag_masked;
writer.put_bits(combined, huff_size + category);
}
} else {
let bit: u32 = ((block[0] >> al) & 1) as u32;
writer.put_bits(bit, 1);
}
}
}
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn encode_progressive_ac_scan(
coeff_bufs: &[Vec<[i16; 64]>],
comp_layouts: &[CompLayout],
scan: &crate::encode::progressive::ProgressiveScan,
_mcus_x: usize,
_mcus_y: usize,
ac_luma_table: &HuffTable,
ac_chroma_table: &HuffTable,
writer: &mut BitWriter,
) {
let ci = scan.component_indices[0]; let _layout = &comp_layouts[ci];
let ac_table = if ci == 0 {
ac_luma_table
} else {
ac_chroma_table
};
let ss = scan.ss as usize;
let se = scan.se as usize;
let al = scan.al;
let ah = scan.ah;
let blocks: &[[i16; 64]] = &coeff_bufs[ci];
if ah == 0 {
for block in blocks.iter() {
encode_ac_first_block(block, ss, se, al, ac_table, writer);
}
} else {
for block in blocks.iter() {
encode_ac_refine_block(block, ss, se, al, ac_table, writer);
}
}
}
fn encode_ac_first_block(
block: &[i16; 64],
ss: usize,
se: usize,
al: u8,
ac_table: &HuffTable,
writer: &mut BitWriter,
) {
let band_len: usize = se - ss + 1;
let mut values = [0u16; 64]; let mut diffs = [0u16; 64]; let mut zerobits: u64 = 0;
for i in 0..band_len {
let coeff: i16 = block[ss + i];
if coeff == 0 {
continue;
}
let sign_mask: i16 = coeff >> 15;
let abs_coeff: i16 = (coeff ^ sign_mask) - sign_mask;
let temp: u16 = (abs_coeff >> al) as u16;
if temp == 0 {
continue;
}
values[i] = temp;
diffs[i] = (sign_mask ^ (abs_coeff >> al)) as u16;
zerobits |= 1u64 << i;
}
if zerobits == 0 {
writer.put_bits(ac_table.ehufco[0x00] as u32, ac_table.ehufsi[0x00]);
return;
}
let mut nbits_arr = [0u8; 64];
{
let mut bits: u64 = zerobits;
while bits != 0 {
let pos: usize = bits.trailing_zeros() as usize;
bits &= bits - 1;
nbits_arr[pos] = 16 - values[pos].leading_zeros() as u8;
}
}
let mut prev_pos: usize = 0;
while zerobits != 0 {
let pos: usize = zerobits.trailing_zeros() as usize;
zerobits &= zerobits - 1;
let mut zero_run: usize = pos - prev_pos;
while zero_run >= 16 {
writer.put_bits(ac_table.ehufco[0xF0] as u32, ac_table.ehufsi[0xF0]);
zero_run -= 16;
}
let nbits: u8 = nbits_arr[pos];
let symbol: usize = (zero_run << 4) | (nbits as usize);
let huff_code: u32 = ac_table.ehufco[symbol] as u32;
let huff_size: u8 = ac_table.ehufsi[symbol];
let mag_masked: u32 = diffs[pos] as u32 & ((1u32 << nbits) - 1);
let combined: u32 = (huff_code << nbits) | mag_masked;
writer.put_bits(combined, huff_size + nbits);
prev_pos = pos + 1;
}
if prev_pos < band_len {
writer.put_bits(ac_table.ehufco[0x00] as u32, ac_table.ehufsi[0x00]);
}
}
#[inline]
fn flush_corr_bits(writer: &mut BitWriter, corr_bits: &mut u64, corr_len: &mut u8) {
if *corr_len == 0 {
return;
}
if *corr_len <= 32 {
writer.put_bits(*corr_bits as u32, *corr_len);
} else {
let hi_len: u8 = *corr_len - 32;
writer.put_bits((*corr_bits >> 32) as u32, hi_len);
writer.put_bits(*corr_bits as u32, 32);
}
*corr_bits = 0;
*corr_len = 0;
}
fn encode_ac_refine_block(
block: &[i16; 64],
ss: usize,
se: usize,
al: u8,
ac_table: &HuffTable,
writer: &mut BitWriter,
) {
let band_len: usize = se - ss + 1;
let mut absvals = [0u16; 64];
let mut sign_bits = [0u16; 64];
let mut eob: usize = 0;
for i in 0..band_len {
let coeff: i32 = block[ss + i] as i32;
let sign_mask: i32 = coeff >> 31;
let abs_coeff: i32 = (coeff ^ sign_mask) - sign_mask;
let temp: u16 = (abs_coeff >> al) as u16;
absvals[i] = temp;
sign_bits[i] = (sign_mask as u16).wrapping_add(1);
if temp == 1 {
eob = i + 1; }
}
let mut r: usize = 0;
let mut corr_bits: u64 = 0; let mut corr_len: u8 = 0; let mut idx: usize = 0;
while idx < band_len {
let temp: u16 = absvals[idx];
if temp == 0 {
r += 1;
idx += 1;
continue;
}
while r > 15 && idx < eob {
writer.put_bits(ac_table.ehufco[0xF0] as u32, ac_table.ehufsi[0xF0]);
r -= 16;
flush_corr_bits(writer, &mut corr_bits, &mut corr_len);
}
if temp > 1 {
corr_bits = (corr_bits << 1) | (temp & 1) as u64;
corr_len += 1;
idx += 1;
continue;
}
let symbol: usize = (r << 4) | 1;
let huff_code: u32 = ac_table.ehufco[symbol] as u32;
let huff_size: u8 = ac_table.ehufsi[symbol];
let combined: u32 = (huff_code << 1) | sign_bits[idx] as u32;
writer.put_bits(combined, huff_size + 1);
flush_corr_bits(writer, &mut corr_bits, &mut corr_len);
r = 0;
idx += 1;
}
if r > 0 || corr_len > 0 {
writer.put_bits(ac_table.ehufco[0x00] as u32, ac_table.ehufsi[0x00]);
flush_corr_bits(writer, &mut corr_bits, &mut corr_len);
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn progressive_fdct_y_block(
plane: &[u8],
plane_w: usize,
plane_h: usize,
bx: usize,
by: usize,
quant: &QuantDivisors,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
output: &mut [i16; 64],
) {
#[cfg(target_arch = "aarch64")]
{
if bx + 8 <= plane_w && by + 8 <= plane_h {
unsafe {
crate::simd::aarch64::neon_extract_fdct_quantize(
plane.as_ptr().add(by * plane_w + bx),
plane_w,
quant,
output,
);
}
return;
}
}
let mut block = [0i16; 64];
extract_block(plane, plane_w, plane_h, bx, by, &mut block);
fdct_quantize_fn(&mut block, quant, output);
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn progressive_fdct_chroma_block(
plane: &[u8],
plane_w: usize,
plane_h: usize,
x0: usize,
y0: usize,
h_samp: usize,
v_samp: usize,
quant: &QuantDivisors,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
output: &mut [i16; 64],
) {
let hf: usize = if h_samp > 1 { 2 } else { 1 };
let vf: usize = if v_samp > 1 { 2 } else { 1 };
if hf == 1 && vf == 1 {
progressive_fdct_y_block(
plane,
plane_w,
plane_h,
x0,
y0,
quant,
fdct_quantize_fn,
output,
);
return;
}
#[cfg(target_arch = "aarch64")]
{
let src_w: usize = hf * 8;
let src_h: usize = vf * 8;
if x0 + src_w <= plane_w && y0 + src_h <= plane_h {
unsafe {
let ptr: *const u8 = plane.as_ptr().add(y0 * plane_w + x0);
if hf == 2 && vf == 2 {
crate::simd::aarch64::neon_downsample_h2v2_fdct_quantize(
ptr, plane_w, quant, output,
);
} else if hf == 2 && vf == 1 {
crate::simd::aarch64::neon_downsample_h2v1_fdct_quantize(
ptr, plane_w, quant, output,
);
} else {
let mut block = [0i16; 64];
downsample_chroma_block(plane, plane_w, plane_h, x0, y0, hf, vf, &mut block);
fdct_quantize_fn(&mut block, quant, output);
}
}
return;
}
}
let mut block = [0i16; 64];
downsample_chroma_block(plane, plane_w, plane_h, x0, y0, hf, vf, &mut block);
fdct_quantize_fn(&mut block, quant, output);
}
fn scale_quant_for_fdct(quant_table: &[u16; 64]) -> QuantDivisors {
let mut divisors = [0u16; 64];
let mut reciprocals = [0u16; 64];
for i in 0..64 {
let d: u32 = quant_table[i] as u32 * 8;
divisors[i] = d as u16;
reciprocals[i] = (1u32 << 16).div_ceil(d) as u16;
}
let zigzag = &crate::encode::tables::ZIGZAG_ORDER;
let mut divisors_zigzag = [0u16; 64];
let mut reciprocals_zigzag = [0u16; 64];
for zz in 0..64 {
divisors_zigzag[zz] = divisors[zigzag[zz]];
reciprocals_zigzag[zz] = reciprocals[zigzag[zz]];
}
QuantDivisors {
divisors,
reciprocals,
divisors_zigzag,
reciprocals_zigzag,
}
}
#[allow(clippy::type_complexity)]
fn convert_to_ycbcr(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
rgb_to_ycbcr_row_fn: fn(&[u8], &mut [u8], &mut [u8], &mut [u8], usize),
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let plane_size = width * height;
let mut y_plane = vec![0u8; plane_size];
let mut cb_plane = vec![0u8; plane_size];
let mut cr_plane = vec![0u8; plane_size];
let bpp = pixel_format.bytes_per_pixel();
match pixel_format {
PixelFormat::Grayscale => {
y_plane.copy_from_slice(&pixels[..plane_size]);
}
PixelFormat::Rgb => {
for row in 0..height {
let src_offset = row * width * bpp;
let dst_offset = row * width;
rgb_to_ycbcr_row_fn(
&pixels[src_offset..src_offset + width * bpp],
&mut y_plane[dst_offset..dst_offset + width],
&mut cb_plane[dst_offset..dst_offset + width],
&mut cr_plane[dst_offset..dst_offset + width],
width,
);
}
}
PixelFormat::Rgba => {
for row in 0..height {
let src_offset = row * width * bpp;
let dst_offset = row * width;
color::rgba_to_ycbcr_row(
&pixels[src_offset..src_offset + width * bpp],
&mut y_plane[dst_offset..dst_offset + width],
&mut cb_plane[dst_offset..dst_offset + width],
&mut cr_plane[dst_offset..dst_offset + width],
width,
);
}
}
PixelFormat::Bgr => {
let mut rgb_row = vec![0u8; width * 3];
for row in 0..height {
let src_offset = row * width * bpp;
let dst_offset = row * width;
for col in 0..width {
rgb_row[col * 3] = pixels[src_offset + col * 3 + 2]; rgb_row[col * 3 + 1] = pixels[src_offset + col * 3 + 1]; rgb_row[col * 3 + 2] = pixels[src_offset + col * 3]; }
color::rgb_to_ycbcr_row(
&rgb_row,
&mut y_plane[dst_offset..dst_offset + width],
&mut cb_plane[dst_offset..dst_offset + width],
&mut cr_plane[dst_offset..dst_offset + width],
width,
);
}
}
PixelFormat::Bgra => {
let mut rgb_row = vec![0u8; width * 3];
for row in 0..height {
let src_offset = row * width * bpp;
let dst_offset = row * width;
for col in 0..width {
rgb_row[col * 3] = pixels[src_offset + col * 4 + 2]; rgb_row[col * 3 + 1] = pixels[src_offset + col * 4 + 1]; rgb_row[col * 3 + 2] = pixels[src_offset + col * 4]; }
color::rgb_to_ycbcr_row(
&rgb_row,
&mut y_plane[dst_offset..dst_offset + width],
&mut cb_plane[dst_offset..dst_offset + width],
&mut cr_plane[dst_offset..dst_offset + width],
width,
);
}
}
PixelFormat::Rgbx
| PixelFormat::Bgrx
| PixelFormat::Xrgb
| PixelFormat::Xbgr
| PixelFormat::Argb
| PixelFormat::Abgr => {
let r_off: usize = pixel_format.red_offset().unwrap();
let g_off: usize = pixel_format.green_offset().unwrap();
let b_off: usize = pixel_format.blue_offset().unwrap();
for row in 0..height {
let src_offset: usize = row * width * bpp;
let dst_offset: usize = row * width;
color::generic_to_ycbcr_row(
&pixels[src_offset..src_offset + width * bpp],
&mut y_plane[dst_offset..dst_offset + width],
&mut cb_plane[dst_offset..dst_offset + width],
&mut cr_plane[dst_offset..dst_offset + width],
width,
bpp,
r_off,
g_off,
b_off,
);
}
}
PixelFormat::Cmyk => {
return Err(JpegError::Unsupported(
"CMYK pixel format not supported for encoding".to_string(),
));
}
PixelFormat::Rgb565 => {
return Err(JpegError::Unsupported(
"Rgb565 pixel format is decode-only and not supported for encoding".to_string(),
));
}
}
Ok((y_plane, cb_plane, cr_plane))
}
fn extract_block(
plane: &[u8],
plane_width: usize,
plane_height: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
if block_x + 8 <= plane_width && block_y + 8 <= plane_height {
#[cfg(target_arch = "aarch64")]
{
extract_block_neon(plane, plane_width, block_x, block_y, block);
return;
}
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("sse2") {
unsafe {
extract_block_sse2(plane, plane_width, block_x, block_y, block);
}
return;
}
}
}
for row in 0..8 {
let src_y: usize = (block_y + row).min(plane_height - 1);
for col in 0..8 {
let src_x: usize = (block_x + col).min(plane_width - 1);
block[row * 8 + col] = plane[src_y * plane_width + src_x] as i16 - 128;
}
}
}
#[cfg(target_arch = "aarch64")]
fn extract_block_neon(
plane: &[u8],
plane_width: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
use std::arch::aarch64::*;
unsafe {
let level_shift: int16x8_t = vdupq_n_s16(128);
for row in 0..8 {
let src_ptr: *const u8 = plane.as_ptr().add((block_y + row) * plane_width + block_x);
let pixels: uint8x8_t = vld1_u8(src_ptr);
let wide: int16x8_t = vreinterpretq_s16_u16(vmovl_u8(pixels));
let shifted: int16x8_t = vsubq_s16(wide, level_shift);
vst1q_s16(block.as_mut_ptr().add(row * 8), shifted);
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse2")]
unsafe fn extract_block_sse2(
plane: &[u8],
plane_width: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
use core::arch::x86_64::*;
let level_shift: __m128i = _mm_set1_epi16(128);
let zeros: __m128i = _mm_setzero_si128();
for row in 0..8 {
let src_ptr: *const u8 = plane.as_ptr().add((block_y + row) * plane_width + block_x);
let pixels: __m128i = _mm_loadl_epi64(src_ptr as *const __m128i);
let wide: __m128i = _mm_unpacklo_epi8(pixels, zeros);
let shifted: __m128i = _mm_sub_epi16(wide, level_shift);
_mm_storeu_si128(block.as_mut_ptr().add(row * 8) as *mut __m128i, shifted);
}
}
#[allow(clippy::too_many_arguments)]
fn downsample_chroma_block(
plane: &[u8],
plane_width: usize,
plane_height: usize,
block_x: usize,
block_y: usize,
h_factor: usize,
v_factor: usize,
block: &mut [i16; 64],
) {
{
let src_w: usize = 8 * h_factor;
let src_h: usize = 8 * v_factor;
if block_x + src_w <= plane_width && block_y + src_h <= plane_height {
#[cfg(target_arch = "aarch64")]
{
if h_factor == 2 && v_factor == 2 {
downsample_chroma_block_h2v2_neon(plane, plane_width, block_x, block_y, block);
return;
}
if h_factor == 2 && v_factor == 1 {
downsample_chroma_block_h2v1_neon(plane, plane_width, block_x, block_y, block);
return;
}
}
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("ssse3") {
if h_factor == 2 && v_factor == 2 {
unsafe {
downsample_chroma_block_h2v2_ssse3(
plane,
plane_width,
block_x,
block_y,
block,
);
}
return;
}
if h_factor == 2 && v_factor == 1 {
unsafe {
downsample_chroma_block_h2v1_ssse3(
plane,
plane_width,
block_x,
block_y,
block,
);
}
return;
}
}
}
}
}
for row in 0..8 {
for col in 0..8 {
let mut sum: u32 = 0;
for dy in 0..v_factor {
for dx in 0..h_factor {
let sx = (block_x + col * h_factor + dx).min(plane_width - 1);
let sy = (block_y + row * v_factor + dy).min(plane_height - 1);
sum += plane[sy * plane_width + sx] as u32;
}
}
let avg = (sum + (h_factor * v_factor / 2) as u32) / (h_factor * v_factor) as u32;
block[row * 8 + col] = avg as i16 - 128;
}
}
}
#[cfg(target_arch = "aarch64")]
fn downsample_chroma_block_h2v2_neon(
plane: &[u8],
plane_width: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
use std::arch::aarch64::*;
unsafe {
let bias: uint16x8_t = vdupq_n_u16(2);
let level_shift: int16x8_t = vdupq_n_s16(128);
for row in 0..8 {
let sy: usize = block_y + row * 2;
let r0_ptr: *const u8 = plane.as_ptr().add(sy * plane_width + block_x);
let r1_ptr: *const u8 = plane.as_ptr().add((sy + 1) * plane_width + block_x);
let r0: uint8x16_t = vld1q_u8(r0_ptr);
let r1: uint8x16_t = vld1q_u8(r1_ptr);
let mut sum: uint16x8_t = vpadalq_u8(bias, r0);
sum = vpadalq_u8(sum, r1);
let avg_u8: uint8x8_t = vshrn_n_u16(sum, 2);
let avg_i16: int16x8_t = vreinterpretq_s16_u16(vmovl_u8(avg_u8));
let shifted: int16x8_t = vsubq_s16(avg_i16, level_shift);
vst1q_s16(block.as_mut_ptr().add(row * 8), shifted);
}
}
}
#[cfg(target_arch = "aarch64")]
fn downsample_chroma_block_h2v1_neon(
plane: &[u8],
plane_width: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
use std::arch::aarch64::*;
unsafe {
let bias: uint16x8_t = vdupq_n_u16(1);
let level_shift: int16x8_t = vdupq_n_s16(128);
for row in 0..8 {
let sy: usize = block_y + row;
let r_ptr: *const u8 = plane.as_ptr().add(sy * plane_width + block_x);
let r: uint8x16_t = vld1q_u8(r_ptr);
let sum: uint16x8_t = vpadalq_u8(bias, r);
let avg_u8: uint8x8_t = vshrn_n_u16(sum, 1);
let avg_i16: int16x8_t = vreinterpretq_s16_u16(vmovl_u8(avg_u8));
let shifted: int16x8_t = vsubq_s16(avg_i16, level_shift);
vst1q_s16(block.as_mut_ptr().add(row * 8), shifted);
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn downsample_chroma_block_h2v2_ssse3(
plane: &[u8],
plane_width: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
use core::arch::x86_64::*;
let ones: __m128i = _mm_set1_epi8(1);
let bias: __m128i = _mm_set1_epi16(2); let level_shift: __m128i = _mm_set1_epi16(128);
for row in 0..8 {
let sy: usize = block_y + row * 2;
let r0_ptr: *const u8 = plane.as_ptr().add(sy * plane_width + block_x);
let r1_ptr: *const u8 = plane.as_ptr().add((sy + 1) * plane_width + block_x);
let r0: __m128i = _mm_loadu_si128(r0_ptr as *const __m128i);
let r1: __m128i = _mm_loadu_si128(r1_ptr as *const __m128i);
let sum0: __m128i = _mm_maddubs_epi16(r0, ones);
let sum1: __m128i = _mm_maddubs_epi16(r1, ones);
let total: __m128i = _mm_add_epi16(_mm_add_epi16(sum0, sum1), bias);
let avg: __m128i = _mm_srai_epi16::<2>(total);
let shifted: __m128i = _mm_sub_epi16(avg, level_shift);
_mm_storeu_si128(block.as_mut_ptr().add(row * 8) as *mut __m128i, shifted);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "ssse3")]
unsafe fn downsample_chroma_block_h2v1_ssse3(
plane: &[u8],
plane_width: usize,
block_x: usize,
block_y: usize,
block: &mut [i16; 64],
) {
use core::arch::x86_64::*;
let ones: __m128i = _mm_set1_epi8(1);
let bias: __m128i = _mm_set1_epi16(1); let level_shift: __m128i = _mm_set1_epi16(128);
for row in 0..8 {
let sy: usize = block_y + row;
let r_ptr: *const u8 = plane.as_ptr().add(sy * plane_width + block_x);
let r: __m128i = _mm_loadu_si128(r_ptr as *const __m128i);
let sum: __m128i = _mm_add_epi16(_mm_maddubs_epi16(r, ones), bias);
let avg: __m128i = _mm_srai_epi16::<1>(sum);
let shifted: __m128i = _mm_sub_epi16(avg, level_shift);
_mm_storeu_si128(block.as_mut_ptr().add(row * 8) as *mut __m128i, shifted);
}
}
#[allow(clippy::too_many_arguments)]
fn encode_single_block(
plane: &[u8],
plane_width: usize,
plane_height: usize,
block_x: usize,
block_y: usize,
quant_table: &QuantDivisors,
dc_table: &HuffTable,
ac_table: &HuffTable,
writer: &mut BitWriter,
prev_dc: &mut i16,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
) {
let mut quantized = [0i16; 64];
if block_x + 8 <= plane_width && block_y + 8 <= plane_height {
#[cfg(target_arch = "aarch64")]
{
unsafe {
crate::simd::aarch64::neon_extract_fdct_quantize(
plane.as_ptr().add(block_y * plane_width + block_x),
plane_width,
quant_table,
&mut quantized,
);
}
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
return;
}
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
unsafe {
crate::simd::x86_64::avx2_extract_fdct_quantize(
plane.as_ptr().add(block_y * plane_width + block_x),
plane_width,
quant_table,
&mut quantized,
);
}
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
return;
}
}
}
let mut block = [0i16; 64];
extract_block(
plane,
plane_width,
plane_height,
block_x,
block_y,
&mut block,
);
fdct_quantize_fn(&mut block, quant_table, &mut quantized);
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
}
#[allow(clippy::too_many_arguments)]
fn encode_color_mcu(
y_plane: &[u8],
cb_plane: &[u8],
cr_plane: &[u8],
width: usize,
height: usize,
x0: usize,
y0: usize,
subsampling: Subsampling,
luma_quant: &QuantDivisors,
chroma_quant: &QuantDivisors,
dc_luma_table: &HuffTable,
ac_luma_table: &HuffTable,
dc_chroma_table: &HuffTable,
ac_chroma_table: &HuffTable,
writer: &mut BitWriter,
prev_dc_y: &mut i16,
prev_dc_cb: &mut i16,
prev_dc_cr: &mut i16,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
) {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => {
encode_single_block(
y_plane,
width,
height,
x0,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_single_block(
cb_plane,
width,
height,
x0,
y0,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cb,
fdct_quantize_fn,
);
encode_single_block(
cr_plane,
width,
height,
x0,
y0,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cr,
fdct_quantize_fn,
);
}
Subsampling::S422 => {
encode_single_block(
y_plane,
width,
height,
x0,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_single_block(
y_plane,
width,
height,
x0 + 8,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cb_plane,
width,
height,
x0,
y0,
2,
1,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cb,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cr_plane,
width,
height,
x0,
y0,
2,
1,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cr,
fdct_quantize_fn,
);
}
Subsampling::S420 => {
encode_single_block(
y_plane,
width,
height,
x0,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_single_block(
y_plane,
width,
height,
x0 + 8,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_single_block(
y_plane,
width,
height,
x0,
y0 + 8,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_single_block(
y_plane,
width,
height,
x0 + 8,
y0 + 8,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cb_plane,
width,
height,
x0,
y0,
2,
2,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cb,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cr_plane,
width,
height,
x0,
y0,
2,
2,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cr,
fdct_quantize_fn,
);
}
Subsampling::S440 => {
encode_single_block(
y_plane,
width,
height,
x0,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_single_block(
y_plane,
width,
height,
x0,
y0 + 8,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cb_plane,
width,
height,
x0,
y0,
1,
2,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cb,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cr_plane,
width,
height,
x0,
y0,
1,
2,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cr,
fdct_quantize_fn,
);
}
Subsampling::S411 => {
for i in 0..4 {
encode_single_block(
y_plane,
width,
height,
x0 + i * 8,
y0,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
}
encode_downsampled_chroma_block(
cb_plane,
width,
height,
x0,
y0,
4,
1,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cb,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cr_plane,
width,
height,
x0,
y0,
4,
1,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cr,
fdct_quantize_fn,
);
}
Subsampling::S441 => {
for i in 0..4 {
encode_single_block(
y_plane,
width,
height,
x0,
y0 + i * 8,
luma_quant,
dc_luma_table,
ac_luma_table,
writer,
prev_dc_y,
fdct_quantize_fn,
);
}
encode_downsampled_chroma_block(
cb_plane,
width,
height,
x0,
y0,
1,
4,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cb,
fdct_quantize_fn,
);
encode_downsampled_chroma_block(
cr_plane,
width,
height,
x0,
y0,
1,
4,
chroma_quant,
dc_chroma_table,
ac_chroma_table,
writer,
prev_dc_cr,
fdct_quantize_fn,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn encode_downsampled_chroma_block(
plane: &[u8],
plane_width: usize,
plane_height: usize,
block_x: usize,
block_y: usize,
h_factor: usize,
v_factor: usize,
quant_table: &QuantDivisors,
dc_table: &HuffTable,
ac_table: &HuffTable,
writer: &mut BitWriter,
prev_dc: &mut i16,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
) {
#[cfg(target_arch = "aarch64")]
{
let src_w: usize = 8 * h_factor;
let src_h: usize = 8 * v_factor;
if block_x + src_w <= plane_width && block_y + src_h <= plane_height {
let plane_ptr: *const u8 =
unsafe { plane.as_ptr().add(block_y * plane_width + block_x) };
let mut quantized = [0i16; 64];
if h_factor == 2 && v_factor == 2 {
unsafe {
crate::simd::aarch64::neon_downsample_h2v2_fdct_quantize(
plane_ptr,
plane_width,
quant_table,
&mut quantized,
);
}
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
return;
}
if h_factor == 2 && v_factor == 1 {
unsafe {
crate::simd::aarch64::neon_downsample_h2v1_fdct_quantize(
plane_ptr,
plane_width,
quant_table,
&mut quantized,
);
}
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
return;
}
}
}
#[cfg(target_arch = "x86_64")]
{
let src_w: usize = 8 * h_factor;
let src_h: usize = 8 * v_factor;
if is_x86_feature_detected!("avx2")
&& block_x + src_w <= plane_width
&& block_y + src_h <= plane_height
{
if h_factor == 2 && v_factor == 2 {
let mut quantized = [0i16; 64];
unsafe {
crate::simd::x86_64::avx2_downsample_h2v2_fdct_quantize(
plane.as_ptr().add(block_y * plane_width + block_x),
plane_width,
quant_table,
&mut quantized,
);
}
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
return;
}
let mut block = [0i16; 64];
let downsample_ok: bool = if h_factor == 2 && v_factor == 1 {
unsafe {
downsample_chroma_block_h2v1_ssse3(
plane,
plane_width,
block_x,
block_y,
&mut block,
);
}
true
} else {
false
};
if downsample_ok {
let mut quantized = [0i16; 64];
fdct_quantize_fn(&mut block, quant_table, &mut quantized);
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
return;
}
}
}
let mut block = [0i16; 64];
downsample_chroma_block(
plane,
plane_width,
plane_height,
block_x,
block_y,
h_factor,
v_factor,
&mut block,
);
let mut quantized = [0i16; 64];
fdct_quantize_fn(&mut block, quant_table, &mut quantized);
HuffmanEncoder::encode_block(writer, &quantized, prev_dc, dc_table, ac_table);
}
pub fn compress_optimized(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
subsampling: Subsampling,
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp = pixel_format.bytes_per_pixel();
let expected_size = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
let is_grayscale = pixel_format == PixelFormat::Grayscale;
let luma_quant = tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors = scale_quant_for_fdct(&chroma_quant);
let enc_simd = crate::simd::detect_encoder();
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let (mcu_w, mcu_h) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x = width.div_ceil(mcu_w);
let mcus_y = height.div_ceil(mcu_h);
use crate::encode::huff_opt;
let mut dc_luma_freq = [0u32; 257];
let mut dc_chroma_freq = [0u32; 257];
let mut ac_luma_freq = [0u32; 257];
let mut ac_chroma_freq = [0u32; 257];
let mut all_blocks: Vec<[i16; 64]> = Vec::new();
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0 = mcu_col * mcu_w;
let y0 = mcu_row * mcu_h;
if is_grayscale {
let q = gather_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = q[0] - prev_dc_y;
prev_dc_y = q[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&q, &mut ac_luma_freq);
all_blocks.push(q);
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => {
let yq = gather_block(
&y_plane,
width,
height,
x0,
y0,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = yq[0] - prev_dc_y;
prev_dc_y = yq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&yq, &mut ac_luma_freq);
all_blocks.push(yq);
let cbq = gather_block(
&cb_plane,
width,
height,
x0,
y0,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = cbq[0] - prev_dc_cb;
prev_dc_cb = cbq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&cbq, &mut ac_chroma_freq);
all_blocks.push(cbq);
let crq = gather_block(
&cr_plane,
width,
height,
x0,
y0,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = crq[0] - prev_dc_cr;
prev_dc_cr = crq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&crq, &mut ac_chroma_freq);
all_blocks.push(crq);
}
Subsampling::S422 => {
for dx in [0, 8] {
let yq = gather_block(
&y_plane,
width,
height,
x0 + dx,
y0,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = yq[0] - prev_dc_y;
prev_dc_y = yq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&yq, &mut ac_luma_freq);
all_blocks.push(yq);
}
let cbq = gather_downsampled_block(
&cb_plane,
width,
height,
x0,
y0,
2,
1,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = cbq[0] - prev_dc_cb;
prev_dc_cb = cbq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&cbq, &mut ac_chroma_freq);
all_blocks.push(cbq);
let crq = gather_downsampled_block(
&cr_plane,
width,
height,
x0,
y0,
2,
1,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = crq[0] - prev_dc_cr;
prev_dc_cr = crq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&crq, &mut ac_chroma_freq);
all_blocks.push(crq);
}
Subsampling::S420 => {
for (dx, dy) in [(0, 0), (8, 0), (0, 8), (8, 8)] {
let yq = gather_block(
&y_plane,
width,
height,
x0 + dx,
y0 + dy,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = yq[0] - prev_dc_y;
prev_dc_y = yq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&yq, &mut ac_luma_freq);
all_blocks.push(yq);
}
let cbq = gather_downsampled_block(
&cb_plane,
width,
height,
x0,
y0,
2,
2,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = cbq[0] - prev_dc_cb;
prev_dc_cb = cbq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&cbq, &mut ac_chroma_freq);
all_blocks.push(cbq);
let crq = gather_downsampled_block(
&cr_plane,
width,
height,
x0,
y0,
2,
2,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = crq[0] - prev_dc_cr;
prev_dc_cr = crq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&crq, &mut ac_chroma_freq);
all_blocks.push(crq);
}
Subsampling::S440 => {
for dy in [0usize, 8] {
let yq = gather_block(
&y_plane,
width,
height,
x0,
y0 + dy,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = yq[0] - prev_dc_y;
prev_dc_y = yq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&yq, &mut ac_luma_freq);
all_blocks.push(yq);
}
let cbq = gather_downsampled_block(
&cb_plane,
width,
height,
x0,
y0,
1,
2,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = cbq[0] - prev_dc_cb;
prev_dc_cb = cbq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&cbq, &mut ac_chroma_freq);
all_blocks.push(cbq);
let crq = gather_downsampled_block(
&cr_plane,
width,
height,
x0,
y0,
1,
2,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = crq[0] - prev_dc_cr;
prev_dc_cr = crq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&crq, &mut ac_chroma_freq);
all_blocks.push(crq);
}
Subsampling::S411 => {
for dx in [0usize, 8, 16, 24] {
let yq = gather_block(
&y_plane,
width,
height,
x0 + dx,
y0,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = yq[0] - prev_dc_y;
prev_dc_y = yq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&yq, &mut ac_luma_freq);
all_blocks.push(yq);
}
let cbq = gather_downsampled_block(
&cb_plane,
width,
height,
x0,
y0,
4,
1,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = cbq[0] - prev_dc_cb;
prev_dc_cb = cbq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&cbq, &mut ac_chroma_freq);
all_blocks.push(cbq);
let crq = gather_downsampled_block(
&cr_plane,
width,
height,
x0,
y0,
4,
1,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = crq[0] - prev_dc_cr;
prev_dc_cr = crq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&crq, &mut ac_chroma_freq);
all_blocks.push(crq);
}
Subsampling::S441 => {
for dy in [0usize, 8, 16, 24] {
let yq = gather_block(
&y_plane,
width,
height,
x0,
y0 + dy,
&luma_divisors,
enc_simd.fdct_quantize,
);
let diff = yq[0] - prev_dc_y;
prev_dc_y = yq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_luma_freq);
huff_opt::gather_ac_symbols(&yq, &mut ac_luma_freq);
all_blocks.push(yq);
}
let cbq = gather_downsampled_block(
&cb_plane,
width,
height,
x0,
y0,
1,
4,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = cbq[0] - prev_dc_cb;
prev_dc_cb = cbq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&cbq, &mut ac_chroma_freq);
all_blocks.push(cbq);
let crq = gather_downsampled_block(
&cr_plane,
width,
height,
x0,
y0,
1,
4,
&chroma_divisors,
enc_simd.fdct_quantize,
);
let diff = crq[0] - prev_dc_cr;
prev_dc_cr = crq[0];
huff_opt::gather_dc_symbol(diff, &mut dc_chroma_freq);
huff_opt::gather_ac_symbols(&crq, &mut ac_chroma_freq);
all_blocks.push(crq);
}
}
}
}
}
dc_luma_freq[256] = 1;
ac_luma_freq[256] = 1;
dc_chroma_freq[256] = 1;
ac_chroma_freq[256] = 1;
let (dc_luma_bits, dc_luma_values) = huff_opt::gen_optimal_table(&dc_luma_freq);
let (ac_luma_bits, ac_luma_values) = huff_opt::gen_optimal_table(&ac_luma_freq);
let (dc_chroma_bits, dc_chroma_values) = huff_opt::gen_optimal_table(&dc_chroma_freq);
let (ac_chroma_bits, ac_chroma_values) = huff_opt::gen_optimal_table(&ac_chroma_freq);
let dc_luma_table = build_huff_table(&dc_luma_bits, &dc_luma_values);
let ac_luma_table = build_huff_table(&ac_luma_bits, &ac_luma_values);
let dc_chroma_table = build_huff_table(&dc_chroma_bits, &dc_chroma_values);
let ac_chroma_table = build_huff_table(&ac_chroma_bits, &ac_chroma_values);
let mut bit_writer = BitWriter::new(width * height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
let mut block_idx = 0;
for _mcu_row in 0..mcus_y {
for _mcu_col in 0..mcus_x {
if is_grayscale {
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_y,
&dc_luma_table,
&ac_luma_table,
);
block_idx += 1;
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => {
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_y,
&dc_luma_table,
&ac_luma_table,
);
block_idx += 1;
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cb,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cr,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
}
Subsampling::S422 => {
for _ in 0..2 {
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_y,
&dc_luma_table,
&ac_luma_table,
);
block_idx += 1;
}
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cb,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cr,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
}
Subsampling::S420 => {
for _ in 0..4 {
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_y,
&dc_luma_table,
&ac_luma_table,
);
block_idx += 1;
}
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cb,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cr,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
}
Subsampling::S440 => {
for _ in 0..2 {
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_y,
&dc_luma_table,
&ac_luma_table,
);
block_idx += 1;
}
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cb,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cr,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
}
Subsampling::S411 | Subsampling::S441 => {
for _ in 0..4 {
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_y,
&dc_luma_table,
&ac_luma_table,
);
block_idx += 1;
}
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cb,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
HuffmanEncoder::encode_block(
&mut bit_writer,
&all_blocks[block_idx],
&mut prev_dc_cr,
&dc_chroma_table,
&ac_chroma_table,
);
block_idx += 1;
}
}
}
}
}
bit_writer.flush();
let mut output = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components = vec![(1, 1, 1, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
} else {
let (h_samp, v_samp) = subsampling.sampling_factors();
let components = vec![(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(&mut output, 0, 0, &dc_luma_bits, &dc_luma_values);
marker_writer::write_dht(&mut output, 1, 0, &ac_luma_bits, &ac_luma_values);
if !is_grayscale {
marker_writer::write_dht(&mut output, 0, 1, &dc_chroma_bits, &dc_chroma_values);
marker_writer::write_dht(&mut output, 1, 1, &ac_chroma_bits, &ac_chroma_values);
}
if is_grayscale {
let scan_components = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components = vec![(1, 0, 0), (2, 1, 1), (3, 1, 1)];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
fn gather_block(
plane: &[u8],
plane_width: usize,
plane_height: usize,
block_x: usize,
block_y: usize,
quant_table: &QuantDivisors,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
) -> [i16; 64] {
let mut block = [0i16; 64];
extract_block(
plane,
plane_width,
plane_height,
block_x,
block_y,
&mut block,
);
let mut quantized = [0i16; 64];
fdct_quantize_fn(&mut block, quant_table, &mut quantized);
quantized
}
#[allow(clippy::too_many_arguments)]
fn gather_downsampled_block(
plane: &[u8],
plane_width: usize,
plane_height: usize,
block_x: usize,
block_y: usize,
h_factor: usize,
v_factor: usize,
quant_table: &QuantDivisors,
fdct_quantize_fn: fn(&mut [i16; 64], &QuantDivisors, &mut [i16; 64]),
) -> [i16; 64] {
let mut block = [0i16; 64];
downsample_chroma_block(
plane,
plane_width,
plane_height,
block_x,
block_y,
h_factor,
v_factor,
&mut block,
);
let mut quantized = [0i16; 64];
fdct_quantize_fn(&mut block, quant_table, &mut quantized);
quantized
}
#[allow(clippy::too_many_arguments)]
pub fn compress_raw(
planes: &[&[u8]],
plane_widths: &[usize],
plane_heights: &[usize],
image_width: usize,
image_height: usize,
quality: u8,
subsampling: Subsampling,
) -> Result<Vec<u8>> {
if image_width == 0 || image_height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
if planes.len() != plane_widths.len() || planes.len() != plane_heights.len() {
return Err(JpegError::CorruptData(
"planes, plane_widths, and plane_heights must have the same length".to_string(),
));
}
let is_grayscale: bool = planes.len() == 1;
if is_grayscale && subsampling != Subsampling::S444 {
return Err(JpegError::CorruptData(format!(
"1 plane (grayscale) is only valid with S444 subsampling, got {:?}",
subsampling
)));
}
if !is_grayscale && planes.len() != 3 {
return Err(JpegError::CorruptData(format!(
"expected 1 (grayscale) or 3 (YCbCr) planes, got {}",
planes.len()
)));
}
let (h_samp, v_samp): (u8, u8) = subsampling.sampling_factors();
if !is_grayscale {
let expected_cb_w: usize = image_width.div_ceil(h_samp as usize);
let expected_cb_h: usize = image_height.div_ceil(v_samp as usize);
if plane_widths[0] != image_width || plane_heights[0] != image_height {
return Err(JpegError::CorruptData(format!(
"Y plane dimensions {}x{} do not match image dimensions {}x{}",
plane_widths[0], plane_heights[0], image_width, image_height
)));
}
for comp_idx in 1..3 {
let comp_name: &str = if comp_idx == 1 { "Cb" } else { "Cr" };
if plane_widths[comp_idx] != expected_cb_w || plane_heights[comp_idx] != expected_cb_h {
return Err(JpegError::CorruptData(format!(
"{} plane dimensions {}x{} do not match expected {}x{} for {:?} subsampling",
comp_name,
plane_widths[comp_idx],
plane_heights[comp_idx],
expected_cb_w,
expected_cb_h,
subsampling
)));
}
}
}
for (i, plane) in planes.iter().enumerate() {
let expected_size: usize = plane_widths[i] * plane_heights[i];
if plane.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: plane.len(),
});
}
}
let luma_quant: [u16; 64] =
tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant: [u16; 64] =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors: QuantDivisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors: QuantDivisors = scale_quant_for_fdct(&chroma_quant);
let dc_luma_table: HuffTable =
build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_luma_table: HuffTable =
build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let dc_chroma_table: HuffTable =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let ac_chroma_table: HuffTable =
build_huff_table(&tables::AC_CHROMINANCE_BITS, &tables::AC_CHROMINANCE_VALUES);
let (mcu_w, mcu_h): (usize, usize) = if is_grayscale {
(8, 8)
} else {
match subsampling {
Subsampling::S444 | Subsampling::Unknown => (8, 8),
Subsampling::S422 => (16, 8),
Subsampling::S420 => (16, 16),
Subsampling::S440 => (8, 16),
Subsampling::S411 => (32, 8),
Subsampling::S441 => (8, 32),
}
};
let mcus_x: usize = image_width.div_ceil(mcu_w);
let mcus_y: usize = image_height.div_ceil(mcu_h);
let enc_simd = crate::simd::detect_encoder();
let fdct_quantize_fn = enc_simd.fdct_quantize;
let mut bit_writer: BitWriter = BitWriter::new(image_width * image_height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0: usize = mcu_col * mcu_w;
let y0: usize = mcu_row * mcu_h;
if is_grayscale {
encode_single_block(
planes[0],
plane_widths[0],
plane_heights[0],
x0,
y0,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
fdct_quantize_fn,
);
} else {
let h: usize = h_samp as usize;
let v: usize = v_samp as usize;
for vy in 0..v {
for hx in 0..h {
encode_single_block(
planes[0],
plane_widths[0],
plane_heights[0],
x0 + hx * 8,
y0 + vy * 8,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
fdct_quantize_fn,
);
}
}
let chroma_x: usize = x0 / h;
let chroma_y: usize = y0 / v;
encode_single_block(
planes[1],
plane_widths[1],
plane_heights[1],
chroma_x,
chroma_y,
&chroma_divisors,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_cb,
fdct_quantize_fn,
);
encode_single_block(
planes[2],
plane_widths[2],
plane_heights[2],
chroma_x,
chroma_y,
&chroma_divisors,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_cr,
fdct_quantize_fn,
);
}
}
}
bit_writer.flush();
let mut output: Vec<u8> = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components: Vec<(u8, u8, u8, u8)> = vec![(1, 1, 1, 0)];
marker_writer::write_sof0(
&mut output,
image_width as u16,
image_height as u16,
&components,
);
} else {
let components: Vec<(u8, u8, u8, u8)> =
vec![(1, h_samp, v_samp, 0), (2, 1, 1, 1), (3, 1, 1, 1)];
marker_writer::write_sof0(
&mut output,
image_width as u16,
image_height as u16,
&components,
);
}
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
if !is_grayscale {
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
1,
&tables::AC_CHROMINANCE_BITS,
&tables::AC_CHROMINANCE_VALUES,
);
}
if is_grayscale {
marker_writer::write_sos(&mut output, &[(1, 0, 0)]);
} else {
marker_writer::write_sos(&mut output, &[(1, 0, 0), (2, 1, 1), (3, 1, 1)]);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
pub fn compress_custom_sampling(
pixels: &[u8],
width: usize,
height: usize,
pixel_format: PixelFormat,
quality: u8,
factors: &[(u8, u8)],
) -> Result<Vec<u8>> {
if width == 0 || height == 0 {
return Err(JpegError::CorruptData(
"image dimensions must be non-zero".to_string(),
));
}
let bpp: usize = pixel_format.bytes_per_pixel();
let expected_size: usize = width * height * bpp;
if pixels.len() < expected_size {
return Err(JpegError::BufferTooSmall {
need: expected_size,
got: pixels.len(),
});
}
let is_grayscale: bool = pixel_format == PixelFormat::Grayscale;
let num_components: usize = if is_grayscale { 1 } else { 3 };
if factors.len() != num_components {
return Err(JpegError::CorruptData(format!(
"expected {} sampling factors for {}, got {}",
num_components,
if is_grayscale { "grayscale" } else { "YCbCr" },
factors.len()
)));
}
for (i, &(h, v)) in factors.iter().enumerate() {
if h == 0 || h > 4 || v == 0 || v > 4 {
return Err(JpegError::CorruptData(format!(
"sampling factor ({}, {}) for component {} is out of range 1..=4",
h, v, i
)));
}
}
let max_h: u8 = factors.iter().map(|&(h, _)| h).max().unwrap_or(1);
let max_v: u8 = factors.iter().map(|&(_, v)| v).max().unwrap_or(1);
for (i, &(h, v)) in factors.iter().enumerate() {
if !max_h.is_multiple_of(h) || !max_v.is_multiple_of(v) {
return Err(JpegError::CorruptData(format!(
"component {} sampling factors ({}, {}) must evenly divide max factors ({}, {})",
i, h, v, max_h, max_v
)));
}
}
let mcu_w: usize = max_h as usize * 8;
let mcu_h: usize = max_v as usize * 8;
let mcus_x: usize = width.div_ceil(mcu_w);
let mcus_y: usize = height.div_ceil(mcu_h);
let luma_quant: [u16; 64] =
tables::quality_scale_quant_table(&tables::STD_LUMINANCE_QUANT_TABLE, quality);
let chroma_quant: [u16; 64] =
tables::quality_scale_quant_table(&tables::STD_CHROMINANCE_QUANT_TABLE, quality);
let luma_divisors: QuantDivisors = scale_quant_for_fdct(&luma_quant);
let chroma_divisors: QuantDivisors = scale_quant_for_fdct(&chroma_quant);
let dc_luma_table: HuffTable =
build_huff_table(&tables::DC_LUMINANCE_BITS, &tables::DC_LUMINANCE_VALUES);
let ac_luma_table: HuffTable =
build_huff_table(&tables::AC_LUMINANCE_BITS, &tables::AC_LUMINANCE_VALUES);
let dc_chroma_table: HuffTable =
build_huff_table(&tables::DC_CHROMINANCE_BITS, &tables::DC_CHROMINANCE_VALUES);
let ac_chroma_table: HuffTable =
build_huff_table(&tables::AC_CHROMINANCE_BITS, &tables::AC_CHROMINANCE_VALUES);
let enc_simd = crate::simd::detect_encoder();
let (y_plane, cb_plane, cr_plane) = convert_to_ycbcr(
pixels,
width,
height,
pixel_format,
enc_simd.rgb_to_ycbcr_row,
)?;
let fdct_quantize_fn = enc_simd.fdct_quantize;
let mut bit_writer: BitWriter = BitWriter::new(width * height);
let mut prev_dc_y: i16 = 0;
let mut prev_dc_cb: i16 = 0;
let mut prev_dc_cr: i16 = 0;
let y_h: u8 = factors[0].0;
let y_v: u8 = factors[0].1;
for mcu_row in 0..mcus_y {
for mcu_col in 0..mcus_x {
let x0: usize = mcu_col * mcu_w;
let y0: usize = mcu_row * mcu_h;
if is_grayscale {
for bv in 0..y_v as usize {
for bh in 0..y_h as usize {
encode_single_block(
&y_plane,
width,
height,
x0 + bh * 8,
y0 + bv * 8,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
fdct_quantize_fn,
);
}
}
} else {
for bv in 0..y_v as usize {
for bh in 0..y_h as usize {
encode_single_block(
&y_plane,
width,
height,
x0 + bh * 8,
y0 + bv * 8,
&luma_divisors,
&dc_luma_table,
&ac_luma_table,
&mut bit_writer,
&mut prev_dc_y,
fdct_quantize_fn,
);
}
}
let cb_h: u8 = factors[1].0;
let cb_v: u8 = factors[1].1;
let h_downsample: usize = max_h as usize / cb_h as usize;
let v_downsample: usize = max_v as usize / cb_v as usize;
for bv in 0..cb_v as usize {
for bh in 0..cb_h as usize {
encode_downsampled_chroma_block(
&cb_plane,
width,
height,
x0 + bh * 8 * h_downsample,
y0 + bv * 8 * v_downsample,
h_downsample,
v_downsample,
&chroma_divisors,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_cb,
fdct_quantize_fn,
);
}
}
let cr_h: u8 = factors[2].0;
let cr_v: u8 = factors[2].1;
let h_downsample_cr: usize = max_h as usize / cr_h as usize;
let v_downsample_cr: usize = max_v as usize / cr_v as usize;
for bv in 0..cr_v as usize {
for bh in 0..cr_h as usize {
encode_downsampled_chroma_block(
&cr_plane,
width,
height,
x0 + bh * 8 * h_downsample_cr,
y0 + bv * 8 * v_downsample_cr,
h_downsample_cr,
v_downsample_cr,
&chroma_divisors,
&dc_chroma_table,
&ac_chroma_table,
&mut bit_writer,
&mut prev_dc_cr,
fdct_quantize_fn,
);
}
}
}
}
}
bit_writer.flush();
let mut output: Vec<u8> = Vec::with_capacity(bit_writer.data().len() + 1024);
marker_writer::write_soi(&mut output);
marker_writer::write_app0_jfif(&mut output);
marker_writer::write_dqt(&mut output, 0, &luma_quant);
if !is_grayscale {
marker_writer::write_dqt(&mut output, 1, &chroma_quant);
}
if is_grayscale {
let components: Vec<(u8, u8, u8, u8)> = vec![(1, y_h, y_v, 0)];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
} else {
let components: Vec<(u8, u8, u8, u8)> = vec![
(1, factors[0].0, factors[0].1, 0), (2, factors[1].0, factors[1].1, 1), (3, factors[2].0, factors[2].1, 1), ];
marker_writer::write_sof0(&mut output, width as u16, height as u16, &components);
}
marker_writer::write_dht(
&mut output,
0,
0,
&tables::DC_LUMINANCE_BITS,
&tables::DC_LUMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
0,
&tables::AC_LUMINANCE_BITS,
&tables::AC_LUMINANCE_VALUES,
);
if !is_grayscale {
marker_writer::write_dht(
&mut output,
0,
1,
&tables::DC_CHROMINANCE_BITS,
&tables::DC_CHROMINANCE_VALUES,
);
marker_writer::write_dht(
&mut output,
1,
1,
&tables::AC_CHROMINANCE_BITS,
&tables::AC_CHROMINANCE_VALUES,
);
}
if is_grayscale {
let scan_components: Vec<(u8, u8, u8)> = vec![(1, 0, 0)];
marker_writer::write_sos(&mut output, &scan_components);
} else {
let scan_components: Vec<(u8, u8, u8)> = vec![
(1, 0, 0), (2, 1, 1), (3, 1, 1), ];
marker_writer::write_sos(&mut output, &scan_components);
}
output.extend_from_slice(bit_writer.data());
marker_writer::write_eoi(&mut output);
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compress_grayscale_1x1() {
let pixels = [128u8];
let result = compress(
&pixels,
1,
1,
PixelFormat::Grayscale,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
let jpeg = result.unwrap();
assert_eq!(jpeg[0], 0xFF);
assert_eq!(jpeg[1], 0xD8);
assert_eq!(jpeg[jpeg.len() - 2], 0xFF);
assert_eq!(jpeg[jpeg.len() - 1], 0xD9);
}
#[test]
fn compress_rgb_8x8() {
let mut pixels = vec![0u8; 8 * 8 * 3];
for i in 0..64 {
pixels[i * 3] = 255; pixels[i * 3 + 1] = 0; pixels[i * 3 + 2] = 0; }
let result = compress(
&pixels,
8,
8,
PixelFormat::Rgb,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
let jpeg = result.unwrap();
assert_eq!(jpeg[0], 0xFF);
assert_eq!(jpeg[1], 0xD8);
assert_eq!(jpeg[jpeg.len() - 2], 0xFF);
assert_eq!(jpeg[jpeg.len() - 1], 0xD9);
}
#[test]
fn compress_rgb_422() {
let mut pixels = vec![0u8; 16 * 8 * 3];
for i in 0..(16 * 8) {
pixels[i * 3] = 0;
pixels[i * 3 + 1] = 255;
pixels[i * 3 + 2] = 0;
}
let result = compress(
&pixels,
16,
8,
PixelFormat::Rgb,
75,
Subsampling::S422,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_rgb_420() {
let mut pixels = vec![0u8; 16 * 16 * 3];
for i in 0..(16 * 16) {
pixels[i * 3] = 0;
pixels[i * 3 + 1] = 0;
pixels[i * 3 + 2] = 255;
}
let result = compress(
&pixels,
16,
16,
PixelFormat::Rgb,
75,
Subsampling::S420,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_non_multiple_of_8() {
let pixels = vec![128u8; 10 * 6 * 3];
let result = compress(
&pixels,
10,
6,
PixelFormat::Rgb,
50,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_non_multiple_of_16_420() {
let pixels = vec![200u8; 13 * 11 * 3];
let result = compress(
&pixels,
13,
11,
PixelFormat::Rgb,
90,
Subsampling::S420,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_rgba_input() {
let pixels = vec![128u8; 8 * 8 * 4];
let result = compress(
&pixels,
8,
8,
PixelFormat::Rgba,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_bgr_input() {
let pixels = vec![128u8; 8 * 8 * 3];
let result = compress(
&pixels,
8,
8,
PixelFormat::Bgr,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_bgra_input() {
let pixels = vec![128u8; 8 * 8 * 4];
let result = compress(
&pixels,
8,
8,
PixelFormat::Bgra,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn compress_rejects_zero_dimensions() {
let pixels = vec![128u8; 64];
let result = compress(
&pixels,
0,
8,
PixelFormat::Grayscale,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_err());
}
#[test]
fn compress_rejects_buffer_too_small() {
let pixels = vec![128u8; 10];
let result = compress(
&pixels,
8,
8,
PixelFormat::Rgb,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_err());
}
#[test]
fn compress_quality_extremes() {
let pixels = vec![128u8; 8 * 8 * 3];
let result1 = compress(
&pixels,
8,
8,
PixelFormat::Rgb,
1,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result1.is_ok());
let result100 = compress(
&pixels,
8,
8,
PixelFormat::Rgb,
100,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result100.is_ok());
assert!(result100.unwrap().len() >= result1.unwrap().len());
}
#[test]
fn roundtrip_grayscale() {
let width = 8;
let height = 8;
let pixels: Vec<u8> = (0..64).map(|i| (i * 4) as u8).collect();
let jpeg = compress(
&pixels,
width,
height,
PixelFormat::Grayscale,
100,
Subsampling::S444,
DctMethod::IsLow,
)
.unwrap();
let image = crate::api::high_level::decompress(&jpeg).unwrap();
assert_eq!(image.width, width);
assert_eq!(image.height, height);
assert_eq!(image.pixel_format, PixelFormat::Grayscale);
for i in 0..64 {
let diff = (image.data[i] as i16 - pixels[i] as i16).unsigned_abs();
assert!(
diff <= 3,
"pixel {i}: expected ~{}, got {} (diff {})",
pixels[i],
image.data[i],
diff
);
}
}
#[test]
fn roundtrip_rgb_444() {
let width = 8;
let height = 8;
let pixels = vec![128u8; width * height * 3];
let jpeg = compress(
&pixels,
width,
height,
PixelFormat::Rgb,
100,
Subsampling::S444,
DctMethod::IsLow,
)
.unwrap();
let image = crate::api::high_level::decompress(&jpeg).unwrap();
assert_eq!(image.width, width);
assert_eq!(image.height, height);
for i in 0..image.data.len() {
let diff = (image.data[i] as i16 - 128).unsigned_abs();
assert!(
diff <= 8,
"byte {i}: expected ~128, got {} (diff {})",
image.data[i],
diff
);
}
}
#[test]
fn compress_cmyk_produces_valid_jpeg() {
let pixels = vec![128u8; 8 * 8 * 4];
let result = compress(
&pixels,
8,
8,
PixelFormat::Cmyk,
75,
Subsampling::S444,
DctMethod::IsLow,
);
assert!(result.is_ok());
}
#[test]
fn extract_block_edge_padding() {
let plane: Vec<u8> = (0..16).map(|i| (i * 16) as u8).collect();
let mut block = [0i16; 64];
extract_block(&plane, 4, 4, 0, 0, &mut block);
assert_eq!(block[0], -128);
assert_eq!(block[3], 48 - 128);
assert_eq!(block[4], 48 - 128);
assert_eq!(block[7], 48 - 128);
assert_eq!(block[4 * 8], block[3 * 8]);
}
}