use image::{error::*, *};
use zng_task::channel::IpcReadBlocking;
use zng_task::channel::{IpcBytesMut, IpcBytesMutCast};
use crate::image_cache::decode::ContainerFormat;
pub(crate) enum IpcDynamicImage {
ImageLuma8(GrayImage),
ImageLumaA8(GrayAlphaImage),
ImageRgb8(RgbImage),
ImageRgba8(RgbaImage),
ImageLuma16(Gray16Image),
ImageLumaA16(GrayAlpha16Image),
ImageRgb16(Rgb16Image),
ImageRgba16(Rgba16Image),
ImageRgb32F(Rgb32FImage),
ImageRgba32F(Rgba32FImage),
}
pub(crate) type RgbImage = ImageBuffer<Rgb<u8>, IpcBytesMut>;
pub(crate) type RgbaImage = ImageBuffer<Rgba<u8>, IpcBytesMut>;
pub(crate) type GrayImage = ImageBuffer<Luma<u8>, IpcBytesMut>;
pub(crate) type GrayAlphaImage = ImageBuffer<LumaA<u8>, IpcBytesMut>;
pub(crate) type Rgb16Image = ImageBuffer<Rgb<u16>, IpcBytesMutCast<u16>>;
pub(crate) type Rgba16Image = ImageBuffer<Rgba<u16>, IpcBytesMutCast<u16>>;
pub(crate) type Gray16Image = ImageBuffer<Luma<u16>, IpcBytesMutCast<u16>>;
pub(crate) type GrayAlpha16Image = ImageBuffer<LumaA<u16>, IpcBytesMutCast<u16>>;
pub type Rgb32FImage = ImageBuffer<Rgb<f32>, IpcBytesMutCast<f32>>;
pub type Rgba32FImage = ImageBuffer<Rgba<f32>, IpcBytesMutCast<f32>>;
macro_rules! dynamic_map(
($dynimage: expr, $image: pat => $action: expr) => ({
use IpcDynamicImage::*;
match $dynimage {
ImageLuma8($image) => ImageLuma8($action),
ImageLumaA8($image) => ImageLumaA8($action),
ImageRgb8($image) => ImageRgb8($action),
ImageRgba8($image) => ImageRgba8($action),
ImageLuma16($image) => ImageLuma16($action),
ImageLumaA16($image) => ImageLumaA16($action),
ImageRgb16($image) => ImageRgb16($action),
ImageRgba16($image) => ImageRgba16($action),
ImageRgb32F($image) => ImageRgb32F($action),
ImageRgba32F($image) => ImageRgba32F($action),
}
});
($dynimage: expr, $image:pat_param, $action: expr) => ({
use IpcDynamicImage::*;
match $dynimage {
ImageLuma8($image) => $action,
ImageLumaA8($image) => $action,
ImageRgb8($image) => $action,
ImageRgba8($image) => $action,
ImageLuma16($image) => $action,
ImageLumaA16($image) => $action,
ImageRgb16($image) => $action,
ImageRgba16($image) => $action,
ImageRgb32F($image) => $action,
ImageRgba32F($image) => $action,
}
});
);
impl IpcDynamicImage {
pub fn decode(buf: &mut IpcReadBlocking, format: ContainerFormat, entry: usize) -> image::ImageResult<Self> {
let format = match format {
ContainerFormat::Image(f) => match f {
#[cfg(feature = "image_ico")]
ImageFormat::Ico => return Self::decode_ico(buf, entry),
#[cfg(feature = "image_tiff")]
ImageFormat::Tiff => return Self::decode_tiff(buf, entry),
f => f,
},
#[cfg(feature = "image_cur")]
ContainerFormat::Cur => return Self::decode_ico(buf, entry),
};
debug_assert_eq!(entry, 0);
let _ = entry;
let mut reader = image::ImageReader::new(buf);
reader.set_format(format);
reader.no_limits();
let decoder = reader.into_decoder()?;
let (w, h) = decoder.dimensions();
let color_type = decoder.color_type();
let mut buf = Self::alloc_buf(decoder.total_bytes())?;
decoder.read_image(&mut buf[..])?;
Self::from_decoded(buf, color_type, w, h)
}
#[cfg(any(feature = "image_ico", feature = "image_cur"))]
fn decode_ico(buf: &mut IpcReadBlocking, entry: usize) -> image::ImageResult<Self> {
let icon = ico::IconDir::read(buf)?;
let entry = icon.entries()[entry].decode()?;
let (w, h) = (entry.width(), entry.height());
let buf = IpcBytesMut::from_vec_blocking(entry.into_rgba_data())?;
Self::from_decoded(buf, ColorType::Rgba8, w, h)
}
#[cfg(feature = "image_tiff")]
fn decode_tiff(buf: &mut IpcReadBlocking, entry: usize) -> image::ImageResult<Self> {
let mut tiff = tiff::decoder::Decoder::new(buf).map_err(tiff_error)?;
tiff.seek_to_image(entry).map_err(tiff_error)?;
let (w, h) = tiff.dimensions().map_err(tiff_error)?;
let color_type = tiff_color_type(&mut tiff)?;
let total_bytes = tiff.image_buffer_layout().map_err(tiff_error)?.len;
let mut buf = Self::alloc_buf(total_bytes as u64)?;
tiff.read_image_bytes(&mut buf[..]).map_err(tiff_error)?;
Self::from_decoded(buf, color_type, w, h)
}
fn from_decoded(buf: IpcBytesMut, color_type: image::ColorType, w: u32, h: u32) -> image::ImageResult<Self> {
match color_type {
ColorType::Rgb8 => ImageBuffer::from_raw(w, h, buf).map(IpcDynamicImage::ImageRgb8),
ColorType::Rgba8 => ImageBuffer::from_raw(w, h, buf).map(IpcDynamicImage::ImageRgba8),
ColorType::L8 => ImageBuffer::from_raw(w, h, buf).map(IpcDynamicImage::ImageLuma8),
ColorType::La8 => ImageBuffer::from_raw(w, h, buf).map(IpcDynamicImage::ImageLumaA8),
ColorType::Rgb16 => ImageBuffer::from_raw(w, h, buf.cast()).map(IpcDynamicImage::ImageRgb16),
ColorType::Rgba16 => ImageBuffer::from_raw(w, h, buf.cast()).map(IpcDynamicImage::ImageRgba16),
ColorType::Rgb32F => ImageBuffer::from_raw(w, h, buf.cast()).map(IpcDynamicImage::ImageRgb32F),
ColorType::Rgba32F => ImageBuffer::from_raw(w, h, buf.cast()).map(IpcDynamicImage::ImageRgba32F),
ColorType::L16 => ImageBuffer::from_raw(w, h, buf.cast()).map(IpcDynamicImage::ImageLuma16),
ColorType::La16 => ImageBuffer::from_raw(w, h, buf.cast()).map(IpcDynamicImage::ImageLumaA16),
_ => unreachable!(),
}
.ok_or_else(|| ImageError::Parameter(ParameterError::from_kind(ParameterErrorKind::DimensionMismatch)))
}
fn alloc_buf(len: u64) -> image::ImageResult<IpcBytesMut> {
if len > usize::MAX as u64 {
return Err(ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)));
}
let buf = IpcBytesMut::new_blocking(len as usize)?;
Ok(buf)
}
pub fn dimensions(&self) -> (u32, u32) {
dynamic_map!(*self, ref p, p.dimensions())
}
}
#[cfg(feature = "image_tiff")]
fn tiff_error(e: tiff::TiffError) -> image::ImageError {
match e {
tiff::TiffError::IoError(err) => image::ImageError::IoError(err),
err @ (tiff::TiffError::FormatError(_) | tiff::TiffError::IntSizeError | tiff::TiffError::UsageError(_)) => {
image::ImageError::Decoding(DecodingError::new(ImageFormat::Tiff.into(), err))
}
tiff::TiffError::UnsupportedError(desc) => image::ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Tiff.into(),
UnsupportedErrorKind::GenericFeature(desc.to_string()),
)),
tiff::TiffError::LimitsExceeded => image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::InsufficientMemory)),
}
}
#[cfg(feature = "image_tiff")]
pub(crate) fn tiff_color_type<R: std::io::Read + std::io::Seek>(tiff: &mut tiff::decoder::Decoder<R>) -> image::ImageResult<ColorType> {
fn err_unknown_color_type(value: u8) -> ImageError {
ImageError::Unsupported(UnsupportedError::from_format_and_kind(
ImageFormat::Tiff.into(),
UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)),
))
}
let color_type = match tiff.colortype().map_err(tiff_error)? {
tiff::ColorType::Gray(1) => ColorType::L8,
tiff::ColorType::Gray(8) => ColorType::L8,
tiff::ColorType::Gray(16) => ColorType::L16,
tiff::ColorType::GrayA(8) => ColorType::La8,
tiff::ColorType::GrayA(16) => ColorType::La16,
tiff::ColorType::RGB(8) => ColorType::Rgb8,
tiff::ColorType::RGB(16) => ColorType::Rgb16,
tiff::ColorType::RGBA(8) => ColorType::Rgba8,
tiff::ColorType::RGBA(16) => ColorType::Rgba16,
tiff::ColorType::CMYK(8) => ColorType::Rgb8,
tiff::ColorType::CMYK(16) => ColorType::Rgb16,
tiff::ColorType::RGB(32) => ColorType::Rgb32F,
tiff::ColorType::RGBA(32) => ColorType::Rgba32F,
tiff::ColorType::Palette(n) | tiff::ColorType::Gray(n) => return Err(err_unknown_color_type(n)),
tiff::ColorType::GrayA(n) => return Err(err_unknown_color_type(n.saturating_mul(2))),
tiff::ColorType::RGB(n) => return Err(err_unknown_color_type(n.saturating_mul(3))),
tiff::ColorType::YCbCr(n) => return Err(err_unknown_color_type(n.saturating_mul(3))),
tiff::ColorType::RGBA(n) | tiff::ColorType::CMYK(n) => return Err(err_unknown_color_type(n.saturating_mul(4))),
tiff::ColorType::Multiband { bit_depth, num_samples } => {
return Err(err_unknown_color_type(bit_depth.saturating_mul(num_samples.min(255) as u8)));
}
_ => return Err(err_unknown_color_type(0)),
};
Ok(color_type)
}
#[cfg(feature = "image_tiff")]
pub(crate) fn tiff_orientation<R: std::io::Read + std::io::Seek>(
tiff: &mut tiff::decoder::Decoder<R>,
) -> image::ImageResult<image::metadata::Orientation> {
let orientation = tiff.find_tag(tiff::tags::Tag::Orientation).map_err(tiff_error)?;
let orientation = orientation.and_then(|v| image::metadata::Orientation::from_exif(v.into_u16().ok()?.min(255) as u8));
Ok(orientation.unwrap_or(image::metadata::Orientation::NoTransforms))
}
#[cfg(feature = "image_tiff")]
pub(crate) fn tiff_density<R: std::io::Read + std::io::Seek>(tiff: &mut tiff::decoder::Decoder<R>) -> Option<zng_unit::PxDensity2d> {
use zng_unit::PxDensity2d;
use zng_unit::PxDensityUnits as _;
use tiff::{decoder::ifd::Value, tags::Tag};
let res_unit = tiff.get_tag(Tag::ResolutionUnit).ok().and_then(|t| t.into_u16().ok()).unwrap_or(2);
if let Ok(Value::Rational(x_num, x_denom)) = tiff.get_tag(Tag::XResolution)
&& let Ok(Value::Rational(y_num, y_denom)) = tiff.get_tag(Tag::YResolution)
{
let x = x_num as f32 / x_denom as f32;
let y = y_num as f32 / y_denom as f32;
match res_unit {
2 => {
return Some(PxDensity2d::new(x.ppi(), y.ppi()));
}
3 => {
return Some(PxDensity2d::new(x.ppcm(), y.ppcm()));
}
_ => {}
}
}
None
}
#[cfg(feature = "image_tiff")]
pub(crate) fn tiff_icc_profile<R: std::io::Read + std::io::Seek>(tiff: &mut tiff::decoder::Decoder<R>) -> Option<Vec<u8>> {
tiff.get_tag_u8_vec(tiff::tags::Tag::Unknown(34675)).ok()
}
#[cfg(all(feature = "image_tiff", feature = "image_meta_exif"))]
pub(crate) fn tiff_exif<R: std::io::Read + std::io::Seek>(tiff: &mut tiff::decoder::Decoder<R>) -> image::ImageResult<Option<Vec<u8>>> {
let reader = tiff.inner();
let original_pos = reader.stream_position()?;
reader.seek(std::io::SeekFrom::Start(0))?;
let exif_data = exif::Reader::new()
.read_from_container(&mut std::io::BufReader::new(&mut *reader))
.ok()
.map(|exif| exif.buf().to_vec());
reader.seek(std::io::SeekFrom::Start(original_pos))?;
Ok(exif_data)
}
#[cfg(feature = "image_jpeg")]
pub(crate) fn jpeg_density(data: &mut IpcReadBlocking) -> Option<zng_unit::PxDensity2d> {
use zng_unit::PxDensity2d;
use zng_unit::PxDensityUnits as _;
fn parse_density(reader: &mut IpcReadBlocking) -> Option<(u8, u16, u16)> {
use std::io::{Read as _, Seek as _, SeekFrom};
let mut buf = [0u8; 2];
if reader.read_exact(&mut buf[0..1]).is_err() {
return None;
}
loop {
if reader.read_exact(&mut buf[1..2]).is_err() {
break;
}
if buf[0] == 0xFF && buf[1] == 0xE0 {
let mut len_bytes = [0u8; 2];
if reader.read_exact(&mut len_bytes).is_err() {
break;
}
let len = u16::from_be_bytes(len_bytes) as usize;
if len >= 2 + 5 + 9 {
let mut header = [0u8; 14];
if reader.read_exact(&mut header).is_err() {
break;
}
if header.starts_with(b"JFIF\0") {
let unit = header[7];
let x = u16::from_be_bytes([header[8], header[9]]);
let y = u16::from_be_bytes([header[10], header[11]]);
return Some((unit, x, y));
}
if reader.seek(SeekFrom::Current((len - 16) as i64)).is_err() {
break;
}
} else if len >= 2 {
if reader.seek(SeekFrom::Current((len - 2) as i64)).is_err() {
break;
}
} else {
break;
}
if reader.read_exact(&mut buf[0..1]).is_err() {
break;
}
continue;
} else if buf[0] == 0xFF && buf[1] == 0xDA {
break;
}
buf[0] = buf[1];
}
None
}
if let Some((unit, x, y)) = parse_density(data) {
match unit {
1 => {
return Some(PxDensity2d::new((x as f32).ppi(), (y as f32).ppi()));
}
2 => {
return Some(PxDensity2d::new((x as f32).ppcm(), (y as f32).ppcm()));
}
_ => {}
}
}
None
}