iced_graphics/
image.rs

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