1use bitflags::bitflags;
4use serde::{Deserialize, Serialize};
5use zng_task::channel::IpcBytes;
6use zng_txt::Txt;
7
8use zng_unit::{Px, PxDensity2d, PxSize};
9
10use crate::api_extension::{ApiExtensionId, ApiExtensionPayload};
11
12crate::declare_id! {
13 pub struct ImageId(_);
17
18 pub struct ImageTextureId(_);
22
23 pub struct ImageEncodeId(_);
27}
28
29#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Hash, Deserialize, Default)]
31#[non_exhaustive]
32pub enum ImageMaskMode {
33 #[default]
37 A,
38 B,
42 G,
46 R,
50 Luminance,
54}
55
56bitflags! {
57 #[derive(Copy, Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
61 pub struct ImageEntriesMode: u8 {
62 const PAGES = 0b0001;
64 const REDUCED = 0b0010;
66 const PRIMARY = 0;
70
71 const OTHER = 0b1000;
73 }
74}
75#[cfg(feature = "var")]
76zng_var::impl_from_and_into_var! {
77 fn from(kind: ImageEntryKind) -> ImageEntriesMode {
78 match kind {
79 ImageEntryKind::Page => ImageEntriesMode::PAGES,
80 ImageEntryKind::Reduced { .. } => ImageEntriesMode::REDUCED,
81 ImageEntryKind::Other { .. } => ImageEntriesMode::OTHER,
82 }
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88#[non_exhaustive]
89pub struct ImageRequest<D> {
90 pub format: ImageDataFormat,
92 pub data: D,
98 pub max_decoded_len: u64,
103
104 pub downscale: Option<ImageDownscaleMode>,
111
112 pub mask: Option<ImageMaskMode>,
114
115 pub entries: ImageEntriesMode,
117
118 pub parent: Option<ImageEntryMetadata>,
123}
124impl<D> ImageRequest<D> {
125 pub fn new(
127 format: ImageDataFormat,
128 data: D,
129 max_decoded_len: u64,
130 downscale: Option<ImageDownscaleMode>,
131 mask: Option<ImageMaskMode>,
132 ) -> Self {
133 Self {
134 format,
135 data,
136 max_decoded_len,
137 downscale,
138 mask,
139 entries: ImageEntriesMode::PRIMARY,
140 parent: None,
141 }
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
150#[non_exhaustive]
151pub enum ImageDownscaleMode {
152 Fit(PxSize),
154 Fill(PxSize),
156 MipMap {
160 min_size: PxSize,
162 max_size: PxSize,
164 },
165 Entries(Vec<ImageDownscaleMode>),
169}
170impl From<PxSize> for ImageDownscaleMode {
171 fn from(fit: PxSize) -> Self {
173 ImageDownscaleMode::Fit(fit)
174 }
175}
176impl From<Px> for ImageDownscaleMode {
177 fn from(fit: Px) -> Self {
179 ImageDownscaleMode::Fit(PxSize::splat(fit))
180 }
181}
182#[cfg(feature = "var")]
183zng_var::impl_from_and_into_var! {
184 fn from(fit: PxSize) -> ImageDownscaleMode;
185 fn from(fit: Px) -> ImageDownscaleMode;
186 fn from(some: ImageDownscaleMode) -> Option<ImageDownscaleMode>;
187}
188impl ImageDownscaleMode {
189 pub fn mip_map() -> Self {
191 Self::MipMap {
192 min_size: PxSize::splat(Px(512)),
193 max_size: PxSize::splat(Px::MAX),
194 }
195 }
196
197 pub fn with_entry(self, other: impl Into<ImageDownscaleMode>) -> Self {
199 self.with_impl(other.into())
200 }
201 fn with_impl(self, other: Self) -> Self {
202 let mut v = match self {
203 Self::Entries(e) => e,
204 s => vec![s],
205 };
206 match other {
207 Self::Entries(o) => v.extend(o),
208 o => v.push(o),
209 }
210 Self::Entries(v)
211 }
212
213 pub fn sizes(&self, page_size: PxSize, reduced_sizes: &[PxSize]) -> (Option<PxSize>, Vec<PxSize>) {
221 match self {
222 ImageDownscaleMode::Fit(s) => (fit_fill(page_size, *s, false), vec![]),
223 ImageDownscaleMode::Fill(s) => (fit_fill(page_size, *s, true), vec![]),
224 ImageDownscaleMode::MipMap { min_size, max_size } => Self::collect_mip_map(page_size, reduced_sizes, &[], *min_size, *max_size),
225 ImageDownscaleMode::Entries(modes) => {
226 let mut include_full_size = false;
227 let mut sizes = vec![];
228 let mut mip_map = None;
229 for m in modes {
230 m.collect_entries(page_size, &mut sizes, &mut mip_map, &mut include_full_size);
231 }
232 if let Some([min_size, max_size]) = mip_map {
233 let (first, mips) = Self::collect_mip_map(page_size, reduced_sizes, &sizes, min_size, max_size);
234 include_full_size |= first.is_some();
235 sizes.extend(first);
236 sizes.extend(mips);
237 }
238
239 sizes.sort_by_key(|s| s.width.0 * s.height.0);
240 sizes.dedup();
241
242 let full_downscale = if include_full_size { None } else { sizes.pop() };
243 sizes.reverse();
244
245 (full_downscale, sizes)
246 }
247 }
248 }
249
250 fn collect_mip_map(
251 page_size: PxSize,
252 reduced_sizes: &[PxSize],
253 entry_sizes: &[PxSize],
254 min_size: PxSize,
255 max_size: PxSize,
256 ) -> (Option<PxSize>, Vec<PxSize>) {
257 let page_downscale = fit_fill(page_size, max_size, true);
258 let mut size = page_downscale.unwrap_or(page_size) / Px(2);
259 let mut entries = vec![];
260 while min_size.width < size.width && min_size.height < size.height {
261 if let Some(entry) = fit_fill(page_size, size, true)
262 && !reduced_sizes.iter().any(|s| Self::near(entry, *s))
263 && !entry_sizes.iter().any(|s| Self::near(entry, *s))
264 {
265 entries.push(entry);
266 }
267 size /= Px(2);
268 }
269 (page_downscale, entries)
270 }
271 fn near(candidate: PxSize, existing: PxSize) -> bool {
272 let dist = (candidate - existing).abs();
273 dist.width < Px(10) && dist.height <= Px(10)
274 }
275
276 fn collect_entries(&self, page_size: PxSize, sizes: &mut Vec<PxSize>, mip_map: &mut Option<[PxSize; 2]>, include_full_size: &mut bool) {
277 match self {
278 ImageDownscaleMode::Fit(s) => match fit_fill(page_size, *s, false) {
279 Some(s) => sizes.push(s),
280 None => *include_full_size = true,
281 },
282 ImageDownscaleMode::Fill(s) => match fit_fill(page_size, *s, true) {
283 Some(s) => sizes.push(s),
284 None => *include_full_size = true,
285 },
286 ImageDownscaleMode::MipMap { min_size, max_size } => {
287 *include_full_size = true;
288 if let Some([min, max]) = mip_map {
289 *min = min.min(*min_size);
290 *max = max.min(*min_size);
291 } else {
292 *mip_map = Some([*min_size, *max_size]);
293 }
294 }
295 ImageDownscaleMode::Entries(modes) => {
296 for m in modes {
297 m.collect_entries(page_size, sizes, mip_map, include_full_size);
298 }
299 }
300 }
301 }
302}
303
304fn fit_fill(source_size: PxSize, new_size: PxSize, fill: bool) -> Option<PxSize> {
305 let source_size = source_size.cast::<f64>();
306 let new_size = new_size.cast::<f64>();
307
308 let w_ratio = new_size.width / source_size.width;
309 let h_ratio = new_size.height / source_size.height;
310
311 let ratio = if fill {
312 f64::max(w_ratio, h_ratio)
313 } else {
314 f64::min(w_ratio, h_ratio)
315 };
316
317 if ratio >= 1.0 {
318 return None;
319 }
320
321 let nw = u64::max((source_size.width * ratio).round() as _, 1);
322 let nh = u64::max((source_size.height * ratio).round() as _, 1);
323
324 const MAX: u64 = Px::MAX.0 as _;
325
326 let r = if nw > MAX {
327 let ratio = MAX as f64 / source_size.width;
328 (Px::MAX, Px(i32::max((source_size.height * ratio).round() as _, 1)))
329 } else if nh > MAX {
330 let ratio = MAX as f64 / source_size.height;
331 (Px(i32::max((source_size.width * ratio).round() as _, 1)), Px::MAX)
332 } else {
333 (Px(nw as _), Px(nh as _))
334 }
335 .into();
336
337 Some(r)
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
342#[non_exhaustive]
343pub enum ImageDataFormat {
344 Bgra8 {
349 size: PxSize,
351 density: Option<PxDensity2d>,
353 original_color_type: ColorType,
355 },
356
357 A8 {
362 size: PxSize,
364 },
365
366 FileExtension(Txt),
371
372 MimeType(Txt),
377
378 Unknown,
382}
383impl From<Txt> for ImageDataFormat {
384 fn from(ext_or_mime: Txt) -> Self {
385 if ext_or_mime.contains('/') {
386 ImageDataFormat::MimeType(ext_or_mime)
387 } else {
388 ImageDataFormat::FileExtension(ext_or_mime)
389 }
390 }
391}
392impl From<&str> for ImageDataFormat {
393 fn from(ext_or_mime: &str) -> Self {
394 Txt::from_str(ext_or_mime).into()
395 }
396}
397impl From<PxSize> for ImageDataFormat {
398 fn from(bgra8_size: PxSize) -> Self {
399 ImageDataFormat::Bgra8 {
400 size: bgra8_size,
401 density: None,
402 original_color_type: ColorType::BGRA8,
403 }
404 }
405}
406impl PartialEq for ImageDataFormat {
407 fn eq(&self, other: &Self) -> bool {
408 match (self, other) {
409 (Self::FileExtension(l0), Self::FileExtension(r0)) => l0 == r0,
410 (Self::MimeType(l0), Self::MimeType(r0)) => l0 == r0,
411 (
412 Self::Bgra8 {
413 size: s0,
414 density: p0,
415 original_color_type: oc0,
416 },
417 Self::Bgra8 {
418 size: s1,
419 density: p1,
420 original_color_type: oc1,
421 },
422 ) => s0 == s1 && density_key(*p0) == density_key(*p1) && oc0 == oc1,
423 (Self::Unknown, Self::Unknown) => true,
424 _ => false,
425 }
426 }
427}
428impl Eq for ImageDataFormat {}
429impl std::hash::Hash for ImageDataFormat {
430 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
431 core::mem::discriminant(self).hash(state);
432 match self {
433 ImageDataFormat::Bgra8 {
434 size,
435 density,
436 original_color_type,
437 } => {
438 size.hash(state);
439 density_key(*density).hash(state);
440 original_color_type.hash(state)
441 }
442 ImageDataFormat::A8 { size } => {
443 size.hash(state);
444 }
445 ImageDataFormat::FileExtension(ext) => ext.hash(state),
446 ImageDataFormat::MimeType(mt) => mt.hash(state),
447 ImageDataFormat::Unknown => {}
448 }
449 }
450}
451
452fn density_key(density: Option<PxDensity2d>) -> Option<(u16, u16)> {
453 density.map(|s| ((s.width.ppcm() * 3.0) as u16, (s.height.ppcm() * 3.0) as u16))
454}
455
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
458#[non_exhaustive]
459pub struct ImageEntryMetadata {
460 pub parent: ImageId,
464 pub index: usize,
466 pub kind: ImageEntryKind,
468}
469impl ImageEntryMetadata {
470 pub fn new(parent: ImageId, index: usize, kind: ImageEntryKind) -> Self {
472 Self { parent, index, kind }
473 }
474}
475
476#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478#[non_exhaustive]
479pub struct ImageMetadata {
480 pub id: ImageId,
482 pub size: PxSize,
484 pub density: Option<PxDensity2d>,
486 pub is_mask: bool,
488 pub original_color_type: ColorType,
490 pub parent: Option<ImageEntryMetadata>,
494
495 pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
497}
498impl ImageMetadata {
499 pub fn new(id: ImageId, size: PxSize, is_mask: bool, original_color_type: ColorType) -> Self {
501 Self {
502 id,
503 size,
504 density: None,
505 is_mask,
506 original_color_type,
507 parent: None,
508 extensions: vec![],
509 }
510 }
511}
512impl Default for ImageMetadata {
513 fn default() -> Self {
514 Self {
515 id: ImageId::INVALID,
516 size: Default::default(),
517 density: Default::default(),
518 is_mask: Default::default(),
519 original_color_type: ColorType::BGRA8,
520 parent: Default::default(),
521 extensions: vec![],
522 }
523 }
524}
525
526#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
528#[non_exhaustive]
529pub enum ImageEntryKind {
530 Page,
532 Reduced {
536 synthetic: bool,
538 },
539 Other {
541 kind: Txt,
545 },
546}
547impl ImageEntryKind {
548 fn discriminant(&self) -> u8 {
549 match self {
550 ImageEntryKind::Page => 0,
551 ImageEntryKind::Reduced { .. } => 1,
552 ImageEntryKind::Other { .. } => 2,
553 }
554 }
555}
556impl std::cmp::Ord for ImageEntryKind {
557 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
558 self.discriminant().cmp(&other.discriminant())
559 }
560}
561impl std::cmp::PartialOrd for ImageEntryKind {
562 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
563 Some(self.cmp(other))
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
573#[non_exhaustive]
574pub struct ImageDecoded {
575 pub meta: ImageMetadata,
577
578 pub partial: Option<PartialImageKind>,
584
585 pub pixels: IpcBytes,
589 pub is_opaque: bool,
591}
592impl Default for ImageDecoded {
593 fn default() -> Self {
594 Self {
595 meta: Default::default(),
596 partial: Default::default(),
597 pixels: Default::default(),
598 is_opaque: true,
599 }
600 }
601}
602impl ImageDecoded {
603 pub fn new(meta: ImageMetadata, pixels: IpcBytes, is_opaque: bool) -> Self {
605 Self {
606 meta,
607 partial: None,
608 pixels,
609 is_opaque,
610 }
611 }
612}
613
614#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616#[non_exhaustive]
617pub enum PartialImageKind {
618 Placeholder {
622 pixel_size: PxSize,
624 },
625 Rows {
629 y: Px,
633 height: Px,
635 },
636}
637
638bitflags! {
639 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
643 pub struct ImageFormatCapability: u32 {
644 const ENCODE = 1 << 0;
646 const DECODE_ENTRIES = 1 << 1;
648 const ENCODE_ENTRIES = (1 << 2) | ImageFormatCapability::ENCODE.bits();
650 const DECODE_PROGRESSIVE = 1 << 3;
655 }
656}
657
658#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
662#[non_exhaustive]
663pub struct ImageFormat {
664 pub display_name: Txt,
666
667 pub media_type_suffixes: Txt,
671
672 pub file_extensions: Txt,
676
677 pub magic_numbers: Txt,
681
682 pub capabilities: ImageFormatCapability,
684}
685impl ImageFormat {
686 #[deprecated = "use `from_static2`, it will replace this function next breaking release"]
692 pub const fn from_static(
693 display_name: &'static str,
694 media_type_suffixes: &'static str,
695 file_extensions: &'static str,
696 capabilities: ImageFormatCapability,
697 ) -> Self {
698 assert!(media_type_suffixes.is_ascii());
699 Self {
700 display_name: Txt::from_static(display_name),
701 media_type_suffixes: Txt::from_static(media_type_suffixes),
702 file_extensions: Txt::from_static(file_extensions),
703 magic_numbers: Txt::from_static(""),
704 capabilities,
705 }
706 }
707
708 pub const fn from_static2(
714 display_name: &'static str,
715 media_type_suffixes: &'static str,
716 file_extensions: &'static str,
717 magic_numbers: &'static str,
718 capabilities: ImageFormatCapability,
719 ) -> Self {
720 assert!(media_type_suffixes.is_ascii());
721 assert!(magic_numbers.is_ascii());
722 Self {
723 display_name: Txt::from_static(display_name),
724 media_type_suffixes: Txt::from_static(media_type_suffixes),
725 file_extensions: Txt::from_static(file_extensions),
726 magic_numbers: Txt::from_static(magic_numbers),
727 capabilities,
728 }
729 }
730
731 pub fn media_type_suffixes_iter(&self) -> impl Iterator<Item = &str> {
733 self.media_type_suffixes.split(',').map(|e| e.trim())
734 }
735
736 pub fn media_types(&self) -> impl Iterator<Item = Txt> {
738 self.media_type_suffixes_iter().map(Txt::from_str)
739 }
740
741 pub fn file_extensions_iter(&self) -> impl Iterator<Item = &str> {
743 self.file_extensions.split(',').map(|e| e.trim())
744 }
745
746 pub fn matches(&self, f: &str) -> bool {
750 let f = f.strip_prefix('.').unwrap_or(f);
751 let f = f.strip_prefix("image/").unwrap_or(f);
752 self.media_type_suffixes_iter().any(|e| e.eq_ignore_ascii_case(f)) || self.file_extensions_iter().any(|e| e.eq_ignore_ascii_case(f))
753 }
754
755 pub fn matches_magic(&self, file_prefix: &[u8]) -> bool {
759 'search: for magic in self.magic_numbers.split(',') {
760 if magic.is_empty() || magic.len() > file_prefix.len() * 2 {
761 continue 'search;
762 }
763 'm: for (c, b) in magic.as_bytes().chunks_exact(2).zip(file_prefix) {
764 if c == b"xx" {
765 continue 'm;
766 }
767 fn decode(c: u8) -> u8 {
768 if c >= b'a' { c - b'a' + 10 } else { c - b'0' }
769 }
770 let c = (decode(c[0]) << 4) | decode(c[1]);
771 if c != *b {
772 continue 'search;
773 }
774 }
775 return true;
776 }
777 false
778 }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
783#[non_exhaustive]
784pub struct ColorType {
785 pub name: Txt,
787 pub bits: u8,
789 pub channels: u8,
791}
792impl ColorType {
793 pub const fn new(name: Txt, bits: u8, channels: u8) -> Self {
795 Self { name, bits, channels }
796 }
797
798 pub fn bits_per_pixel(&self) -> u16 {
800 self.bits as u16 * self.channels as u16
801 }
802
803 pub fn bytes_per_pixel(&self) -> u16 {
805 self.bits_per_pixel() / 8
806 }
807}
808impl ColorType {
809 pub const BGRA8: ColorType = ColorType::new(Txt::from_static("BGRA8"), 8, 4);
811 pub const RGBA8: ColorType = ColorType::new(Txt::from_static("RGBA8"), 8, 4);
813
814 pub const A8: ColorType = ColorType::new(Txt::from_static("A8"), 8, 4);
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize)]
820#[non_exhaustive]
821pub struct ImageEncodeRequest {
822 pub id: ImageId,
824
825 pub entries: Vec<(ImageId, ImageEntryKind)>,
829
830 pub format: Txt,
832}
833impl ImageEncodeRequest {
834 pub fn new(id: ImageId, format: Txt) -> Self {
836 Self {
837 id,
838 entries: vec![],
839 format,
840 }
841 }
842}