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#[derive(Debug, Clone, PartialEq)]
11pub enum Image {
12    /// A raster image.
13    Raster(image::Image, Rectangle),
14
15    /// A vector image.
16    Vector(svg::Svg, Rectangle),
17}
18
19impl Image {
20    /// Returns the bounds of the [`Image`].
21    pub fn bounds(&self) -> Rectangle {
22        match self {
23            Image::Raster(image, bounds) => bounds.rotate(image.rotation),
24            Image::Vector(svg, bounds) => bounds.rotate(svg.rotation),
25        }
26    }
27}
28
29#[cfg(feature = "image")]
30/// Tries to load an image by its [`Handle`].
31///
32/// [`Handle`]: image::Handle
33pub fn load(
34    handle: &image::Handle,
35) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba<u8>, image::Bytes>>
36{
37    use bitflags::bitflags;
38
39    bitflags! {
40        struct Operation: u8 {
41            const FLIP_HORIZONTALLY = 0b001;
42            const ROTATE_180 = 0b010;
43            const FLIP_DIAGONALLY = 0b100;
44        }
45    }
46
47    impl Operation {
48        // Meaning of the returned value is described e.g. at:
49        // https://magnushoff.com/articles/jpeg-orientation/
50        fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
51        where
52            R: std::io::BufRead + std::io::Seek,
53        {
54            let exif = exif::Reader::new().read_from_container(reader)?;
55
56            Ok(exif
57                .get_field(exif::Tag::Orientation, exif::In::PRIMARY)
58                .and_then(|field| field.value.get_uint(0))
59                .and_then(|value| u8::try_from(value).ok())
60                .and_then(|value| Self::from_bits(value.saturating_sub(1)))
61                .unwrap_or_else(Self::empty))
62        }
63
64        fn perform(
65            self,
66            mut image: ::image::DynamicImage,
67        ) -> ::image::DynamicImage {
68            use ::image::imageops;
69
70            if self.contains(Self::FLIP_DIAGONALLY) {
71                imageops::flip_vertical_in_place(&mut image);
72            }
73
74            if self.contains(Self::ROTATE_180) {
75                imageops::rotate180_in_place(&mut image);
76            }
77
78            if self.contains(Self::FLIP_HORIZONTALLY) {
79                imageops::flip_horizontal_in_place(&mut image);
80            }
81
82            image
83        }
84    }
85
86    let (width, height, pixels) = match handle {
87        image::Handle::Path(_, path) => {
88            let image = ::image::open(path)?;
89
90            let operation = std::fs::File::open(path)
91                .ok()
92                .map(std::io::BufReader::new)
93                .and_then(|mut reader| Operation::from_exif(&mut reader).ok())
94                .unwrap_or_else(Operation::empty);
95
96            let rgba = operation.perform(image).into_rgba8();
97
98            (
99                rgba.width(),
100                rgba.height(),
101                image::Bytes::from(rgba.into_raw()),
102            )
103        }
104        image::Handle::Bytes(_, bytes) => {
105            let image = ::image::load_from_memory(bytes)?;
106            let operation =
107                Operation::from_exif(&mut std::io::Cursor::new(bytes))
108                    .ok()
109                    .unwrap_or_else(Operation::empty);
110
111            let rgba = operation.perform(image).into_rgba8();
112
113            (
114                rgba.width(),
115                rgba.height(),
116                image::Bytes::from(rgba.into_raw()),
117            )
118        }
119        image::Handle::Rgba {
120            width,
121            height,
122            pixels,
123            ..
124        } => (*width, *height, pixels.clone()),
125    };
126
127    if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) {
128        Ok(image)
129    } else {
130        Err(::image::error::ImageError::Limits(
131            ::image::error::LimitError::from_kind(
132                ::image::error::LimitErrorKind::DimensionError,
133            ),
134        ))
135    }
136}