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),
198
199 MimeType(Txt),
201
202 Unknown,
205}
206impl From<Txt> for ImageDataFormat {
207 fn from(ext_or_mime: Txt) -> Self {
208 if ext_or_mime.contains('/') {
209 ImageDataFormat::MimeType(ext_or_mime)
210 } else {
211 ImageDataFormat::FileExtension(ext_or_mime)
212 }
213 }
214}
215impl From<&str> for ImageDataFormat {
216 fn from(ext_or_mime: &str) -> Self {
217 Txt::from_str(ext_or_mime).into()
218 }
219}
220impl From<PxSize> for ImageDataFormat {
221 fn from(bgra8_size: PxSize) -> Self {
222 ImageDataFormat::Bgra8 {
223 size: bgra8_size,
224 ppi: None,
225 }
226 }
227}
228impl PartialEq for ImageDataFormat {
229 fn eq(&self, other: &Self) -> bool {
230 match (self, other) {
231 (Self::FileExtension(l0), Self::FileExtension(r0)) => l0 == r0,
232 (Self::MimeType(l0), Self::MimeType(r0)) => l0 == r0,
233 (Self::Bgra8 { size: s0, ppi: p0 }, Self::Bgra8 { size: s1, ppi: p1 }) => s0 == s1 && ppi_key(*p0) == ppi_key(*p1),
234 (Self::Unknown, Self::Unknown) => true,
235 _ => false,
236 }
237 }
238}
239impl Eq for ImageDataFormat {}
240impl std::hash::Hash for ImageDataFormat {
241 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
242 core::mem::discriminant(self).hash(state);
243 match self {
244 ImageDataFormat::Bgra8 { size, ppi } => {
245 size.hash(state);
246 ppi_key(*ppi).hash(state);
247 }
248 ImageDataFormat::A8 { size } => {
249 size.hash(state);
250 }
251 ImageDataFormat::FileExtension(ext) => ext.hash(state),
252 ImageDataFormat::MimeType(mt) => mt.hash(state),
253 ImageDataFormat::Unknown => {}
254 }
255 }
256}
257
258fn ppi_key(ppi: Option<ImagePpi>) -> Option<(u16, u16)> {
259 ppi.map(|s| ((s.x * 3.0) as u16, (s.y * 3.0) as u16))
260}
261
262#[derive(Clone, PartialEq, Serialize, Deserialize)]
268#[non_exhaustive]
269pub struct ImageLoadedData {
270 pub id: ImageId,
272 pub size: PxSize,
274 pub ppi: Option<ImagePpi>,
276 pub is_opaque: bool,
278 pub is_mask: bool,
280 pub pixels: IpcBytes,
282}
283impl ImageLoadedData {
284 pub fn new(id: ImageId, size: PxSize, ppi: Option<ImagePpi>, is_opaque: bool, is_mask: bool, pixels: IpcBytes) -> Self {
286 Self {
287 id,
288 size,
289 ppi,
290 is_opaque,
291 is_mask,
292 pixels,
293 }
294 }
295}
296impl fmt::Debug for ImageLoadedData {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 f.debug_struct("ImageLoadedData")
299 .field("id", &self.id)
300 .field("size", &self.size)
301 .field("ppi", &self.ppi)
302 .field("is_opaque", &self.is_opaque)
303 .field("is_mask", &self.is_mask)
304 .field("pixels", &format_args!("<{} bytes shared memory>", self.pixels.len()))
305 .finish()
306 }
307}
308#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
310pub struct ImagePpi {
311 pub x: f32,
313 pub y: f32,
315}
316impl ImagePpi {
317 pub const fn new(x: f32, y: f32) -> Self {
319 Self { x, y }
320 }
321
322 pub const fn splat(xy: f32) -> Self {
324 Self::new(xy, xy)
325 }
326}
327impl Default for ImagePpi {
328 fn default() -> Self {
330 Self::splat(96.0)
331 }
332}
333impl fmt::Debug for ImagePpi {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 if f.alternate() || self.x != self.y {
336 f.debug_struct("ImagePpi").field("x", &self.x).field("y", &self.y).finish()
337 } else {
338 write!(f, "{}", self.x)
339 }
340 }
341}
342
343impl From<f32> for ImagePpi {
344 fn from(xy: f32) -> Self {
345 ImagePpi::splat(xy)
346 }
347}
348impl From<(f32, f32)> for ImagePpi {
349 fn from((x, y): (f32, f32)) -> Self {
350 ImagePpi::new(x, y)
351 }
352}
353
354#[cfg(feature = "var")]
355zng_var::impl_from_and_into_var! {
356 fn from(xy: f32) -> ImagePpi;
357 fn from(xy: (f32, f32)) -> ImagePpi;
358}