use crate::error::CodecError;
#[cfg(not(feature = "png"))]
use crate::error::UnsupportedFeature;
use crate::options::ImageInfo;
use crate::pixel::ImagePixel;
use edgefirst_tensor::{DType, Tensor, TensorDyn};
use std::io::Read;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DctMethod {
#[default]
Accurate,
Fast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ChromaUpsample {
#[default]
Box,
}
pub struct ImageDecoder {
pub(crate) jpeg_state: crate::jpeg::JpegDecoderState,
pub(crate) scratch: Vec<u8>,
pub(crate) input_buffer: Vec<u8>,
}
impl ImageDecoder {
pub fn new() -> Self {
Self {
jpeg_state: crate::jpeg::JpegDecoderState::new(),
scratch: Vec::new(),
input_buffer: Vec::new(),
}
}
pub fn set_output_format(&mut self, format: Option<edgefirst_tensor::PixelFormat>) {
self.jpeg_state.preferred_format = format;
}
pub fn set_dct_method(&mut self, method: DctMethod) {
self.jpeg_state.fast_dct = method == DctMethod::Fast;
}
pub fn set_chroma_upsample(&mut self, mode: ChromaUpsample) {
self.jpeg_state.chroma_upsample = mode;
}
pub fn decode_into<T: ImagePixel>(
&mut self,
data: &[u8],
dst: &mut Tensor<T>,
) -> crate::Result<ImageInfo> {
if is_jpeg(data) {
crate::jpeg::decode_jpeg_into(data, dst, &mut self.jpeg_state)
} else if is_png(data) {
{
#[cfg(feature = "png")]
{
crate::png::decode_png_into(data, dst, &mut self.scratch)
}
#[cfg(not(feature = "png"))]
{
Err(CodecError::Unsupported(UnsupportedFeature::PngNotEnabled))
}
}
} else {
Err(CodecError::InvalidData(
"unrecognized image format (expected JPEG or PNG magic bytes)".into(),
))
}
}
pub fn decode_into_dyn(
&mut self,
data: &[u8],
dst: &mut TensorDyn,
) -> crate::Result<ImageInfo> {
match dst.dtype() {
DType::U8 => self.decode_into(data, dst.as_typed_mut::<u8>().expect("dtype checked")),
DType::I8 => self.decode_into(data, dst.as_typed_mut::<i8>().expect("dtype checked")),
DType::U16 => self.decode_into(data, dst.as_typed_mut::<u16>().expect("dtype checked")),
DType::I16 => self.decode_into(data, dst.as_typed_mut::<i16>().expect("dtype checked")),
DType::F32 => self.decode_into(data, dst.as_typed_mut::<f32>().expect("dtype checked")),
other => Err(CodecError::UnsupportedDtype(other)),
}
}
pub fn decode_from_reader<T: ImagePixel, R: Read>(
&mut self,
mut reader: R,
dst: &mut Tensor<T>,
) -> crate::Result<ImageInfo> {
self.input_buffer.clear();
reader.read_to_end(&mut self.input_buffer)?;
decode_into_inner(
&mut self.jpeg_state,
&mut self.scratch,
&self.input_buffer,
dst,
)
}
pub fn decode_from_reader_dyn<R: Read>(
&mut self,
mut reader: R,
dst: &mut TensorDyn,
) -> crate::Result<ImageInfo> {
self.input_buffer.clear();
reader.read_to_end(&mut self.input_buffer)?;
decode_into_inner_dyn(
&mut self.jpeg_state,
&mut self.scratch,
&self.input_buffer,
dst,
)
}
}
pub(crate) fn decode_into_inner<T: ImagePixel>(
jpeg_state: &mut crate::jpeg::JpegDecoderState,
scratch: &mut Vec<u8>,
data: &[u8],
dst: &mut Tensor<T>,
) -> crate::Result<ImageInfo> {
if is_jpeg(data) {
crate::jpeg::decode_jpeg_into(data, dst, jpeg_state)
} else if is_png(data) {
{
#[cfg(feature = "png")]
{
crate::png::decode_png_into(data, dst, scratch)
}
#[cfg(not(feature = "png"))]
{
let _ = scratch;
Err(CodecError::Unsupported(UnsupportedFeature::PngNotEnabled))
}
}
} else {
Err(CodecError::InvalidData(
"unrecognized image format (expected JPEG or PNG magic bytes)".into(),
))
}
}
pub(crate) fn decode_into_inner_dyn(
jpeg_state: &mut crate::jpeg::JpegDecoderState,
scratch: &mut Vec<u8>,
data: &[u8],
dst: &mut TensorDyn,
) -> crate::Result<ImageInfo> {
match dst.dtype() {
DType::U8 => decode_into_inner(
jpeg_state,
scratch,
data,
dst.as_typed_mut::<u8>().expect("dtype checked"),
),
DType::I8 => decode_into_inner(
jpeg_state,
scratch,
data,
dst.as_typed_mut::<i8>().expect("dtype checked"),
),
DType::U16 => decode_into_inner(
jpeg_state,
scratch,
data,
dst.as_typed_mut::<u16>().expect("dtype checked"),
),
DType::I16 => decode_into_inner(
jpeg_state,
scratch,
data,
dst.as_typed_mut::<i16>().expect("dtype checked"),
),
DType::F32 => decode_into_inner(
jpeg_state,
scratch,
data,
dst.as_typed_mut::<f32>().expect("dtype checked"),
),
other => Err(CodecError::UnsupportedDtype(other)),
}
}
impl Default for ImageDecoder {
fn default() -> Self {
Self::new()
}
}
pub fn peek_info(data: &[u8]) -> crate::Result<ImageInfo> {
if is_jpeg(data) {
crate::jpeg::peek_jpeg_info(data)
} else if is_png(data) {
{
#[cfg(feature = "png")]
{
crate::png::peek_png_info(data)
}
#[cfg(not(feature = "png"))]
{
Err(CodecError::Unsupported(UnsupportedFeature::PngNotEnabled))
}
}
} else {
Err(CodecError::InvalidData(
"unrecognized image format (expected JPEG or PNG magic bytes)".into(),
))
}
}
fn is_jpeg(data: &[u8]) -> bool {
data.len() >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF
}
fn is_png(data: &[u8]) -> bool {
data.len() >= 4 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn magic_bytes_jpeg() {
assert!(is_jpeg(&[0xFF, 0xD8, 0xFF, 0xE0]));
assert!(!is_jpeg(&[0x89, 0x50, 0x4E, 0x47]));
assert!(!is_jpeg(&[]));
assert!(!is_jpeg(&[0xFF]));
}
#[test]
fn magic_bytes_png() {
assert!(is_png(&[0x89, 0x50, 0x4E, 0x47]));
assert!(!is_png(&[0xFF, 0xD8, 0xFF, 0xE0]));
assert!(!is_png(&[]));
}
#[test]
fn invalid_data() {
let mut decoder = ImageDecoder::new();
let mut tensor = Tensor::<u8>::image(
100,
100,
edgefirst_tensor::PixelFormat::Nv12,
Some(edgefirst_tensor::TensorMemory::Mem),
edgefirst_tensor::CpuAccess::ReadWrite,
)
.unwrap();
let result = decoder.decode_into(b"not an image", &mut tensor);
assert!(matches!(result, Err(CodecError::InvalidData(_))));
}
}