#![cfg(feature = "jpeg")]
use jpeg_encoder::{ColorType, EncodingError};
use zune_core::bit_depth::BitDepth;
use zune_core::bytestream::ZReaderTrait;
use zune_core::colorspace::ColorSpace;
use zune_core::log::warn;
use zune_core::options::EncoderOptions;
use zune_jpeg::errors::DecodeErrors;
pub use zune_jpeg::{ImageInfo, JpegDecoder};
use crate::codecs::{create_options_for_encoder, ImageFormat};
use crate::errors::{ImageErrors, ImgEncodeErrors};
use crate::image::Image;
use crate::metadata::ImageMetadata;
use crate::traits::{DecodeInto, DecoderTrait, EncoderTrait};
impl<T: ZReaderTrait> DecoderTrait<T> for zune_jpeg::JpegDecoder<T> {
fn decode(&mut self) -> Result<Image, crate::errors::ImageErrors> {
let metadata = self.read_headers()?.unwrap();
let pixels = self
.decode()
.map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
let colorspace = self.get_output_colorspace().unwrap();
let (width, height) = self.dimensions().unwrap();
let mut image = Image::from_u8(&pixels, width, height, colorspace);
image.metadata = metadata;
image.metadata.colorspace = self.get_output_colorspace().unwrap();
Ok(image)
}
fn dimensions(&self) -> Option<(usize, usize)> {
self.dimensions().map(|dims| (dims.0, dims.1))
}
fn out_colorspace(&self) -> ColorSpace {
self.get_output_colorspace().unwrap()
}
fn name(&self) -> &'static str {
"JPEG decoder"
}
fn read_headers(&mut self) -> Result<Option<ImageMetadata>, crate::errors::ImageErrors> {
self.decode_headers()
.map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
let (width, height) = self.dimensions().unwrap();
let mut metadata = ImageMetadata {
format: Some(ImageFormat::JPEG),
colorspace: self.get_input_colorspace().unwrap(),
depth: BitDepth::Eight,
width: width,
height: height,
..Default::default()
};
#[cfg(feature = "metadata")]
{
if let Some(exif) = self.exif() {
metadata.parse_raw_exif(exif)
}
}
Ok(Some(metadata))
}
}
impl From<zune_jpeg::errors::DecodeErrors> for ImageErrors {
fn from(from: zune_jpeg::errors::DecodeErrors) -> Self {
let err = format!("jpg: {from:?}");
ImageErrors::ImageDecodeErrors(err)
}
}
#[derive(Copy, Clone, Default)]
pub struct JpegEncoder {
options: Option<EncoderOptions>
}
impl JpegEncoder {
pub fn new() -> JpegEncoder {
JpegEncoder::default()
}
pub fn new_with_options(options: EncoderOptions) -> JpegEncoder {
JpegEncoder {
options: Some(options)
}
}
}
impl EncoderTrait for JpegEncoder {
fn name(&self) -> &'static str {
"jpeg-encoder(vstroebel)"
}
fn encode_inner(&mut self, image: &Image) -> Result<Vec<u8>, ImageErrors> {
assert_eq!(
image.depth(),
BitDepth::Eight,
"Unsupported bit depth{:?}",
image.depth()
);
let pixels = &image.flatten_frames::<u8>()[0];
if let Some(colorspace) = match_colorspace_to_colortype(image.colorspace()) {
let max_dims = usize::from(u16::MAX);
let (width, height) = image.dimensions();
if (width > max_dims) || (height > max_dims) {
let msg = format!(
"Too large image dimensions {} x {}, maximum is {} x {}",
width, height, max_dims, max_dims
);
return Err(ImgEncodeErrors::ImageEncodeErrors(msg).into());
}
let mut encoded_data =
Vec::with_capacity(width * height * image.colorspace().num_components());
let options = create_options_for_encoder(self.options, image);
let mut encoder = jpeg_encoder::Encoder::new(&mut encoded_data, options.get_quality());
encoder.set_progressive(options.jpeg_encode_progressive());
encoder.set_optimized_huffman_tables(options.jpeg_optimized_huffman_tables());
#[cfg(feature = "metadata")]
{
use exif::experimental::Writer;
if options.strip_metadata() {
} else if let Some(metadata) = &image.metadata.exif {
let mut writer = Writer::new();
let mut buf = std::io::Cursor::new(b"Exif\x00\x00".to_vec());
buf.set_position(6);
for metadatum in metadata {
writer.push_field(metadatum);
}
let result = writer.write(&mut buf, false);
if result.is_ok() {
encoder.add_app_segment(1, buf.get_ref())?;
} else {
warn!("Writing exif failed {:?}", result);
}
}
}
encoder.encode(pixels, width as u16, height as u16, colorspace)?;
Ok(encoded_data)
} else {
Err(ImgEncodeErrors::UnsupportedColorspace(
image.colorspace(),
self.supported_colorspaces()
)
.into())
}
}
fn supported_colorspaces(&self) -> &'static [ColorSpace] {
&[
ColorSpace::Luma,
ColorSpace::RGB,
ColorSpace::RGBA,
ColorSpace::YCbCr,
ColorSpace::YCCK,
ColorSpace::CMYK
]
}
fn format(&self) -> ImageFormat {
ImageFormat::JPEG
}
fn supported_bit_depth(&self) -> &'static [BitDepth] {
&[BitDepth::Eight]
}
fn default_depth(&self, _: BitDepth) -> BitDepth {
BitDepth::Eight
}
fn set_options(&mut self, options: EncoderOptions) {
self.options = Some(options)
}
}
const fn match_colorspace_to_colortype(colorspace: ColorSpace) -> Option<ColorType> {
match colorspace {
ColorSpace::RGBA => Some(ColorType::Rgba),
ColorSpace::RGB => Some(ColorType::Rgb),
ColorSpace::YCbCr => Some(ColorType::Ycbcr),
ColorSpace::Luma => Some(ColorType::Luma),
ColorSpace::YCCK => Some(ColorType::Ycck),
ColorSpace::CMYK => Some(ColorType::Cmyk),
_ => None
}
}
impl From<EncodingError> for ImageErrors {
fn from(value: EncodingError) -> Self {
ImageErrors::EncodeErrors(ImgEncodeErrors::Generic(value.to_string()))
}
}
impl<T> DecodeInto for JpegDecoder<T>
where
T: ZReaderTrait
{
fn decode_into(&mut self, buffer: &mut [u8]) -> Result<(), ImageErrors> {
self.decode_into(buffer)
.map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
Ok(())
}
fn output_buffer_size(&mut self) -> Result<usize, ImageErrors> {
self.decode_headers()
.map_err(<DecodeErrors as Into<ImageErrors>>::into)?;
Ok(self.output_buffer_size().unwrap())
}
}