use crate::colorspace::ColorSpaceExt;
use crate::component::CompInfo;
use crate::component::CompInfoExt;
use crate::errormgr::unwinding_error_mgr;
use crate::errormgr::ErrorMgr;
use crate::fail;
use crate::ffi;
use crate::ffi::boolean;
use crate::ffi::jpegli_compress_struct;
use crate::ffi::DCTSIZE;
use crate::ffi::JDIMENSION;
use crate::ffi::JPEG_LIB_VERSION;
use crate::marker::Marker;
use crate::qtable::QTable;
use crate::writedst::DestinationMgr;
use crate::{colorspace::ColorSpace, PixelDensity};
use arrayvec::ArrayVec;
use std::cmp::min;
use std::io;
use std::marker::PhantomPinned;
use std::mem;
use std::os::raw::{c_int, c_uchar, c_uint, c_ulong, c_void};
use std::ptr;
use std::ptr::addr_of_mut;
use std::slice;
const MAX_MCU_HEIGHT: usize = 16;
const MAX_COMPONENTS: usize = 4;
pub struct Compress {
cinfo: Box<jpegli_compress_struct>,
own_err: *mut ErrorMgr,
_it_is_self_referential: PhantomPinned,
}
#[derive(Copy, Clone)]
pub enum ScanMode {
AllComponentsTogether = 0,
ScanPerComponent = 1,
Auto = 2,
}
pub struct CompressStarted<W> {
compress: Compress,
dest_mgr: Box<DestinationMgr<W>>,
}
impl Compress {
#[must_use]
pub fn new(color_space: ColorSpace) -> Compress {
Compress::new_err(unwinding_error_mgr(), color_space)
}
#[must_use]
pub fn new_err(err: Box<ErrorMgr>, color_space: ColorSpace) -> Compress {
unsafe {
let mut newself = Compress {
cinfo: Box::new(mem::zeroed()),
own_err: Box::into_raw(err),
_it_is_self_referential: PhantomPinned,
};
newself.cinfo.common.err = addr_of_mut!(*newself.own_err);
let s = mem::size_of_val(&*newself.cinfo);
ffi::jpegli_CreateCompress(&mut newself.cinfo, JPEG_LIB_VERSION, s);
newself.cinfo.in_color_space = color_space;
newself.cinfo.input_components = color_space.num_components() as c_int;
ffi::jpegli_set_defaults(&mut newself.cinfo);
newself
}
}
#[doc(hidden)]
#[deprecated(note = "Give a Vec to start_compress instead")]
pub fn set_mem_dest(&self) {}
pub fn start_compress<W: io::Write>(self, writer: W) -> io::Result<CompressStarted<W>> {
if !self.components().iter().any(|c| c.h_samp_factor == 1) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"at least one h_samp_factor must be 1",
));
}
if !self.components().iter().any(|c| c.v_samp_factor == 1) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"at least one v_samp_factor must be 1",
));
}
let expected_file_size =
(self.cinfo.image_width as usize * self.cinfo.image_height as usize / 8 + 4095) & !4095;
let write_buffer_capacity = expected_file_size.clamp(1 << 12, 1 << 16);
let mut started = CompressStarted {
compress: self,
dest_mgr: Box::new(DestinationMgr::new(writer, write_buffer_capacity)),
};
unsafe {
let dest_ptr = addr_of_mut!(started.dest_mgr.as_mut().iface);
started.compress.cinfo.dest = dest_ptr;
ffi::jpegli_start_compress(&mut started.compress.cinfo, true as boolean);
}
Ok(started)
}
}
impl<W> CompressStarted<W> {
pub fn write_marker(&mut self, marker: Marker, data: &[u8]) {
unsafe {
ffi::jpegli_write_marker(
&mut self.compress.cinfo,
marker.into(),
data.as_ptr(),
data.len() as c_uint,
);
}
}
pub fn write_icc_profile(&mut self, data: &[u8]) {
const OVERHEAD_LEN: usize = 14;
const MAX_BYTES_IN_MARKER: usize = 65533;
const MAX_DATA_BYTES_IN_MARKER: usize = MAX_BYTES_IN_MARKER - OVERHEAD_LEN;
if data.is_empty() {
fail(&mut self.compress.cinfo.common, ffi::JERR_BUFFER_SIZE);
}
let chunks = data.chunks(MAX_DATA_BYTES_IN_MARKER);
let num_chunks = chunks.len();
let mut buf = Vec::with_capacity(MAX_BYTES_IN_MARKER.min(data.len() + OVERHEAD_LEN));
chunks.enumerate().for_each(move |(current_marker, chunk)| {
buf.clear();
buf.extend_from_slice(b"ICC_PROFILE\0");
buf.extend([(current_marker as u8) + 1, num_chunks as u8]);
buf.extend_from_slice(chunk);
self.write_marker(Marker::APP(2), &buf)
});
}
pub fn components(&self) -> &[CompInfo] {
self.compress.components()
}
fn can_write_more_lines(&self) -> bool {
self.compress.cinfo.next_scanline < self.compress.cinfo.image_height
}
}
impl Compress {
pub fn components_mut(&mut self) -> &mut [CompInfo] {
unsafe {
slice::from_raw_parts_mut(self.cinfo.comp_info, self.cinfo.num_components as usize)
}
}
#[must_use]
pub fn components(&self) -> &[CompInfo] {
unsafe { slice::from_raw_parts(self.cinfo.comp_info, self.cinfo.num_components as usize) }
}
}
impl<W> CompressStarted<W> {
pub fn write_scanlines(&mut self, image_src: &[u8]) -> io::Result<()> {
if self.compress.cinfo.raw_data_in != 0
|| self.compress.cinfo.input_components <= 0
|| self.compress.cinfo.image_width == 0
{
return Err(io::ErrorKind::InvalidInput.into());
}
let byte_width = self.compress.cinfo.image_width as usize
* self.compress.cinfo.input_components as usize;
for rows in image_src.chunks(MAX_MCU_HEIGHT * byte_width) {
let mut row_pointers = ArrayVec::<_, MAX_MCU_HEIGHT>::new();
for row in rows.chunks_exact(byte_width) {
row_pointers.push(row.as_ptr());
}
let mut rows_left = row_pointers.len() as u32;
let mut row_pointers = row_pointers.as_ptr();
while rows_left > 0 {
unsafe {
let rows_written = ffi::jpegli_write_scanlines(
&mut self.compress.cinfo,
row_pointers,
rows_left,
);
debug_assert!(rows_left >= rows_written);
if rows_written == 0 {
return Err(io::ErrorKind::UnexpectedEof.into());
}
rows_left -= rows_written;
row_pointers = row_pointers.add(rows_written as usize);
}
}
}
Ok(())
}
#[track_caller]
pub fn write_raw_data(&mut self, image_src: &[&[u8]]) -> bool {
if 0 == self.compress.cinfo.raw_data_in {
panic!("Raw data not set");
}
let mcu_height = self.compress.cinfo.max_v_samp_factor as usize * DCTSIZE;
if mcu_height > MAX_MCU_HEIGHT {
panic!("Subsampling factor too large");
}
assert!(mcu_height > 0);
let num_components = self.components().len();
if num_components > MAX_COMPONENTS || num_components > image_src.len() {
panic!(
"Too many components: declared {}, got {}",
num_components,
image_src.len()
);
}
for (ci, comp_info) in self.components().iter().enumerate() {
if comp_info.row_stride() * comp_info.col_stride() > image_src[ci].len() {
panic!(
"Bitmap too small. Expected {}x{}, got {}",
comp_info.row_stride(),
comp_info.col_stride(),
image_src[ci].len()
);
}
}
let mut start_row = self.compress.cinfo.next_scanline as usize;
while self.can_write_more_lines() {
unsafe {
let mut row_ptrs = [[ptr::null::<u8>(); MAX_MCU_HEIGHT]; MAX_COMPONENTS];
let mut comp_ptrs = [ptr::null::<*const u8>(); MAX_COMPONENTS];
for (ci, comp_info) in self.components().iter().enumerate() {
let row_stride = comp_info.row_stride();
let input_height = image_src[ci].len() / row_stride;
let comp_start_row = start_row * comp_info.v_samp_factor as usize
/ self.compress.cinfo.max_v_samp_factor as usize;
let comp_height = min(
input_height - comp_start_row,
DCTSIZE * comp_info.v_samp_factor as usize,
);
assert!(comp_height >= 8);
for ri in 0..comp_height {
let start_offset = (comp_start_row + ri) * row_stride;
row_ptrs[ci][ri] =
image_src[ci][start_offset..start_offset + row_stride].as_ptr();
}
for ri in comp_height..mcu_height {
row_ptrs[ci][ri] = ptr::null();
}
comp_ptrs[ci] = row_ptrs[ci].as_ptr();
}
let rows_written = ffi::jpegli_write_raw_data(
&mut self.compress.cinfo,
comp_ptrs.as_ptr(),
mcu_height as u32,
) as usize;
if 0 == rows_written {
return false;
}
start_row += rows_written;
}
}
true
}
}
impl Compress {
pub fn set_color_space(&mut self, color_space: ColorSpace) {
unsafe {
ffi::jpegli_set_colorspace(&mut self.cinfo, color_space);
}
}
pub fn set_size(&mut self, width: usize, height: usize) {
self.cinfo.image_width = width as JDIMENSION;
self.cinfo.image_height = height as JDIMENSION;
}
#[deprecated(note = "it doesn't do anything")]
pub fn set_gamma(&mut self, gamma: f64) {
self.cinfo.input_gamma = gamma;
}
pub fn set_pixel_density(&mut self, density: PixelDensity) {
self.cinfo.density_unit = density.unit as u8;
self.cinfo.X_density = density.x;
self.cinfo.Y_density = density.y;
}
pub fn set_smoothing_factor(&mut self, smoothing_factor: u8) {
self.cinfo.smoothing_factor = smoothing_factor as c_int;
}
pub fn set_optimize_coding(&mut self, opt: bool) {
self.cinfo.optimize_coding = opt as boolean;
}
pub fn set_progressive_mode(&mut self) {
unsafe {
ffi::jpegli_simple_progression(&mut self.cinfo);
}
}
pub fn set_raw_data_in(&mut self, opt: bool) {
self.cinfo.raw_data_in = opt as boolean;
}
pub fn set_quality(&mut self, quality: f32) {
unsafe {
ffi::jpegli_set_quality(&mut self.cinfo, quality as c_int, false as boolean);
}
}
pub fn set_luma_qtable(&mut self, qtable: &QTable) {
unsafe {
ffi::jpegli_add_quant_table(&mut self.cinfo, 0, qtable.as_ptr(), 100, 1);
}
}
pub fn set_chroma_qtable(&mut self, qtable: &QTable) {
unsafe {
ffi::jpegli_add_quant_table(&mut self.cinfo, 1, qtable.as_ptr(), 100, 1);
}
}
pub fn set_chroma_sampling_pixel_sizes(&mut self, cb: (u8, u8), cr: (u8, u8)) {
let max_sampling_h = cb.0.max(cr.0);
let max_sampling_v = cb.1.max(cr.1);
let px_sizes = [(1, 1), cb, cr];
for (c, (h, v)) in self.components_mut().iter_mut().zip(px_sizes) {
c.h_samp_factor = (max_sampling_h / h).into();
c.v_samp_factor = (max_sampling_v / v).into();
}
}
}
impl<W: io::Write> CompressStarted<W> {
#[inline]
pub fn finish(mut self) -> io::Result<W> {
unsafe {
ffi::jpegli_finish_compress(&mut self.compress.cinfo);
}
self.compress.cinfo.dest = ptr::null_mut();
drop(self.compress);
Ok(self.dest_mgr.into_inner())
}
#[doc(hidden)]
#[deprecated(note = "use finish(); it now returns a writer given to start_compress()")]
pub fn finish_compress(self) -> io::Result<W> {
self.finish()
}
#[cold]
pub fn abort(mut self) -> W {
self.compress.cinfo.dest = ptr::null_mut();
self.dest_mgr.into_inner()
}
}
impl Drop for Compress {
#[inline]
fn drop(&mut self) {
unsafe {
self.cinfo.dest = ptr::null_mut();
ffi::jpegli_destroy_compress(&mut self.cinfo);
let _ = Box::from_raw(self.own_err);
}
}
}
#[test]
fn write_mem() {
let mut cinfo = Compress::new(ColorSpace::JCS_YCbCr);
assert_eq!(3, cinfo.components().len());
cinfo.set_size(17, 33);
#[allow(deprecated)]
{
cinfo.set_gamma(1.0);
}
cinfo.set_progressive_mode();
cinfo.set_raw_data_in(true);
cinfo.set_quality(88.);
cinfo.set_chroma_sampling_pixel_sizes((1, 1), (1, 1));
for c in cinfo.components() {
assert_eq!(c.v_samp_factor, 1);
assert_eq!(c.h_samp_factor, 1);
}
cinfo.set_chroma_sampling_pixel_sizes((2, 2), (2, 2));
for (c, samp) in cinfo.components().iter().zip([2, 1, 1]) {
assert_eq!(c.v_samp_factor, samp);
assert_eq!(c.h_samp_factor, samp);
}
let mut cinfo = cinfo.start_compress(Vec::new()).unwrap();
cinfo.write_marker(Marker::APP(2), "Hello World".as_bytes());
assert_eq!(24, cinfo.components()[0].row_stride());
assert_eq!(40, cinfo.components()[0].col_stride());
assert_eq!(16, cinfo.components()[1].row_stride());
assert_eq!(24, cinfo.components()[1].col_stride());
assert_eq!(16, cinfo.components()[2].row_stride());
assert_eq!(24, cinfo.components()[2].col_stride());
let bitmaps = cinfo
.components()
.iter()
.map(|c| vec![128u8; c.row_stride() * c.col_stride()])
.collect::<Vec<_>>();
assert!(cinfo.write_raw_data(&bitmaps.iter().map(|c| &c[..]).collect::<Vec<_>>()));
cinfo.finish().unwrap();
}
#[test]
fn convert_colorspace() {
let mut cinfo = Compress::new(ColorSpace::JCS_RGB);
cinfo.set_color_space(ColorSpace::JCS_GRAYSCALE);
assert_eq!(1, cinfo.components().len());
cinfo.set_size(33, 15);
cinfo.set_quality(44.);
let mut cinfo = cinfo.start_compress(Vec::new()).unwrap();
let scanlines = vec![127u8; 33 * 15 * 3];
cinfo.write_scanlines(&scanlines).unwrap();
let res = cinfo.finish().unwrap();
assert!(!res.is_empty());
}