1use 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 pub struct ImageId(_);
16
17 pub struct ImageTextureId(_);
21}
22
23#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Hash, Deserialize, Default)]
25#[non_exhaustive]
26pub enum ImageMaskMode {
27 #[default]
31 A,
32 B,
36 G,
40 R,
44 Luminance,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52#[non_exhaustive]
53pub struct ImageRequest<D> {
54 pub format: ImageDataFormat,
56 pub data: D,
62 pub max_decoded_len: u64,
67 pub downscale: Option<ImageDownscale>,
70 pub mask: Option<ImageMaskMode>,
72}
73impl<D> ImageRequest<D> {
74 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#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
97pub enum ImageDownscale {
98 Fit(PxSize),
100 Fill(PxSize),
102}
103impl From<PxSize> for ImageDownscale {
104 fn from(fit: PxSize) -> Self {
106 ImageDownscale::Fit(fit)
107 }
108}
109impl From<Px> for ImageDownscale {
110 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 pub fn resize_dimensions(self, source_size: PxSize) -> PxSize {
124 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#[derive(Debug, Clone, Serialize, Deserialize)]
173#[non_exhaustive]
174pub enum ImageDataFormat {
175 Bgra8 {
180 size: PxSize,
182 density: Option<PxDensity2d>,
184 },
185
186 A8 {
191 size: PxSize,
193 },
194
195 FileExtension(Txt),
200
201 MimeType(Txt),
206
207 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#[derive(Clone, PartialEq, Serialize, Deserialize)]
276#[non_exhaustive]
277pub struct ImageLoadedData {
278 pub id: ImageId,
280 pub size: PxSize,
282 pub density: Option<PxDensity2d>,
284 pub is_opaque: bool,
286 pub is_mask: bool,
288 pub pixels: IpcBytes,
290}
291impl ImageLoadedData {
292 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}