1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use zng_txt::Txt;
7
8use crate::ipc::IpcBytes;
9use zng_unit::{Px, 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 ppi: Option<ImagePpi>,
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 ppi: 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, ppi: p0 }, Self::Bgra8 { size: s1, ppi: p1 }) => s0 == s1 && ppi_key(*p0) == ppi_key(*p1),
240 (Self::Unknown, Self::Unknown) => true,
241 _ => false,
242 }
243 }
244}
245impl Eq for ImageDataFormat {}
246impl std::hash::Hash for ImageDataFormat {
247 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
248 core::mem::discriminant(self).hash(state);
249 match self {
250 ImageDataFormat::Bgra8 { size, ppi } => {
251 size.hash(state);
252 ppi_key(*ppi).hash(state);
253 }
254 ImageDataFormat::A8 { size } => {
255 size.hash(state);
256 }
257 ImageDataFormat::FileExtension(ext) => ext.hash(state),
258 ImageDataFormat::MimeType(mt) => mt.hash(state),
259 ImageDataFormat::Unknown => {}
260 }
261 }
262}
263
264fn ppi_key(ppi: Option<ImagePpi>) -> Option<(u16, u16)> {
265 ppi.map(|s| ((s.x * 3.0) as u16, (s.y * 3.0) as u16))
266}
267
268#[derive(Clone, PartialEq, Serialize, Deserialize)]
274#[non_exhaustive]
275pub struct ImageLoadedData {
276 pub id: ImageId,
278 pub size: PxSize,
280 pub ppi: Option<ImagePpi>,
282 pub is_opaque: bool,
284 pub is_mask: bool,
286 pub pixels: IpcBytes,
288}
289impl ImageLoadedData {
290 pub fn new(id: ImageId, size: PxSize, ppi: Option<ImagePpi>, is_opaque: bool, is_mask: bool, pixels: IpcBytes) -> Self {
292 Self {
293 id,
294 size,
295 ppi,
296 is_opaque,
297 is_mask,
298 pixels,
299 }
300 }
301}
302impl fmt::Debug for ImageLoadedData {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 f.debug_struct("ImageLoadedData")
305 .field("id", &self.id)
306 .field("size", &self.size)
307 .field("ppi", &self.ppi)
308 .field("is_opaque", &self.is_opaque)
309 .field("is_mask", &self.is_mask)
310 .field("pixels", &format_args!("<{} bytes shared memory>", self.pixels.len()))
311 .finish()
312 }
313}
314#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
316pub struct ImagePpi {
317 pub x: f32,
319 pub y: f32,
321}
322impl ImagePpi {
323 pub const fn new(x: f32, y: f32) -> Self {
325 Self { x, y }
326 }
327
328 pub const fn splat(xy: f32) -> Self {
330 Self::new(xy, xy)
331 }
332}
333impl Default for ImagePpi {
334 fn default() -> Self {
336 Self::splat(96.0)
337 }
338}
339impl fmt::Debug for ImagePpi {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 if f.alternate() || self.x != self.y {
342 f.debug_struct("ImagePpi").field("x", &self.x).field("y", &self.y).finish()
343 } else {
344 write!(f, "{}", self.x)
345 }
346 }
347}
348
349impl From<f32> for ImagePpi {
350 fn from(xy: f32) -> Self {
351 ImagePpi::splat(xy)
352 }
353}
354impl From<(f32, f32)> for ImagePpi {
355 fn from((x, y): (f32, f32)) -> Self {
356 ImagePpi::new(x, y)
357 }
358}
359
360#[cfg(feature = "var")]
361zng_var::impl_from_and_into_var! {
362 fn from(xy: f32) -> ImagePpi;
363 fn from(xy: (f32, f32)) -> ImagePpi;
364}