1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
use crate::image::*;
use lcms2::*;
use std::fs;
use std::io;
use std::io::Read;
use std::path::Path;

#[derive(Eq, PartialEq)]
pub enum Profiles {
    /// Apply all profiles
    All,
    /// Do not support profiles (gives incorrectly-looking images, but doesn't change pixel values)
    None,
    /// Apply profiles only if they don't appear to be sRGB
    NonsRGB,
}

pub struct Loader {
    pub(crate) opaque: bool,
    pub(crate) profiles: Profiles,
}

impl Loader {
    #[inline(always)]
    pub fn new() -> Self {
        Loader {
            opaque: false,
            profiles: Profiles::NonsRGB,
        }
    }

    /// If true, alpha channel will be discarded
    #[inline(always)]
    pub fn opaque(&mut self, v: bool) -> &mut Self {
        self.opaque = v;
        self
    }

    /// Strategy for converting color profiles
    #[inline(always)]
    pub fn profiles(&mut self, convert_profiles: Profiles) -> &mut Self {
        self.profiles = convert_profiles;
        self
    }

    pub fn load_path<P: AsRef<Path>>(&self, path: P) -> Result<Image, crate::Error> {
        let path = path.as_ref();
        let mut data = Vec::new();
        let (data, stat) = if path.as_os_str() == "-" {
            io::stdin().read_to_end(&mut data)?;
            (data, None)
        } else {
            let mut file = fs::File::open(path)?;
            let stat = file.metadata()?;
            file.read_to_end(&mut data)?;
            (data, Some(stat))
        };
        self.load_data_with_stat(&data, stat)
    }

    #[inline(always)]
    pub fn load_data(&self, data: &[u8]) -> Result<Image, crate::Error> {
        self.load_data_with_stat(data, None)
    }

    fn load_data_with_stat(&self, data: &[u8], meta: Option<fs::Metadata>) -> Result<Image, crate::Error> {
        if data.starts_with(b"\x89PNG") {
            return self.load_png(data, meta);
        }

        #[cfg(feature = "avif")]
        if data.get(4..4+8) == Some(b"ftypavif") {
            return self.load_avif(data, meta).map_err(|_| crate::Error::new(28));
        }

        #[cfg(feature = "webp")]
        if data.get(0..4) == Some(b"RIFF") {
            return self.load_webp(data, meta).map_err(|_| crate::Error::new(28));
        }

        if data.get(0) == Some(&0xFF) {
            self.load_jpeg(data, meta)
        } else {
            Err(crate::Error::new(28))
        }
    }

    pub(crate) fn process_profile(&self, profile: LCMSResult<Profile>) -> Option<Profile> {
        match profile {
            Err(_) => None,
            Ok(profile) => {
                if self.profiles == Profiles::NonsRGB {
                    if let Some(desc) = profile.info(InfoType::Description, Locale::new("en_US")) {
                        if desc.starts_with("sRGB ") {
                            return None;
                        }
                    }
                }
                if self.profiles == Profiles::None {
                    return None;
                }
                Some(profile)
            }
        }
    }
}