use std::convert::TryInto as _;
use std::fmt;
use crate::{Image, YuvImage, raw};
use crate::common::{PixelFormat, Subsamp, Colorspace, Result, Error};
use crate::handle::Handle;
use crate::image_internal::yuv_pixels_len;
#[derive(Debug)]
#[doc(alias = "tjhandle")]
pub struct Decompressor {
handle: Handle,
scaling_factor: ScalingFactor,
}
unsafe impl Send for Decompressor {}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DecompressHeader {
#[doc(alias = "TJPARAM_JPEGWIDTH")]
pub width: usize,
#[doc(alias = "TJPARAM_JPEGHEIGHT")]
pub height: usize,
#[doc(alias = "TJPARAM_SUBSAMP")]
pub subsamp: Subsamp,
#[doc(alias = "TJPARAM_COLORSPACE")]
pub colorspace: Colorspace,
#[doc(alias = "TJPARAM_LOSSLESS")]
pub is_lossless: bool,
#[doc(alias = "TJPARAM_PROGRESSIVE")]
pub is_progressive: bool,
#[doc(alias = "TJPARAM_ARITHMETIC")]
pub is_arithmetic: bool,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[doc(alias = "tjscalingfactor")]
pub struct ScalingFactor {
num: usize,
denom: usize,
}
impl ScalingFactor {
pub const ONE: Self = Self { num: 1, denom: 1 };
pub const ONE_HALF: Self = Self { num: 1, denom: 2 };
pub const ONE_QUARTER: Self = Self { num: 1, denom: 4 };
pub const ONE_EIGHTH: Self = Self { num: 1, denom: 8 };
pub const TWO: Self = Self { num: 2, denom: 1 };
pub fn new(num: usize, denom: usize) -> Self {
let gcd = gcd::binary_usize(num, denom);
Self { num: num / gcd, denom: denom / gcd }
}
pub fn num(&self) -> usize {
self.num
}
pub fn denom(&self) -> usize {
self.denom
}
#[doc(alias = "TJSCALED")]
pub fn scale(&self, dimension: usize) -> usize {
(dimension * self.num + self.denom - 1) / self.denom
}
}
impl fmt::Display for ScalingFactor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.num, self.denom)
}
}
impl DecompressHeader {
#[doc(alias = "TJSCALED")]
pub fn scaled(&self, factor: ScalingFactor) -> Self {
Self {
width: factor.scale(self.width),
height: factor.scale(self.height),
.. *self
}
}
}
impl Decompressor {
#[doc(alias = "tj3Init")]
pub fn new() -> Result<Decompressor> {
let handle = Handle::new(raw::TJINIT_TJINIT_DECOMPRESS)?;
Ok(Self { handle, scaling_factor: ScalingFactor::ONE })
}
#[doc(alias = "tj3DecompressHeader")]
pub fn read_header(&mut self, jpeg_data: &[u8]) -> Result<DecompressHeader> {
let jpeg_data_len = jpeg_data.len().try_into()
.map_err(|_| Error::IntegerOverflow("jpeg_data.len()"))?;
let res = unsafe {
raw::tj3DecompressHeader(self.handle.as_ptr(), jpeg_data.as_ptr(), jpeg_data_len)
};
if res != 0 {
return Err(self.handle.get_error())
}
let width = self.handle.get(raw::TJPARAM_TJPARAM_JPEGWIDTH)
.try_into().map_err(|_| Error::IntegerOverflow("width"))?;
let height = self.handle.get(raw::TJPARAM_TJPARAM_JPEGHEIGHT)
.try_into().map_err(|_| Error::IntegerOverflow("height"))?;
let subsamp = Subsamp::from_int(self.handle.get(raw::TJPARAM_TJPARAM_SUBSAMP))?;
let colorspace = Colorspace::from_int(self.handle.get(raw::TJPARAM_TJPARAM_COLORSPACE))?;
let is_lossless = self.handle.get(raw::TJPARAM_TJPARAM_LOSSLESS) != 0;
let is_progressive = self.handle.get(raw::TJPARAM_TJPARAM_PROGRESSIVE) != 0;
let is_arithmetic = self.handle.get(raw::TJPARAM_TJPARAM_ARITHMETIC) != 0;
Ok(DecompressHeader {
width, height, subsamp, colorspace,
is_lossless, is_progressive, is_arithmetic,
})
}
#[doc(alias = "tj3SetScalingFactor")]
pub fn set_scaling_factor(&mut self, scaling_factor: ScalingFactor) -> Result<()> {
let num: libc::c_int = scaling_factor.num.try_into()
.map_err(|_| Error::IntegerOverflow("num"))?;
let denom: libc::c_int = scaling_factor.denom.try_into()
.map_err(|_| Error::IntegerOverflow("denom"))?;
self.handle.set_scaling_factor(raw::tjscalingfactor { num, denom })?;
self.scaling_factor = scaling_factor;
Ok(())
}
pub fn scaling_factor(&self) -> ScalingFactor {
self.scaling_factor
}
pub fn set_fast_upsample(&mut self, fast_upsample: bool) -> Result<()> {
self.handle.set(raw::TJPARAM_TJPARAM_FASTUPSAMPLE, fast_upsample as libc::c_int)
}
pub fn set_scan_limit(&mut self, scan_limit: u32) -> Result<()> {
let scan_limit = scan_limit.try_into().map_err(|_| Error::IntegerOverflow("scan limit"))?;
self.handle.set(raw::TJPARAM_TJPARAM_SCANLIMIT, scan_limit)
}
#[doc(alias = "tj3Decompress8")]
pub fn decompress(&mut self, jpeg_data: &[u8], output: Image<&mut [u8]>) -> Result<()> {
output.assert_valid(output.pixels.len());
let Image { pixels, width, pitch, height, format } = output;
let width: libc::c_int = width.try_into().map_err(|_| Error::IntegerOverflow("width"))?;
let pitch: libc::c_int = pitch.try_into().map_err(|_| Error::IntegerOverflow("pitch"))?;
let height: libc::c_int = height.try_into().map_err(|_| Error::IntegerOverflow("height"))?;
let jpeg_data_len: raw::size_t = jpeg_data.len().try_into()
.map_err(|_| Error::IntegerOverflow("jpeg_data.len()"))?;
self.check_output_size(jpeg_data, width, height)?;
let res = unsafe {
raw::tj3Decompress8(
self.handle.as_ptr(),
jpeg_data.as_ptr(), jpeg_data_len,
pixels.as_mut_ptr(), pitch, format as i32,
)
};
if res != 0 {
return Err(self.handle.get_error())
}
Ok(())
}
#[doc(alias = "tj3DecompressToYUV8")]
pub fn decompress_to_yuv(&mut self, jpeg_data: &[u8], output: YuvImage<&mut [u8]>) -> Result<()> {
output.assert_valid(output.pixels.len());
let YuvImage { pixels, width, align, height, subsamp: _ } = output;
let width: libc::c_int = width.try_into().map_err(|_| Error::IntegerOverflow("width"))?;
let align: libc::c_int = align.try_into().map_err(|_| Error::IntegerOverflow("align"))?;
let height: libc::c_int = height.try_into().map_err(|_| Error::IntegerOverflow("height"))?;
let jpeg_data_len: raw::size_t = jpeg_data.len().try_into()
.map_err(|_| Error::IntegerOverflow("jpeg_data.len()"))?;
self.check_output_size(jpeg_data, width, height)?;
let res = unsafe {
raw::tj3DecompressToYUV8(
self.handle.as_ptr(),
jpeg_data.as_ptr(), jpeg_data_len,
pixels.as_mut_ptr(), align,
)
};
if res != 0 {
return Err(self.handle.get_error())
}
Ok(())
}
fn check_output_size(&mut self, jpeg_data: &[u8], width: libc::c_int, height: libc::c_int) -> Result<()> {
let header = self.read_header(jpeg_data)?;
if header.is_lossless && self.scaling_factor != ScalingFactor::ONE {
return Err(Error::CannotScaleLossless)
}
let scaled_width = self.scaling_factor.scale(header.width);
let scaled_height = self.scaling_factor.scale(header.height);
if width < scaled_width as i32 || height < scaled_height as i32 {
return Err(Error::OutputTooSmall(scaled_width as i32, scaled_height as i32))
}
Ok(())
}
#[doc(alias = "tj3GetScalingFactors")]
pub fn supported_scaling_factors() -> Vec<ScalingFactor> {
let mut count: libc::c_int = 0;
let ptr: *const raw::tjscalingfactor = unsafe {
raw::tj3GetScalingFactors(&mut count as *mut _)
};
let count: usize = count.try_into()
.expect("tj3GetScalingFactors() returned a number that cannot be converted to usize");
let mut list = Vec::with_capacity(count);
for i in 0..count {
let factor = unsafe { ptr.add(i).read() };
let num: usize = factor.num.try_into()
.expect("Numerator of a tjscalingfactor cannot be converted to usize");
let denom: usize = factor.denom.try_into()
.expect("Denominator of a tjscalingfactor cannot be converted to usize");
list.push(ScalingFactor { num, denom });
}
list
}
}
pub fn decompress(jpeg_data: &[u8], format: PixelFormat) -> Result<Image<Vec<u8>>> {
let mut decompressor = Decompressor::new()?;
let header = decompressor.read_header(jpeg_data)?;
let pitch = header.width * format.size();
let mut image = Image {
pixels: vec![0; header.height * pitch],
width: header.width,
pitch,
height: header.height,
format,
};
decompressor.decompress(jpeg_data, image.as_deref_mut())?;
Ok(image)
}
pub fn decompress_to_yuv(jpeg_data: &[u8]) -> Result<YuvImage<Vec<u8>>> {
let mut decompressor = Decompressor::new()?;
let header = decompressor.read_header(jpeg_data)?;
let align = 4;
let yuv_pixels_len = yuv_pixels_len(
header.width,
align,
header.height,
header.subsamp,
)?;
let mut yuv_image = YuvImage {
pixels: vec![0; yuv_pixels_len],
width: header.width,
align,
height: header.height,
subsamp: header.subsamp,
};
decompressor.decompress_to_yuv(jpeg_data, yuv_image.as_deref_mut())?;
Ok(yuv_image)
}
pub fn read_header(jpeg_data: &[u8]) -> Result<DecompressHeader> {
let mut decompressor = Decompressor::new()?;
decompressor.read_header(jpeg_data)
}