zng_view_api/
image.rs

1//! Image types.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use zng_txt::Txt;
7
8use crate::ipc::IpcBytes;
9use zng_unit::{Px, PxDensity2d, PxSize};
10
11crate::declare_id! {
12    /// Id of a decoded image in the cache.
13    ///
14    /// The View Process defines the ID.
15    pub struct ImageId(_);
16
17    /// Id of an image loaded in a renderer.
18    ///
19    /// The View Process defines the ID.
20    pub struct ImageTextureId(_);
21}
22
23/// Defines how the A8 image mask pixels are to be derived from a source mask image.
24#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Hash, Deserialize, Default)]
25#[non_exhaustive]
26pub enum ImageMaskMode {
27    /// Alpha channel.
28    ///
29    /// If the image has no alpha channel masks by `Luminance`.
30    #[default]
31    A,
32    /// Blue channel.
33    ///
34    /// If the image has no color channel fallback to monochrome channel, or `A`.
35    B,
36    /// Green channel.
37    ///
38    /// If the image has no color channel fallback to monochrome channel, or `A`.
39    G,
40    /// Red channel.
41    ///
42    /// If the image has no color channel fallback to monochrome channel, or `A`.
43    R,
44    /// Relative luminance.
45    ///
46    /// If the image has no color channel fallback to monochrome channel, or `A`.
47    Luminance,
48}
49
50/// Represent a image load/decode request.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[non_exhaustive]
53pub struct ImageRequest<D> {
54    /// Image data format.
55    pub format: ImageDataFormat,
56    /// Image data.
57    ///
58    /// Bytes layout depends on the `format`, data structure is [`IpcBytes`] or [`IpcBytesReceiver`] in the view API.
59    ///
60    /// [`IpcBytesReceiver`]: crate::IpcBytesReceiver
61    pub data: D,
62    /// Maximum allowed decoded size.
63    ///
64    /// View-process will avoid decoding and return an error if the image decoded to BGRA (4 bytes) exceeds this size.
65    /// This limit applies to the image before the `resize_to_fit`.
66    pub max_decoded_len: u64,
67    /// A size constraints to apply after the image is decoded. The image is resized so both dimensions fit inside
68    /// the constraints, the image aspect ratio is preserved.
69    pub downscale: Option<ImageDownscale>,
70    /// Convert or decode the image into a single channel mask (R8).
71    pub mask: Option<ImageMaskMode>,
72}
73impl<D> ImageRequest<D> {
74    /// New request.
75    pub fn new(
76        format: ImageDataFormat,
77        data: D,
78        max_decoded_len: u64,
79        downscale: Option<ImageDownscale>,
80        mask: Option<ImageMaskMode>,
81    ) -> Self {
82        Self {
83            format,
84            data,
85            max_decoded_len,
86            downscale,
87            mask,
88        }
89    }
90}
91
92/// Defines how an image is downscaled after decoding.
93///
94/// The image aspect ratio is preserved in both modes, the image is not upscaled, if it already fits the size
95/// constraints if will not be resized.
96#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
97pub enum ImageDownscale {
98    /// Image is downscaled so that both dimensions fit inside the size.
99    Fit(PxSize),
100    /// Image is downscaled so that at least one dimension fits inside the size.
101    Fill(PxSize),
102}
103impl From<PxSize> for ImageDownscale {
104    /// Fit
105    fn from(fit: PxSize) -> Self {
106        ImageDownscale::Fit(fit)
107    }
108}
109impl From<Px> for ImageDownscale {
110    /// Fit splat
111    fn from(fit: Px) -> Self {
112        ImageDownscale::Fit(PxSize::splat(fit))
113    }
114}
115#[cfg(feature = "var")]
116zng_var::impl_from_and_into_var! {
117    fn from(fit: PxSize) -> ImageDownscale;
118    fn from(fit: Px) -> ImageDownscale;
119    fn from(some: ImageDownscale) -> Option<ImageDownscale>;
120}
121impl ImageDownscale {
122    /// Compute the expected final size if the downscale is applied on an image of `source_size`.
123    pub fn resize_dimensions(self, source_size: PxSize) -> PxSize {
124        // code from image crate
125        fn resize_dimensions(width: u32, height: u32, n_width: u32, n_height: u32, fill: bool) -> (u32, u32) {
126            use std::cmp::max;
127
128            let w_ratio = n_width as f64 / width as f64;
129            let h_ratio = n_height as f64 / height as f64;
130
131            let ratio = if fill {
132                f64::max(w_ratio, h_ratio)
133            } else {
134                f64::min(w_ratio, h_ratio)
135            };
136
137            let nw = max((width as f64 * ratio).round() as u64, 1);
138            let nh = max((height as f64 * ratio).round() as u64, 1);
139
140            if nw > u64::from(u32::MAX) {
141                let ratio = u32::MAX as f64 / width as f64;
142                (u32::MAX, max((height as f64 * ratio).round() as u32, 1))
143            } else if nh > u64::from(u32::MAX) {
144                let ratio = u32::MAX as f64 / height as f64;
145                (max((width as f64 * ratio).round() as u32, 1), u32::MAX)
146            } else {
147                (nw as u32, nh as u32)
148            }
149        }
150
151        let (x, y) = match self {
152            ImageDownscale::Fit(s) => resize_dimensions(
153                source_size.width.0.max(0) as _,
154                source_size.height.0.max(0) as _,
155                s.width.0.max(0) as _,
156                s.height.0.max(0) as _,
157                false,
158            ),
159            ImageDownscale::Fill(s) => resize_dimensions(
160                source_size.width.0.max(0) as _,
161                source_size.height.0.max(0) as _,
162                s.width.0.max(0) as _,
163                s.height.0.max(0) as _,
164                true,
165            ),
166        };
167        PxSize::new(Px(x as _), Px(y as _))
168    }
169}
170
171/// Format of the image bytes.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[non_exhaustive]
174pub enum ImageDataFormat {
175    /// Decoded BGRA8.
176    ///
177    /// This is the internal image format, it indicates the image data
178    /// is already decoded and color managed (to sRGB).
179    Bgra8 {
180        /// Size in pixels.
181        size: PxSize,
182        /// Pixel density of the image.
183        density: Option<PxDensity2d>,
184    },
185
186    /// Decoded A8.
187    ///
188    /// This is the internal mask format it indicates the mask data
189    /// is already decoded.
190    A8 {
191        /// Size in pixels.
192        size: PxSize,
193    },
194
195    /// The image is encoded.
196    ///
197    /// This file extension maybe identifies the format. Fallback to `Unknown` handling if the file extension
198    /// is unknown or the file header does not match.
199    FileExtension(Txt),
200
201    /// The image is encoded.
202    ///
203    /// This MIME type maybe identifies the format. Fallback to `Unknown` handling if the file extension
204    /// is unknown or the file header does not match.
205    MimeType(Txt),
206
207    /// The image is encoded.
208    ///
209    /// A decoder will be selected using the "magic number" at the start of the bytes buffer.
210    Unknown,
211}
212impl From<Txt> for ImageDataFormat {
213    fn from(ext_or_mime: Txt) -> Self {
214        if ext_or_mime.contains('/') {
215            ImageDataFormat::MimeType(ext_or_mime)
216        } else {
217            ImageDataFormat::FileExtension(ext_or_mime)
218        }
219    }
220}
221impl From<&str> for ImageDataFormat {
222    fn from(ext_or_mime: &str) -> Self {
223        Txt::from_str(ext_or_mime).into()
224    }
225}
226impl From<PxSize> for ImageDataFormat {
227    fn from(bgra8_size: PxSize) -> Self {
228        ImageDataFormat::Bgra8 {
229            size: bgra8_size,
230            density: None,
231        }
232    }
233}
234impl PartialEq for ImageDataFormat {
235    fn eq(&self, other: &Self) -> bool {
236        match (self, other) {
237            (Self::FileExtension(l0), Self::FileExtension(r0)) => l0 == r0,
238            (Self::MimeType(l0), Self::MimeType(r0)) => l0 == r0,
239            (Self::Bgra8 { size: s0, density: p0 }, Self::Bgra8 { size: s1, density: p1 }) => {
240                s0 == s1 && density_key(*p0) == density_key(*p1)
241            }
242            (Self::Unknown, Self::Unknown) => true,
243            _ => false,
244        }
245    }
246}
247impl Eq for ImageDataFormat {}
248impl std::hash::Hash for ImageDataFormat {
249    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
250        core::mem::discriminant(self).hash(state);
251        match self {
252            ImageDataFormat::Bgra8 { size, density } => {
253                size.hash(state);
254                density_key(*density).hash(state);
255            }
256            ImageDataFormat::A8 { size } => {
257                size.hash(state);
258            }
259            ImageDataFormat::FileExtension(ext) => ext.hash(state),
260            ImageDataFormat::MimeType(mt) => mt.hash(state),
261            ImageDataFormat::Unknown => {}
262        }
263    }
264}
265
266fn density_key(density: Option<PxDensity2d>) -> Option<(u16, u16)> {
267    density.map(|s| ((s.width.ppcm() * 3.0) as u16, (s.height.ppcm() * 3.0) as u16))
268}
269
270/// Represents a successfully decoded image.
271///
272/// See [`Event::ImageLoaded`].
273///
274/// [`Event::ImageLoaded`]: crate::Event::ImageLoaded
275#[derive(Clone, PartialEq, Serialize, Deserialize)]
276#[non_exhaustive]
277pub struct ImageLoadedData {
278    /// Image ID.
279    pub id: ImageId,
280    /// Pixel size.
281    pub size: PxSize,
282    /// Pixel density metadata.
283    pub density: Option<PxDensity2d>,
284    /// If all pixels have an alpha value of 255.
285    pub is_opaque: bool,
286    /// If the `pixels` are in a single channel (A8).
287    pub is_mask: bool,
288    /// Reference to the BGRA8 pre-multiplied image pixels or the A8 pixels if `is_mask`.
289    pub pixels: IpcBytes,
290}
291impl ImageLoadedData {
292    /// New response.
293    pub fn new(id: ImageId, size: PxSize, density: Option<PxDensity2d>, is_opaque: bool, is_mask: bool, pixels: IpcBytes) -> Self {
294        Self {
295            id,
296            size,
297            density,
298            is_opaque,
299            is_mask,
300            pixels,
301        }
302    }
303}
304impl fmt::Debug for ImageLoadedData {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.debug_struct("ImageLoadedData")
307            .field("id", &self.id)
308            .field("size", &self.size)
309            .field("density", &self.density)
310            .field("is_opaque", &self.is_opaque)
311            .field("is_mask", &self.is_mask)
312            .field("pixels", &format_args!("<{} bytes shared memory>", self.pixels.len()))
313            .finish()
314    }
315}