load_image 3.4.0

Load PNG or JPEG with color profile support
Documentation
use crate::cms::{Profile, Transform};
use crate::export::rgb::{Gray, GrayAlpha, Rgb, Rgba};
use crate::image::{Image, ImageData, ImageMeta};
#[cfg(any(feature = "jpeg", feature = "mozjpeg"))]
use crate::pixel_format::CMYK;
use crate::pixel_format::{ColorSpace, PixelConversion, HasPixelFormat};
use crate::profiles;
use imgref::{Img, ImgVec};
use rgb::bytemuck::cast_slice;

pub(crate) trait CopyAlpha<Converted: Copy> where Self: Copy {
    fn copy_alpha(src: &[Self], dst: &mut [Converted]);
}

macro_rules! copy_alpha_impl {
    ($in_type:ty => $out_type:ty, $fix:expr) => {
        impl CopyAlpha<$out_type> for $in_type {
            fn copy_alpha(src: &[Self], dst: &mut [$out_type]) {
                let fix = $fix;
                for (s, d) in src.iter().zip(dst.iter_mut()) {
                    fix(s, d);
                }
            }
        }
    };
}

macro_rules! copy_alpha_nop {
    ($in_type:ty => $out_type:ty) => {
        impl CopyAlpha<$out_type> for $in_type {
            fn copy_alpha(_: &[Self], _: &mut [$out_type]) {}
        }
    };
}

copy_alpha_nop! { Rgb<u8> => Rgb<u16> }
copy_alpha_nop! { Rgb<u16> => Rgb<u16> }
copy_alpha_impl! { Rgba<u8> => Rgba<u16>, |s:&Rgba<u8>, d:&mut Rgba<u16>| {d.a = u16::from(s.a) * 257} }
copy_alpha_impl! { Rgba<u16> => Rgba<u16>, |s:&Rgba<u16>, d:&mut Rgba<u16>| {d.a = s.a} }
copy_alpha_nop! { Gray<u8> => Gray<u16> }
copy_alpha_nop! { Gray<u16> => Gray<u16> }
copy_alpha_impl! { GrayAlpha<u8> => GrayAlpha<u16>, |s:&GrayAlpha<u8>,d:&mut GrayAlpha<u16>|{d.a = u16::from(s.a) * 257} }
copy_alpha_impl! { GrayAlpha<u16> => GrayAlpha<u16>, |s:&GrayAlpha<u16>,d:&mut GrayAlpha<u16>|{d.a = s.a} }

pub(crate) trait ToSRGBImage {
    fn to_image(&mut self, profile: Option<Profile>, width: usize, height: usize, discard_alpha: bool, orig_meta: ImageMeta) -> Image;
}

pub(crate) trait Convertible<Converted: Copy> {
    fn apply_profile(&self, profile: Profile) -> Option<Vec<Converted>>;
}

#[cfg(any(feature = "jpeg", feature = "mozjpeg"))]
impl ToSRGBImage for &[CMYK] {
    fn to_image(&mut self, profile: Option<Profile>, width: usize, height: usize, _opaque: bool, orig_meta: ImageMeta) -> Image {
        // The image may be CMYK, but lack any profile
        // The image may be CMYK, but with an RGB profile
        // The image may be CMYK with CMYK profile, but the profile may not work with LCMS
        // So in all cases fall back to a known good profile, since profile-less CMYK is bogus.
        let converted: Option<Vec<<CMYK as PixelConversion>::Converted>> = profile
            .and_then(|profile| self.apply_profile(profile))
            .or_else(|| self.apply_profile(Profile::new_icc(profiles::CMYK).ok()?));
        Image::from_opts(ImgVec::new(converted.expect("Unable to apply CMYK profile"), width, height), orig_meta)
    }
}

