zng-view 0.17.1

Part of the zng project.
Documentation
// like image::DynamicImage, but with IpcBytesMut storage

use image::{error::*, *};
use zng_task::channel::IpcReadBlocking;
use zng_task::channel::{IpcBytesMut, IpcBytesMutCast};

use crate::image_cache::decode::ContainerFormat;

pub(crate) enum IpcDynamicImage {
    /// Each pixel in this image is 8-bit Luma
    ImageLuma8(GrayImage),

    /// Each pixel in this image is 8-bit Luma with alpha
    ImageLumaA8(GrayAlphaImage),

    /// Each pixel in this image is 8-bit Rgb
    ImageRgb8(RgbImage),

    /// Each pixel in this image is 8-bit Rgb with alpha
    ImageRgba8(RgbaImage),

    /// Each pixel in this image is 16-bit Luma
    ImageLuma16(Gray16Image),

    /// Each pixel in this image is 16-bit Luma with alpha
    ImageLumaA16(GrayAlpha16Image),

    /// Each pixel in this image is 16-bit Rgb
    ImageRgb16(Rgb16Image),

    /// Each pixel in this image is 16-bit Rgb with alpha
    ImageRgba16(Rgba16Image),

    /// Each pixel in this image is 32-bit float Rgb
    ImageRgb32F(Rgb32FImage),

    /// Each pixel in this image is 32-bit float Rgb with alpha
    ImageRgba32F(Rgba32FImage),
}

/// Sendable Rgb image buffer
pub(crate) type RgbImage = ImageBuffer<Rgb<u8>, IpcBytesMut>;
/// Sendable Rgb + alpha channel image buffer
pub(crate) type RgbaImage = ImageBuffer<Rgba<u8>, IpcBytesMut>;
/// Sendable grayscale image buffer
pub(crate) type GrayImage = ImageBuffer<Luma<u8>, IpcBytesMut>;
/// Sendable grayscale + alpha channel image buffer
pub(crate) type GrayAlphaImage = ImageBuffer<LumaA<u8>, IpcBytesMut>;
/// Sendable 16-bit Rgb image buffer
pub(crate) type Rgb16Image = ImageBuffer<Rgb<u16>, IpcBytesMutCast<u16>>;
/// Sendable 16-bit Rgb + alpha channel image buffer
pub(crate) type Rgba16Image = ImageBuffer<Rgba<u16>, IpcBytesMutCast<u16>>;
/// Sendable 16-bit grayscale image buffer
pub(crate) type Gray16Image = ImageBuffer<Luma<u16>, IpcBytesMutCast<u16>>;
/// Sendable 16-bit grayscale + alpha channel image buffer
pub(crate) type GrayAlpha16Image = ImageBuffer<LumaA<u16>, IpcBytesMutCast<u16>>;

/// An image buffer for 32-bit float RGB pixels,
/// where the backing container is a flattened vector of floats.
pub type Rgb32FImage = ImageBuffer<Rgb<f32>, IpcBytesMutCast<f32>>;

/// An image buffer for 32-bit float RGBA pixels,
/// where the backing container is a flattened vector of floats.
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> {
        // copied from image-0.25.9\src\images\dynimage.rs
        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 {
    // https://docs.rs/image/0.25.9/src/image/codecs/tiff.rs.html#190
    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> {
    // https://docs.rs/image/0.25.9/src/image/codecs/tiff.rs.html#182
    fn err_unknown_color_type(value: u8) -> ImageError {
        ImageError::Unsupported(UnsupportedError::from_format_and_kind(
            ImageFormat::Tiff.into(),
            UnsupportedErrorKind::Color(ExtendedColorType::Unknown(value)),
        ))
    }
    // https://docs.rs/image/0.25.9/src/image/codecs/tiff.rs.html#74
    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> {
    // https://docs.rs/image/latest/src/image/codecs/tiff.rs.html#290
    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 {
            // inches
            2 => {
                return Some(PxDensity2d::new(x.ppi(), y.ppi()));
            }
            // centimeters
            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>> {
    // https://docs.rs/image/latest/src/image/codecs/tiff.rs.html#264
    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 _;

    // `image` uses `zune-jpeg`, that decoder does not parse density correctly,
    // so we do it manually here
    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 {
                // APP0 marker
                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;

                // 2 for length, 5 for "JFIF\0", 9 for the rest
                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));
                    }

                    // not JFIF, skip
                    if reader.seek(SeekFrom::Current((len - 16) as i64)).is_err() {
                        break;
                    }
                } else if len >= 2 {
                    // skip invalid APP0
                    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 {
                // SOS marker
                break;
            }

            buf[0] = buf[1];
        }

        None
    }
    if let Some((unit, x, y)) = parse_density(data) {
        match unit {
            // inches
            1 => {
                return Some(PxDensity2d::new((x as f32).ppi(), (y as f32).ppi()));
            }
            // centimeters
            2 => {
                return Some(PxDensity2d::new((x as f32).ppcm(), (y as f32).ppcm()));
            }
            _ => {}
        }
    }
    None
}