pub mod bitstream;
pub mod huffman;
pub mod idct;
pub mod markers;
pub mod mcu;
pub mod types;
#[cfg(all(target_os = "linux", feature = "v4l2"))]
mod v4l2;
#[cfg(all(target_os = "linux", feature = "nvjpeg"))]
mod nvjpeg;
#[cfg(all(target_os = "linux", feature = "nvjpeg"))]
pub(crate) use nvjpeg::is_nvjpeg_available as nvjpeg_available;
#[cfg(all(target_os = "linux", any(feature = "v4l2", feature = "nvjpeg")))]
pub(crate) fn env_flag(name: &str) -> bool {
std::env::var(name)
.map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes"))
.unwrap_or(false)
}
use crate::error::{CodecError, UnsupportedFeature};
use crate::exif::read_exif_orientation;
use crate::options::ImageInfo;
use crate::pixel::ImagePixel;
use edgefirst_tensor::{PixelFormat, Tensor, TensorTrait};
pub struct JpegDecoderState {
mcu_scratch: Option<mcu::McuScratch>,
#[cfg(all(target_os = "linux", feature = "v4l2"))]
v4l2: v4l2::V4l2Probe,
#[cfg(all(target_os = "linux", feature = "nvjpeg"))]
nvjpeg: nvjpeg::NvJpegProbe,
}
impl JpegDecoderState {
pub fn new() -> Self {
Self {
mcu_scratch: None,
#[cfg(all(target_os = "linux", feature = "v4l2"))]
v4l2: v4l2::V4l2Probe::default(),
#[cfg(all(target_os = "linux", feature = "nvjpeg"))]
nvjpeg: nvjpeg::NvJpegProbe::default(),
}
}
}
impl Default for JpegDecoderState {
fn default() -> Self {
Self::new()
}
}
fn native_format(headers: &markers::JpegHeaders) -> crate::Result<PixelFormat> {
let comps = &headers.header.components;
match comps.len() {
1 => Ok(PixelFormat::Grey),
3 => {
let max_h = headers.header.max_h_samp as usize;
let max_v = headers.header.max_v_samp as usize;
let cb = comps[1].sampling;
let cr = comps[2].sampling;
if cb.h == cr.h && cb.v == cr.v && cb.h as usize > 0 && cb.v as usize > 0 {
let h_ratio = max_h / cb.h as usize;
let v_ratio = max_v / cb.v as usize;
return Ok(match (h_ratio, v_ratio) {
(1, 1) => PixelFormat::Nv24, (2, 1) => PixelFormat::Nv16, (2, 2) => PixelFormat::Nv12, _ => PixelFormat::Nv12, });
}
Ok(PixelFormat::Nv12)
}
n => Err(CodecError::Unsupported(
UnsupportedFeature::JpegComponentCount {
components: n as u8,
},
)),
}
}
fn native_row_stride(width: usize) -> usize {
width.next_multiple_of(2).next_multiple_of(64)
}
pub fn peek_jpeg_info(data: &[u8]) -> crate::Result<ImageInfo> {
let headers = markers::parse_markers(data)?;
let img_w = headers.header.width as usize;
let img_h = headers.header.height as usize;
let format = native_format(&headers)?;
let (rotation_degrees, flip_horizontal) = headers
.exif_data
.as_deref()
.map(read_exif_orientation)
.unwrap_or((0, false));
Ok(ImageInfo {
width: img_w,
height: img_h,
format,
row_stride: native_row_stride(img_w),
rotation_degrees,
flip_horizontal,
})
}
pub fn decode_jpeg_into<T: ImagePixel>(
data: &[u8],
dst: &mut Tensor<T>,
state: &mut JpegDecoderState,
) -> crate::Result<ImageInfo> {
let _span = tracing::trace_span!(
"codec.decode_jpeg",
dtype = std::any::type_name::<T>(),
n_bytes = data.len(),
)
.entered();
let headers = {
let _s = tracing::trace_span!("codec.decode_jpeg.parse_markers").entered();
markers::parse_markers(data)?
};
let img_w = headers.header.width as usize;
let img_h = headers.header.height as usize;
let output_fmt = native_format(&headers)?;
if T::dtype() != edgefirst_tensor::DType::U8 {
return Err(CodecError::UnsupportedDtype(T::dtype()));
}
let (rotation_degrees, flip_horizontal) = headers
.exif_data
.as_deref()
.map(read_exif_orientation)
.unwrap_or((0, false));
let cap_w = dst.width().unwrap_or(0);
let cap_h = dst.height().unwrap_or(0);
dst.configure_image(img_w, img_h, output_fmt)
.map_err(|e| match e {
edgefirst_tensor::Error::InsufficientCapacity { .. } => {
CodecError::InsufficientCapacity {
image: (img_w, img_h),
tensor: (cap_w, cap_h),
}
}
other => CodecError::Tensor(other),
})?;
let dst_stride = dst
.effective_row_stride()
.unwrap_or(native_row_stride(img_w));
#[cfg(all(target_os = "linux", feature = "nvjpeg"))]
{
match state
.nvjpeg
.try_decode::<T>(data, &headers, dst, output_fmt, img_w, img_h, dst_stride)
{
Ok(Some(mut info)) => {
info.rotation_degrees = rotation_degrees;
info.flip_horizontal = flip_horizontal;
dst.set_colorimetry(None);
return Ok(info);
}
Ok(None) => {}
Err(nvjpeg::NvJpegDecode::Fallback(why)) => {
log::debug!("nvjpeg jpeg decode fell back: {why}");
}
Err(nvjpeg::NvJpegDecode::Fatal(e)) => return Err(e),
}
}
#[cfg(all(target_os = "linux", feature = "v4l2"))]
{
match state
.v4l2
.try_decode::<T>(data, &headers, dst, output_fmt, img_w, img_h, dst_stride)
{
Ok(Some(mut info)) => {
info.rotation_degrees = rotation_degrees;
info.flip_horizontal = flip_horizontal;
dst.set_colorimetry(Some(edgefirst_tensor::Colorimetry::jfif()));
return Ok(info);
}
Ok(None) => {}
Err(v4l2::V4l2Decode::Fallback(why)) => {
log::debug!("v4l2 jpeg decode fell back to cpu: {why}");
}
Err(v4l2::V4l2Decode::Fatal(e)) => return Err(e),
}
}
match &mut state.mcu_scratch {
Some(scratch) => scratch.ensure_capacity(&headers),
None => state.mcu_scratch = Some(mcu::McuScratch::new(&headers)),
}
let mcu_scratch = state.mcu_scratch.as_mut().unwrap();
{
let mut map = dst.map_write()?;
let dst_bytes: &mut [T] = &mut map;
let dst_u8: &mut [u8] = unsafe {
std::slice::from_raw_parts_mut(dst_bytes.as_mut_ptr() as *mut u8, dst_bytes.len())
};
let _s = tracing::trace_span!("codec.decode_jpeg.mcu_loop").entered();
mcu::decode_image(data, &headers, mcu_scratch, dst_u8, dst_stride, output_fmt)?;
}
dst.set_colorimetry(Some(edgefirst_tensor::Colorimetry::jfif()));
Ok(ImageInfo {
width: img_w,
height: img_h,
format: output_fmt,
row_stride: dst_stride,
rotation_degrees,
flip_horizontal,
})
}