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)]
25pub enum ImageMaskMode {
26 #[default]
30 A,
31 B,
35 G,
39 R,
43 Luminance,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ImageRequest<D> {
52 pub format: ImageDataFormat,
54 pub data: D,
60 pub max_decoded_len: u64,
65 pub downscale: Option<ImageDownscale>,
68 pub mask: Option<ImageMaskMode>,
70}
71
72#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
77pub enum ImageDownscale {
78 Fit(PxSize),
80 Fill(PxSize),
82}
83impl From<PxSize> for ImageDownscale {
84 fn from(fit: PxSize) -> Self {
86 ImageDownscale::Fit(fit)
87 }
88}
89impl From<Px> for ImageDownscale {
90 fn from(fit: Px) -> Self {
92 ImageDownscale::Fit(PxSize::splat(fit))
93 }
94}
95#[cfg(feature = "var")]
96zng_var::impl_from_and_into_var! {
97 fn from(fit: PxSize) -> ImageDownscale;
98 fn from(fit: Px) -> ImageDownscale;
99 fn from(some: ImageDownscale) -> Option<ImageDownscale>;
100}
101impl ImageDownscale {
102 pub fn resize_dimensions(self, source_size: PxSize) -> PxSize {
104 fn resize_dimensions(width: u32, height: u32, n_width: u32, n_height: u32, fill: bool) -> (u32, u32) {
106 use std::cmp::max;
107
108 let w_ratio = n_width as f64 / width as f64;
109 let h_ratio = n_height as f64 / height as f64;
110
111 let ratio = if fill {
112 f64::max(w_ratio, h_ratio)
113 } else {
114 f64::min(w_ratio, h_ratio)
115 };
116
117 let nw = max((width as f64 * ratio).round() as u64, 1);
118 let nh = max((height as f64 * ratio).round() as u64, 1);
119
120 if nw > u64::from(u32::MAX) {
121 let ratio = u32::MAX as f64 / width as f64;
122 (u32::MAX, max((height as f64 * ratio).round() as u32, 1))
123 } else if nh > u64::from(u32::MAX) {
124 let ratio = u32::MAX as f64 / height as f64;
125 (max((width as f64 * ratio).round() as u32, 1), u32::MAX)
126 } else {
127 (nw as u32, nh as u32)
128 }
129 }
130
131 let (x, y) = match self {
132 ImageDownscale::Fit(s) => resize_dimensions(
133 source_size.width.0.max(0) as _,
134 source_size.height.0.max(0) as _,
135 s.width.0.max(0) as _,
136 s.height.0.max(0) as _,
137 false,
138 ),
139 ImageDownscale::Fill(s) => resize_dimensions(
140 source_size.width.0.max(0) as _,
141 source_size.height.0.max(0) as _,
142 s.width.0.max(0) as _,
143 s.height.0.max(0) as _,
144 true,
145 ),
146 };
147 PxSize::new(Px(x as _), Px(y as _))
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub enum ImageDataFormat {
154 Bgra8 {
159 size: PxSize,
161 ppi: Option<ImagePpi>,
163 },
164
165 A8 {
170 size: PxSize,
172 },
173
174 FileExtension(Txt),
177
178 MimeType(Txt),
180
181 Unknown,
184}
185impl From<Txt> for ImageDataFormat {
186 fn from(ext_or_mime: Txt) -> Self {
187 if ext_or_mime.contains('/') {
188 ImageDataFormat::MimeType(ext_or_mime)
189 } else {
190 ImageDataFormat::FileExtension(ext_or_mime)
191 }
192 }
193}
194impl From<&str> for ImageDataFormat {
195 fn from(ext_or_mime: &str) -> Self {
196 Txt::from_str(ext_or_mime).into()
197 }
198}
199impl From<PxSize> for ImageDataFormat {
200 fn from(bgra8_size: PxSize) -> Self {
201 ImageDataFormat::Bgra8 {
202 size: bgra8_size,
203 ppi: None,
204 }
205 }
206}
207impl PartialEq for ImageDataFormat {
208 fn eq(&self, other: &Self) -> bool {
209 match (self, other) {
210 (Self::FileExtension(l0), Self::FileExtension(r0)) => l0 == r0,
211 (Self::MimeType(l0), Self::MimeType(r0)) => l0 == r0,
212 (Self::Bgra8 { size: s0, ppi: p0 }, Self::Bgra8 { size: s1, ppi: p1 }) => s0 == s1 && ppi_key(*p0) == ppi_key(*p1),
213 (Self::Unknown, Self::Unknown) => true,
214 _ => false,
215 }
216 }
217}
218impl Eq for ImageDataFormat {}
219impl std::hash::Hash for ImageDataFormat {
220 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
221 core::mem::discriminant(self).hash(state);
222 match self {
223 ImageDataFormat::Bgra8 { size, ppi } => {
224 size.hash(state);
225 ppi_key(*ppi).hash(state);
226 }
227 ImageDataFormat::A8 { size } => {
228 size.hash(state);
229 }
230 ImageDataFormat::FileExtension(ext) => ext.hash(state),
231 ImageDataFormat::MimeType(mt) => mt.hash(state),
232 ImageDataFormat::Unknown => {}
233 }
234 }
235}
236
237fn ppi_key(ppi: Option<ImagePpi>) -> Option<(u16, u16)> {
238 ppi.map(|s| ((s.x * 3.0) as u16, (s.y * 3.0) as u16))
239}
240
241#[derive(Clone, PartialEq, Serialize, Deserialize)]
247pub struct ImageLoadedData {
248 pub id: ImageId,
250 pub size: PxSize,
252 pub ppi: Option<ImagePpi>,
254 pub is_opaque: bool,
256 pub is_mask: bool,
258 pub pixels: IpcBytes,
260}
261impl fmt::Debug for ImageLoadedData {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 f.debug_struct("ImageLoadedData")
264 .field("id", &self.id)
265 .field("size", &self.size)
266 .field("ppi", &self.ppi)
267 .field("is_opaque", &self.is_opaque)
268 .field("is_mask", &self.is_mask)
269 .field("pixels", &format_args!("<{} bytes shared memory>", self.pixels.len()))
270 .finish()
271 }
272}
273#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
275pub struct ImagePpi {
276 pub x: f32,
278 pub y: f32,
280}
281impl ImagePpi {
282 pub const fn new(x: f32, y: f32) -> Self {
284 Self { x, y }
285 }
286
287 pub const fn splat(xy: f32) -> Self {
289 Self::new(xy, xy)
290 }
291}
292impl Default for ImagePpi {
293 fn default() -> Self {
295 Self::splat(96.0)
296 }
297}
298impl fmt::Debug for ImagePpi {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 if f.alternate() || self.x != self.y {
301 f.debug_struct("ImagePpi").field("x", &self.x).field("y", &self.y).finish()
302 } else {
303 write!(f, "{}", self.x)
304 }
305 }
306}
307
308impl From<f32> for ImagePpi {
309 fn from(xy: f32) -> Self {
310 ImagePpi::splat(xy)
311 }
312}
313impl From<(f32, f32)> for ImagePpi {
314 fn from((x, y): (f32, f32)) -> Self {
315 ImagePpi::new(x, y)
316 }
317}
318
319#[cfg(feature = "var")]
320zng_var::impl_from_and_into_var! {
321 fn from(xy: f32) -> ImagePpi;
322 fn from(xy: (f32, f32)) -> ImagePpi;
323}