impl<T> ToSRGBImage for [T]
where
    T: HasPixelFormat + PixelConversion,
    T::Converted: HasPixelFormat + Default,
    T::ConvertedOpaque: HasPixelFormat + Default,
    Image: FromOptions<ImgVec<T>>,
    Image: FromOptions<ImgVec<T::Converted>>,
    Image: FromOptions<ImgVec<T::ConvertedOpaque>>,
    T: CopyAlpha<<T as PixelConversion>::Converted>,
{
    fn to_image(&mut self, profile: Option<Profile>, width: usize, height: usize, discard_alpha: bool, orig_meta: ImageMeta) -> Image {
        if let Some(profile) = profile {
            if discard_alpha {
                let converted: Option<Vec<T::ConvertedOpaque>> = self.apply_profile(profile);
                if let Some(pixels) = converted {
                    return Image::from_opts(ImgVec::new(pixels, width, height), orig_meta);
                }
            } else {
                let converted: Option<Vec<T::Converted>> = self.apply_profile(profile);
                if let Some(mut pixels) = converted {
                    T::copy_alpha(self, &mut pixels);
                    return Image::from_opts(ImgVec::new(pixels, width, height), orig_meta);
                }
            }
        }
        Image::from_opts(ImgVec::new(self.to_owned(), width, height), orig_meta)
    }
}

impl<T, Converted> Convertible<Converted> for [T]
where
    T: Copy + HasPixelFormat,
    Converted: Copy + HasPixelFormat + Default,
    Image: FromOptions<ImgVec<Converted>>,
{
    fn apply_profile(&self, profile: Profile) -> Option<Vec<Converted>> {
        let (format, color_space) = T::pixel_format();
        let (dest_format, _) = Converted::pixel_format();
        if profile.color_space() != color_space {
            return None;
        }
        let dest_profile = if color_space == ColorSpace::Gray {
            Profile::new_icc(profiles::GRAY).ok()?
        } else {
            Profile::new_srgb()
        };

        let t = Transform::new(&profile, format, &dest_profile, dest_format).ok()?;
        let mut dest: Vec<Converted> = vec![Default::default(); self.len()];

        t.transform_pixels(self, &mut dest).ok()?;
        Some(dest)
    }
}

impl From<Image> for Img<ImageData> {
    fn from(img: Image) -> Self {
        Self::new(img.bitmap, img.width, img.height)
    }
}

/// Convert `ImgVec` to an `Image` by providing metadata
pub trait FromOptions<T> {
    fn from_opts(t: T, options: ImageMeta) -> Self;
}

macro_rules! impl_img {
    ($ty:ty, $px:ident) => {
        impl FromOptions<ImgVec<$ty>> for Image {
            fn from_opts(bitmap: ImgVec<$ty>, meta: ImageMeta) -> Image {
                let (bitmap, width, height) = bitmap.into_contiguous_buf();
                Image {
                    width,
                    height,
                    meta,
                    bitmap: ImageData::$px(bitmap),
                }
            }
        }
    };
}

impl_img!(Rgb<u8>, RGB8);
impl_img!(Rgba<u8>, RGBA8);
impl_img!(Rgb<u16>, RGB16);
impl_img!(Rgba<u16>, RGBA16);
impl_img!(Gray<u8>, GRAY8);
impl_img!(Gray<u16>, GRAY16);
impl_img!(GrayAlpha<u8>, GRAYA8);
impl_img!(GrayAlpha<u16>, GRAYA16);

impl FromOptions<ImgVec<u8>> for Image {
    fn from_opts(bitmap: ImgVec<u8>, meta: ImageMeta) -> Self {
        let bitmap = bitmap.new_buf(cast_slice(bitmap.buf()));
        let (bitmap, width, height) = bitmap.to_contiguous_buf();
        Self {
            width,
            height,
            meta,
            bitmap: ImageData::GRAY8(bitmap.into_owned()),
        }
    }
}

impl FromOptions<ImgVec<u16>> for Image {
    fn from_opts(bitmap: ImgVec<u16>, meta: ImageMeta) -> Self {
        let bitmap = bitmap.new_buf(cast_slice(bitmap.buf()));
        let (bitmap, width, height) = bitmap.to_contiguous_buf();
        Self {
            width,
            height,
            meta,
            bitmap: ImageData::GRAY16(bitmap.into_owned()),
        }
    }
}