iced_graphics/
image.rs

1//! Load and operate on images.
2#[cfg(feature = "image")]
3use crate::core::Bytes;
4
5use crate::core::Rectangle;
6use crate::core::image;
7use crate::core::svg;
8
9/// A raster or vector image.
10#[allow(missing_docs)]
11#[derive(Debug, Clone, PartialEq)]
12pub enum Image {
13    /// A raster image.
14    Raster {
15        image: image::Image,
16        bounds: Rectangle,
17        clip_bounds: Rectangle,
18    },
19
20    /// A vector image.
21    Vector {
22        svg: svg::Svg,
23        bounds: Rectangle,
24        clip_bounds: Rectangle,
25    },
26}
27
28impl Image {
29    /// Returns the bounds of the [`Image`].
30    pub fn bounds(&self) -> Rectangle {
31        match self {
32            Image::Raster { image, bounds, .. } => bounds.rotate(image.rotation),
33            Image::Vector { svg, bounds, .. } => bounds.rotate(svg.rotation),
34        }
35    }
36}
37
38/// An image buffer.
39#[cfg(feature = "image")]
40pub type Buffer = ::image::ImageBuffer<::image::Rgba<u8>, Bytes>;
41
42#[cfg(feature = "image")]
43/// Tries to load an image by its [`Handle`].
44///
45/// [`Handle`]: image::Handle
46pub fn load(handle: &image::Handle) -> Result<Buffer, image::Error> {
47    use bitflags::bitflags;
48
49    bitflags! {
50        struct Operation: u8 {
51            const FLIP_HORIZONTALLY = 0b1;
52            const ROTATE_180 = 0b10;
53            const FLIP_VERTICALLY= 0b100;
54            const ROTATE_90 = 0b1000;
55            const ROTATE_270 = 0b10000;
56        }
57    }
58
59    impl Operation {
60        // Meaning of the returned value is described e.g. at:
61        // https://magnushoff.com/articles/jpeg-orientation/
62        fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
63        where
64            R: std::io::BufRead + std::io::Seek,
65        {
66            let exif = exif::Reader::new().read_from_container(reader)?;
67
68            Ok(exif
69                .get_field(exif::Tag::Orientation, exif::In::PRIMARY)
70                .and_then(|field| field.value.get_uint(0))
71                .and_then(|value| u8::try_from(value).ok())
72                .map(|value| match value {
73                    1 => Operation::empty(),
74                    2 => Operation::FLIP_HORIZONTALLY,
75                    3 => Operation::ROTATE_180,
76                    4 => Operation::FLIP_VERTICALLY,
77                    5 => Operation::ROTATE_90 | Operation::FLIP_HORIZONTALLY,
78                    6 => Operation::ROTATE_90,
79                    7 => Operation::ROTATE_90 | Operation::FLIP_VERTICALLY,
80                    8 => Operation::ROTATE_270,
81                    _ => Operation::empty(),
82                })
83                .unwrap_or_else(Self::empty))
84        }
85
86        fn perform(self, mut image: ::image::DynamicImage) -> ::image::DynamicImage {
87            use ::image::imageops;
88
89            if self.contains(Operation::ROTATE_90) {
90                image = imageops::rotate90(&image).into();
91            }
92
93            if self.contains(Self::ROTATE_180) {
94                imageops::rotate180_in_place(&mut image);
95            }
96
97            if self.contains(Operation::ROTATE_270) {
98                image = imageops::rotate270(&image).into();
99            }
100
101            if self.contains(Self::FLIP_VERTICALLY) {
102                imageops::flip_vertical_in_place(&mut image);
103            }
104
105            if self.contains(Self::FLIP_HORIZONTALLY) {
106                imageops::flip_horizontal_in_place(&mut image);
107            }
108
109            image
110        }
111    }
112
113    let (width, height, pixels) = match handle {
114        image::Handle::Path(_, path) => {
115            let image = ::image::open(path).map_err(to_error)?;
116
117            let operation = std::fs::File::open(path)
118                .ok()
119                .map(std::io::BufReader::new)
120                .and_then(|mut reader| Operation::from_exif(&mut reader).ok())
121                .unwrap_or_else(Operation::empty);
122
123            let rgba = operation.perform(image).into_rgba8();
124
125            (rgba.width(), rgba.height(), Bytes::from(rgba.into_raw()))
126        }
127        image::Handle::Bytes(_, bytes) => {
128            let image = ::image::load_from_memory(bytes).map_err(to_error)?;
129
130            let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes))
131                .ok()
132                .unwrap_or_else(Operation::empty);
133
134            let rgba = operation.perform(image).into_rgba8();
135
136            (rgba.width(), rgba.height(), Bytes::from(rgba.into_raw()))
137        }
138        image::Handle::Rgba {
139            width,
140            height,
141            pixels,
142            ..
143        } => (*width, *height, pixels.clone()),
144    };
145
146    if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) {
147        Ok(image)
148    } else {
149        Err(to_error(::image::error::ImageError::Limits(
150            ::image::error::LimitError::from_kind(::image::error::LimitErrorKind::DimensionError),
151        )))
152    }
153}
154
155#[cfg(feature = "image")]
156fn to_error(error: ::image::ImageError) -> image::Error {
157    use std::sync::Arc;
158
159    match error {
160        ::image::ImageError::IoError(error) => image::Error::Inaccessible(Arc::new(error)),
161        error => image::Error::Invalid(Arc::new(error)),
162    }
163}