1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6use zng_task::channel::IpcBytes;
7use zng_txt::Txt;
8
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>,
69 pub mask: Option<ImageMaskMode>,
71}
72impl<D> ImageRequest<D> {
73 pub fn new(
75 format: ImageDataFormat,
76 data: D,
77 max_decoded_len: u64,
78 downscale: Option<ImageDownscale>,
79 mask: Option<ImageMaskMode>,
80 ) -> Self {
81 Self {
82 format,
83 data,
84 max_decoded_len,
85 downscale,
86 mask,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
96pub enum ImageDownscale {
97 Fit(PxSize),
99 Fill(PxSize),
101}
102impl From<PxSize> for ImageDownscale {
103 fn from(fit: PxSize) -> Self {
105 ImageDownscale::Fit(fit)
106 }
107}
108impl From<Px> for ImageDownscale {
109 fn from(fit: Px) -> Self {
111 ImageDownscale::Fit(PxSize::splat(fit))
112 }
113}
114#[cfg(feature = "var")]
115zng_var::impl_from_and_into_var! {
116 fn from(fit: PxSize) -> ImageDownscale;
117 fn from(fit: Px) -> ImageDownscale;
118 fn from(some: ImageDownscale) -> Option<ImageDownscale>;
119}
120impl ImageDownscale {
121 pub fn resize_dimensions(self, source_size: PxSize) -> PxSize {
123 let (new_size, fill) = match self {
124 ImageDownscale::Fill(s) => (s, true),
125 ImageDownscale::Fit(s) => (s, false),
126 };
127 let source_size = source_size.cast::<f64>();
128 let new_size = new_size.cast::<f64>();
129
130 let w_ratio = new_size.width / source_size.width;
131 let h_ratio = new_size.height / source_size.height;
132
133 let ratio = if fill {
134 f64::max(w_ratio, h_ratio)
135 } else {
136 f64::min(w_ratio, h_ratio)
137 };
138
139 let nw = u64::max((source_size.width * ratio).round() as _, 1);
140 let nh = u64::max((source_size.height * ratio).round() as _, 1);
141
142 const MAX: u64 = Px::MAX.0 as _;
143
144 if nw > MAX {
145 let ratio = MAX as f64 / source_size.width;
146 (Px::MAX, Px(i32::max((source_size.height * ratio).round() as _, 1))).into()
147 } else if nh > MAX {
148 let ratio = MAX as f64 / source_size.height;
149 (Px(i32::max((source_size.width * ratio).round() as _, 1)), Px::MAX).into()
150 } else {
151 (Px(nw as _), Px(nh as _)).into()
152 }
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158#[non_exhaustive]
159pub enum ImageDataFormat {
160 Bgra8 {
165 size: PxSize,
167 density: Option<PxDensity2d>,
169 },
170
171 A8 {
176 size: PxSize,
178 },
179
180 FileExtension(Txt),
185
186 MimeType(Txt),
191
192 Unknown,
196}
197impl From<Txt> for ImageDataFormat {
198 fn from(ext_or_mime: Txt) -> Self {
199 if ext_or_mime.contains('/') {
200 ImageDataFormat::MimeType(ext_or_mime)
201 } else {
202 ImageDataFormat::FileExtension(ext_or_mime)
203 }
204 }
205}
206impl From<&str> for ImageDataFormat {
207 fn from(ext_or_mime: &str) -> Self {
208 Txt::from_str(ext_or_mime).into()
209 }
210}
211impl From<PxSize> for ImageDataFormat {
212 fn from(bgra8_size: PxSize) -> Self {
213 ImageDataFormat::Bgra8 {
214 size: bgra8_size,
215 density: None,
216 }
217 }
218}
219impl PartialEq for ImageDataFormat {
220 fn eq(&self, other: &Self) -> bool {
221 match (self, other) {
222 (Self::FileExtension(l0), Self::FileExtension(r0)) => l0 == r0,
223 (Self::MimeType(l0), Self::MimeType(r0)) => l0 == r0,
224 (Self::Bgra8 { size: s0, density: p0 }, Self::Bgra8 { size: s1, density: p1 }) => {
225 s0 == s1 && density_key(*p0) == density_key(*p1)
226 }
227 (Self::Unknown, Self::Unknown) => true,
228 _ => false,
229 }
230 }
231}
232impl Eq for ImageDataFormat {}
233impl std::hash::Hash for ImageDataFormat {
234 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
235 core::mem::discriminant(self).hash(state);
236 match self {
237 ImageDataFormat::Bgra8 { size, density } => {
238 size.hash(state);
239 density_key(*density).hash(state);
240 }
241 ImageDataFormat::A8 { size } => {
242 size.hash(state);
243 }
244 ImageDataFormat::FileExtension(ext) => ext.hash(state),
245 ImageDataFormat::MimeType(mt) => mt.hash(state),
246 ImageDataFormat::Unknown => {}
247 }
248 }
249}
250
251fn density_key(density: Option<PxDensity2d>) -> Option<(u16, u16)> {
252 density.map(|s| ((s.width.ppcm() * 3.0) as u16, (s.height.ppcm() * 3.0) as u16))
253}
254
255#[derive(Clone, PartialEq, Serialize, Deserialize)]
261#[non_exhaustive]
262pub struct ImageLoadedData {
263 pub id: ImageId,
265 pub size: PxSize,
267 pub density: Option<PxDensity2d>,
269 pub is_opaque: bool,
271 pub is_mask: bool,
273 pub pixels: IpcBytes,
275}
276impl ImageLoadedData {
277 pub fn new(id: ImageId, size: PxSize, density: Option<PxDensity2d>, is_opaque: bool, is_mask: bool, pixels: IpcBytes) -> Self {
279 Self {
280 id,
281 size,
282 density,
283 is_opaque,
284 is_mask,
285 pixels,
286 }
287 }
288}
289impl fmt::Debug for ImageLoadedData {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 f.debug_struct("ImageLoadedData")
292 .field("id", &self.id)
293 .field("size", &self.size)
294 .field("density", &self.density)
295 .field("is_opaque", &self.is_opaque)
296 .field("is_mask", &self.is_mask)
297 .field("pixels", &format_args!("<{} bytes shared memory>", self.pixels.len()))
298 .finish()
299 }
300}