1use std::pin::Pin;
2use std::sync::{Arc, Mutex, OnceLock};
3
4#[cfg(feature = "builtin")]
5use futures_util::FutureExt;
6use gio::glib;
7use gio::prelude::*;
8pub use glycin_common::MemoryFormat;
9use glycin_common::{ColorProfilePreference, MemoryFormatInfo, MemoryFormatSelection};
10#[cfg(feature = "builtin")]
11use glycin_utils::LoaderImplementation;
12use glycin_utils::safe_math::*;
13use glycin_utils::{ByteData, FungibleMemory};
14use gufo_common::cicp::Cicp;
15use gufo_common::orientation::{Orientation, Rotation};
16use gufo_common::physical_dimension;
17use util::{CancellableFuture, ShortcutErrorFuture, TimeoutFuture};
18#[cfg(feature = "external")]
19use zbus::zvariant::OwnedObjectPath;
20
21use crate::api::*;
22pub use crate::config::MimeType;
23#[cfg(feature = "external")]
24use crate::dbus::*;
25use crate::error::{ErrorKind, ResultExt};
26use crate::main_context::{MainContextSelector, ProvidesMainContext};
27#[cfg(feature = "external")]
28use crate::pool::{PooledProcess, UsageTracker};
29use crate::source::SourceTransmission;
30use crate::util::spawn_blocking;
31use crate::{Error, MAX_TEXTURE_SIZE, Pool, config, icc, orientation, util};
32
33#[derive(Debug)]
35pub struct Loader {
36 pub(crate) source: Source,
37 pool: Arc<Pool>,
38 pub(crate) cancellable: gio::Cancellable,
39 use_expose_base_dir: bool,
40 pub(crate) apply_transformations: bool,
41 pub(crate) sandbox_selector: SandboxSelector,
42 pub(crate) memory_format_selection: MemoryFormatSelection,
43 pub(crate) limits: Limits,
44 pub(crate) main_context_selector: MainContextSelector,
45 pub(crate) color_convert_icc_srgb: bool,
46}
47
48static_assertions::assert_impl_all!(Loader: Send, Sync);
49
50impl Loader {
51 pub fn new(file: gio::File) -> Self {
53 Self::new_source(Source::File(file))
54 }
55
56 pub unsafe fn new_stream(stream: impl IsA<gio::InputStream>) -> Self {
62 unsafe { Self::new_source(Source::Stream(GInputStreamSend::new(stream.upcast()))) }
63 }
64
65 pub fn new_bytes(bytes: glib::Bytes) -> Self {
67 let stream = gio::MemoryInputStream::from_bytes(&bytes);
68 unsafe { Self::new_stream(stream) }
69 }
70
71 pub fn new_vec(buf: Vec<u8>) -> Self {
73 let bytes = glib::Bytes::from_owned(buf);
74 Self::new_bytes(bytes)
75 }
76
77 pub(crate) fn new_source(source: Source) -> Self {
78 Self {
79 source,
80 pool: Pool::global(),
81 cancellable: gio::Cancellable::new(),
82 apply_transformations: true,
83 use_expose_base_dir: false,
84 sandbox_selector: SandboxSelector::default(),
85 memory_format_selection: MemoryFormatSelection::all(),
86 limits: Limits::default(),
87 main_context_selector: MainContextSelector::Auto,
88 color_convert_icc_srgb: true,
89 }
90 }
91
92 pub fn sandbox_selector(&mut self, sandbox_selector: SandboxSelector) -> &mut Self {
96 self.sandbox_selector = sandbox_selector;
97 self
98 }
99
100 pub fn cancellable(&mut self, cancellable: impl IsA<gio::Cancellable>) -> &mut Self {
102 self.cancellable = cancellable.upcast();
103 self
104 }
105
106 pub fn apply_transformations(&mut self, apply_transformations: bool) -> &mut Self {
113 self.apply_transformations = apply_transformations;
114 self
115 }
116
117 pub fn accepted_memory_formats(
122 &mut self,
123 memory_format_selection: MemoryFormatSelection,
124 ) -> &mut Self {
125 self.memory_format_selection = memory_format_selection;
126 self
127 }
128
129 pub fn color_convert_icc_srgb(&mut self, convert: bool) -> &mut Self {
133 self.color_convert_icc_srgb = convert;
134 self
135 }
136
137 pub fn use_expose_base_dir(&mut self, use_epose_base_dir: bool) -> &mut Self {
147 self.use_expose_base_dir = use_epose_base_dir;
148 self
149 }
150
151 pub fn pool(&mut self, pool: Arc<Pool>) -> &mut Self {
152 self.pool = pool;
153 self
154 }
155
156 pub fn limits(&mut self, limits: Limits) -> &mut Self {
157 self.limits = limits;
158 self
159 }
160
161 pub fn main_context_selector(&mut self, selector: MainContextSelector) -> &mut Self {
162 self.main_context_selector = selector;
163 self
164 }
165
166 pub fn load(self) -> Pin<Box<dyn Future<Output = Result<Image, Error>> + Send>> {
168 self.load_with_sync(false)
169 }
170
171 pub(crate) fn load_with_sync(
180 mut self,
181 sync: bool,
182 ) -> Pin<Box<dyn Future<Output = Result<Image, Error>> + Send>> {
183 Box::pin(async move {
184 tracing::debug!(image = self.source.display(), "Loading image");
185
186 let source = self.source.send();
187 let main_context = self.main_context();
188 let cancellable = self.cancellable.clone();
189 let timeout = self.limits.inner.timeout;
190
191 let f = move || {
192 async move { self.load_internal(source, sync).await }
193 .make_cancellable(cancellable)
194 .enforce_timeout(timeout)
195 };
196
197 main_context.spawn_from_within(f).await?
198 })
199 }
200
201 async fn load_internal(self, source: Source, sync: bool) -> Result<Image, Error> {
202 let loader_context = ProcessorContext::new(
203 source,
204 self.use_expose_base_dir,
205 &self.sandbox_selector,
206 sync,
207 )
208 .await?;
209
210 let loader = loader_context
211 .loader(self.pool.clone(), &self.cancellable)
212 .await?;
213
214 match loader {
215 #[cfg(feature = "external")]
216 Processor::Binary(binary_loader) => self.load_internal_external(binary_loader).await,
217 #[cfg(feature = "builtin")]
218 Processor::Builtin(builtin) => self.load_internal_builtin(builtin).await,
219 }
220 }
221
222 #[cfg(feature = "external")]
223 async fn load_internal_external(
224 self,
225 binary_loader: ExternalProcessor<LoaderProxy<'static>, SourceTransmission>,
226 ) -> Result<Image, Error> {
227 tracing::debug!("Using external loader");
228
229 let process = binary_loader.use_process();
230 let (remote_reader, file_read_future) =
231 binary_loader.source_transmission.spawn_external()?;
232
233 let remote_image_future = process.init(&binary_loader.mime_type, remote_reader);
234
235 let mut remote_image = remote_image_future
237 .join_abort_on_error(file_read_future)
238 .await
239 .err_context(&process)?;
240
241 remote_image.final_seal().await?;
242
243 let mut details = remote_image.details.into_fungible();
244
245 if self.apply_transformations {
246 match Image::transformation_orientation_internal(&details).rotate() {
247 Rotation::_90 | Rotation::_270 => {
248 std::mem::swap(&mut details.width, &mut details.height);
249 }
250 _ => {}
251 }
252 }
253
254 let path = remote_image.frame_request.clone();
255 self.cancellable.connect_cancelled(glib::clone!(
256 #[strong(rename_to=process)]
257 binary_loader.process,
258 move |_| {
259 tracing::debug!("Terminating loader");
260 util::spawn_detached(process.use_().done(path))
261 }
262 ));
263
264 let mime_type = binary_loader.mime_type.clone();
265
266 let image_loader = ImageLoader::Binary(ImageExternalLoader {
267 process: binary_loader.process,
268 active_sandbox_mechanism: binary_loader.sandbox_mechanism,
269 usage_tracker: Mutex::new(Some(binary_loader.usage_tracker)),
270 frame_request: remote_image.frame_request,
271 });
272
273 Ok(Image {
274 image_loader,
275 details: Arc::new(details),
276 loader: self,
277 mime_type,
278 })
279 }
280
281 #[cfg(feature = "builtin")]
282 async fn load_internal_builtin<P: DBusProxy>(
283 self,
284 builtin: BuiltinProcessor<P, SourceTransmission>,
285 ) -> Result<Image, Error> {
286 tracing::debug!("Using builtin loader '{}'", builtin.builtin.common().name());
287
288 let init_function: Box<dyn Fn(_, _, _) -> _ + Send>;
289
290 match builtin.builtin {
291 #[cfg(feature = "builtin-image-rs")]
292 config::BuiltinProcessor::ImageRs(_) => {
293 init_function = Box::new(|stream, mime_type, details| {
294 glycin_image_rs::ImgLoader::load(stream, mime_type, details).map(
295 |(decoder, details)| {
296 (
297 ImageBuiltinLoader::ImageRs(Arc::new(Mutex::new(decoder))),
298 details,
299 )
300 },
301 )
302 });
303 }
304 #[cfg(feature = "builtin-test")]
305 config::BuiltinProcessor::Test(_) => {
306 init_function = Box::new(|stream, mime_type, details| {
307 glycin_test::ImgDecoder::load(stream, mime_type, details).map(
308 |(decoder, details)| {
309 (
310 ImageBuiltinLoader::Test(Arc::new(Mutex::new(decoder))),
311 details,
312 )
313 },
314 )
315 });
316 }
317 }
318
319 let mime_type = builtin.mime_type.clone();
320
321 let (source_reader, file_read_future) = builtin.source_transmission.spawn_builtin();
322
323 let remote_image_future = gio::spawn_blocking(move || {
324 init_function(
325 source_reader,
326 builtin.mime_type.to_string(),
327 glycin_utils::InitializationDetails::default(),
329 )
330 .map_err(|e| Error::from(e.into_loader_error()))
331 })
332 .map(|x| x.map_err(|e| ErrorKind::panic(e).err()));
333
334 let (image_loader, image_details) = remote_image_future
335 .join_abort_on_error(file_read_future)
336 .await??;
337
338 Ok(Image {
339 image_loader: ImageLoader::Builtin(image_loader),
340 details: Arc::new(image_details),
341 loader: self,
342 mime_type,
343 })
344 }
345
346 pub async fn supported_mime_types() -> Vec<MimeType> {
348 config::Config::cached()
349 .await
350 .image_loader
351 .keys()
352 .cloned()
353 .collect()
354 }
355
356 pub const DEFAULT_MIME_TYPES: &'static [&'static str] = &[
358 "image/jpeg",
360 "image/png",
361 "image/gif",
362 "image/webp",
363 "image/tiff",
364 "image/x-tga",
365 "image/x-dds",
366 "image/bmp",
367 "image/x-win-bitmap",
368 "image/vnd.microsoft.icon",
369 "image/vnd.radiance",
370 "image/x-exr",
371 "image/x-portable-bitmap",
372 "image/x-portable-graymap",
373 "image/x-portable-pixmap",
374 "image/x-portable-anymap",
375 "image/x-qoi",
376 "image/qoi",
377 "image/avif",
379 "image/heif",
380 "image/jxl",
382 "image/svg+xml",
384 "image/svg+xml-compressed",
385 ];
386}
387
388#[derive(Debug)]
390pub struct Image {
391 pub(crate) loader: Loader,
392 image_loader: ImageLoader,
393 details: Arc<glycin_utils::ImageDetails<FungibleMemory>>,
394 mime_type: MimeType,
395}
396
397static_assertions::assert_impl_all!(Image: Send, Sync);
398
399impl Drop for Image {
400 fn drop(&mut self) {
401 #[cfg(feature = "external")]
402 #[allow(irrefutable_let_patterns)]
403 if let ImageLoader::Binary(image_loader) = &self.image_loader {
404 let process = image_loader.process.clone();
405 let path = self.frame_request_path();
406 let loader_alive = std::mem::take(&mut *image_loader.usage_tracker.lock().unwrap());
407 util::spawn_detached(async move {
408 if let Err(err) = process.use_().done(path).await {
409 tracing::warn!("Failed to tear down loader: {err}")
410 }
411
412 drop(loader_alive);
413 });
414 }
415 }
416}
417
418impl Image {
419 pub fn next_frame<'a>(
425 &'a mut self,
426 ) -> Pin<Box<dyn Future<Output = Result<Frame, Error>> + 'a + Send>> {
427 self.specific_frame(FrameRequest::default())
428 }
429
430 pub fn specific_frame<'a>(
435 &'a mut self,
436 frame_request: FrameRequest,
437 ) -> Pin<Box<dyn Future<Output = Result<Frame, Error>> + 'a + Send>> {
438 Box::pin(async move {
439 let cancellable = self.loader.cancellable.clone();
440
441 self.specific_frame_internal(frame_request)
442 .make_cancellable(cancellable)
443 .enforce_timeout(self.loader.limits.inner.timeout)
444 .await
445 })
446 }
447
448 async fn specific_frame_internal(&self, frame_request: FrameRequest) -> Result<Frame, Error> {
449 let frame_request = frame_request.request;
450
451 match &self.image_loader {
452 #[cfg(feature = "external")]
453 ImageLoader::Binary(image_loader) => {
454 let process = image_loader.process.use_();
455
456 let frame = process
457 .request_frame(frame_request, self)
458 .await
459 .err_context(&process)?;
460
461 Frame::from_loader(frame, self).await
462 }
463 #[cfg(feature = "builtin")]
464 ImageLoader::Builtin(builtin) => {
465 use glycin_utils::LocalMemory;
466
467 let editor_function: Box<dyn FnOnce() -> _ + Send>;
468
469 match builtin {
470 #[cfg(feature = "builtin-image-rs")]
471 ImageBuiltinLoader::ImageRs(loader) => {
472 let loader: Arc<Mutex<glycin_image_rs::ImgLoader>> = loader.to_owned();
473 editor_function = Box::new(move || {
474 loader
475 .lock()
476 .unwrap()
477 .specific_frame::<LocalMemory>(frame_request)
478 });
479 }
480 #[cfg(feature = "builtin-test")]
481 ImageBuiltinLoader::Test(editor) => {
482 let editor = editor.to_owned();
483 editor_function = Box::new(move || {
484 editor
485 .lock()
486 .unwrap()
487 .specific_frame::<LocalMemory>(frame_request)
488 });
489 }
490 }
491
492 let frame = gio::spawn_blocking(|| {
493 editor_function().map_err(|e| Error::from(e.into_loader_error()))
494 })
495 .await
496 .map_err(|e| ErrorKind::panic(e))??;
497
498 Frame::from_loader(frame, self).await
499 }
500 }
501 }
502
503 pub fn details(&self) -> ImageDetails {
505 ImageDetails::new(self.details.clone())
506 }
507
508 #[cfg(feature = "external")]
510 pub(crate) fn frame_request_path(&self) -> OwnedObjectPath {
511 #[allow(irrefutable_let_patterns)]
512 if let ImageLoader::Binary(image_loader) = &self.image_loader {
513 image_loader.frame_request.clone()
514 } else {
515 todo!()
516 }
517 }
518
519 pub fn mime_type(&self) -> MimeType {
521 self.mime_type.clone()
522 }
523
524 pub fn file(&self) -> Option<gio::File> {
528 self.loader.source.file()
529 }
530
531 pub fn cancellable(&self) -> gio::Cancellable {
533 self.loader.cancellable.clone()
534 }
535
536 pub fn active_sandbox_mechanism(&self) -> SandboxMechanism {
538 match &self.image_loader {
539 #[cfg(feature = "external")]
540 ImageLoader::Binary(image_loader) => image_loader.active_sandbox_mechanism,
541 #[cfg(feature = "builtin")]
542 ImageLoader::Builtin(_) => SandboxMechanism::NotSandboxed,
543 }
544 }
545
546 pub fn transformation_orientation(&self) -> Orientation {
552 Self::transformation_orientation_internal(&self.details)
553 }
554
555 fn transformation_orientation_internal(
556 details: &glycin_utils::ImageDetails<FungibleMemory>,
557 ) -> Orientation {
558 if let Some(orientation) = details.transformation_orientation {
559 orientation
560 } else if !details.transformation_ignore_exif {
561 details
562 .metadata_exif
563 .as_ref()
564 .map(|x| x.to_vec())
565 .and_then(|x| match gufo_exif::Exif::for_vec(x) {
566 Err(err) => {
567 tracing::warn!("exif: Failed to parse data: {err:?}");
568 None
569 }
570 Ok(x) => x.orientation(),
571 })
572 .unwrap_or(Orientation::Id)
573 } else {
574 Orientation::Id
575 }
576 }
577}
578
579#[derive(Debug)]
580enum ImageLoader {
581 #[cfg(feature = "external")]
582 Binary(ImageExternalLoader),
583 #[cfg(feature = "builtin")]
584 Builtin(ImageBuiltinLoader),
585}
586
587#[cfg(feature = "external")]
588#[derive(Debug)]
589struct ImageExternalLoader {
590 process: Arc<PooledProcess<LoaderProxy<'static>>>,
591 active_sandbox_mechanism: SandboxMechanism,
592 usage_tracker: Mutex<Option<Arc<UsageTracker>>>,
593 frame_request: OwnedObjectPath,
594}
595
596#[cfg(feature = "builtin")]
597#[derive(Clone)]
598enum ImageBuiltinLoader {
599 #[cfg(feature = "builtin-image-rs")]
600 ImageRs(Arc<Mutex<glycin_image_rs::ImgLoader>>),
601 #[cfg(feature = "builtin-test")]
602 Test(Arc<Mutex<glycin_test::ImgDecoder>>),
603}
604
605#[cfg(feature = "builtin")]
606impl std::fmt::Debug for ImageBuiltinLoader {
607 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608 f.write_str("ImageBuiltinLoader")
609 }
610}
611
612#[derive(Debug, Clone)]
614pub struct ImageDetails {
615 inner: Arc<glycin_utils::ImageDetails<FungibleMemory>>,
616 metadata: Arc<OnceLock<gufo::Metadata>>,
617}
618
619static_assertions::assert_impl_all!(ImageDetails: Send, Sync);
620
621impl ImageDetails {
622 fn new(inner: Arc<glycin_utils::ImageDetails<FungibleMemory>>) -> Self {
623 Self {
624 inner,
625 metadata: Default::default(),
626 }
627 }
628
629 pub fn width(&self) -> u32 {
630 self.inner.width
631 }
632
633 pub fn height(&self) -> u32 {
634 self.inner.height
635 }
636
637 pub fn info_format_name(&self) -> Option<&str> {
639 self.inner.info_format_name.as_deref()
640 }
641
642 pub fn info_dimensions_text(&self) -> Option<&str> {
643 self.inner.info_dimensions_text.as_deref()
644 }
645
646 pub fn metadata_exif(&self) -> Option<&[u8]> {
647 self.inner.metadata_exif.as_deref()
648 }
649
650 pub fn transformation_orientation(&self) -> Option<Orientation> {
651 self.inner.transformation_orientation
652 }
653
654 pub fn metadata_xmp(&self) -> Option<&[u8]> {
655 self.inner.metadata_xmp.as_deref()
656 }
657
658 pub fn metadata_key_value(&self) -> Option<&std::collections::BTreeMap<String, String>> {
659 self.inner.metadata_key_value.as_ref()
660 }
661
662 pub fn transformation_ignore_exif(&self) -> bool {
663 self.inner.transformation_ignore_exif
664 }
665
666 fn metadata(&self) -> &gufo::Metadata {
667 self.metadata.get_or_init(|| {
668 let mut metadata = gufo::Metadata::new();
669
670 if let Some(exif) = &self.inner.metadata_exif
671 && let Err(err) = metadata.add_raw_exif(exif.to_vec())
672 {
673 tracing::info!("Could parse Exif data: {err}");
674 }
675
676 if let Some(xmp) = &self.inner.metadata_xmp
677 && let Err(err) = metadata.add_raw_xmp(xmp.to_vec())
678 {
679 tracing::info!("Could parse XMP data: {err}");
680 }
681
682 if let Some(key_value) = &self.inner.metadata_key_value
683 && let Err(err) = metadata.add_key_value(key_value.to_owned())
684 {
685 tracing::info!("Could parse key-value data: {err}");
686 }
687
688 metadata
689 })
690 }
691}
692
693#[derive(Debug, Clone)]
695pub struct Frame {
696 pub(crate) buffer: glib::Bytes,
697 pub(crate) width: u32,
698 pub(crate) height: u32,
699 pub(crate) stride: u32,
701 pub(crate) memory_format: MemoryFormat,
702 pub(crate) delay: Option<std::time::Duration>,
703 pub(crate) details: Arc<glycin_utils::FrameDetails<FungibleMemory>>,
704 pub(crate) image_details: ImageDetails,
705 pub(crate) color_state: ColorState,
706}
707
708static_assertions::assert_impl_all!(Frame: Send, Sync);
709
710impl Frame {
711 pub fn buf_bytes(&self) -> glib::Bytes {
712 self.buffer.clone()
713 }
714
715 pub fn buf_slice(&self) -> &[u8] {
716 self.buffer.as_ref()
717 }
718
719 pub fn width(&self) -> u32 {
721 self.width
722 }
723
724 pub fn height(&self) -> u32 {
726 self.height
727 }
728
729 pub fn stride(&self) -> u32 {
731 self.stride
732 }
733
734 pub fn memory_format(&self) -> MemoryFormat {
735 self.memory_format
736 }
737
738 pub fn color_state(&self) -> &ColorState {
739 &self.color_state
740 }
741
742 pub fn delay(&self) -> Option<std::time::Duration> {
746 self.delay
747 }
748
749 pub fn details(&self) -> FrameDetails {
750 FrameDetails::new(self.details.clone(), self.image_details.clone())
751 }
752
753 #[cfg(feature = "gdk4")]
754 pub fn texture(&self) -> gdk::Texture {
755 let color_state = crate::util::gdk_color_state(&self.color_state).unwrap_or_else(|_| {
756 tracing::warn!("Unsupported color state: {:?}", self.color_state);
757 gdk::ColorState::srgb()
758 });
759
760 gdk::MemoryTextureBuilder::new()
761 .set_bytes(Some(&self.buffer))
762 .set_width(self.width().try_i32().unwrap())
764 .set_height(self.height().try_i32().unwrap())
765 .set_stride(self.stride().try_usize().unwrap())
766 .set_format(crate::util::gdk_memory_format(self.memory_format()))
767 .set_color_state(&color_state)
768 .build()
769 }
770
771 pub(crate) async fn from_loader<B: ByteData>(
772 mut frame: glycin_utils::Frame<B>,
773 image: &Image,
774 ) -> Result<Self, Error> {
775 frame.initial_seal().await?;
776
777 validate_frame(&frame, &image.loader.limits)?;
778
779 let frame = if image.loader.apply_transformations {
780 orientation::apply_exif_orientation(frame.into_fungible(), image)
781 } else {
782 frame.into_fungible()
783 };
784
785 let mut color_state = ColorState::Srgb;
786
787 let cicp = frame
788 .details
789 .color_cicp
790 .and_then(|x| Cicp::from_bytes(&x).ok());
791 let icc_profile = frame.details.color_icc_profile.as_ref().map(|x| x.to_vec());
792 let color_profile_preference = frame.details.color_profile_preference.unwrap_or_default();
793
794 let use_cicp = matches!(color_profile_preference, ColorProfilePreference::Cicp)
796 || icc_profile.is_none();
797
798 let frame = if let Some(cicp) = cicp
799 && use_cicp
800 {
801 color_state = ColorState::Cicp(cicp);
802 frame
803 } else if let Some(icc_profile) = icc_profile {
804 if image.loader.color_convert_icc_srgb {
805 let (frame, icc_result) =
806 spawn_blocking(move || icc::apply_transformation(&icc_profile, frame)).await?;
807
808 match icc_result {
809 Err(err) => {
810 tracing::warn!("Failed to apply ICC profile: {err}");
811 }
812 Ok(new_color_state) => {
813 color_state = new_color_state;
814 }
815 }
816
817 frame
818 } else {
819 color_state = ColorState::IccProfile(icc_profile);
820 frame
821 }
822 } else {
823 frame
824 };
825
826 let mut frame = frame.into_fungible();
827
828 if let Some(target_format) = image
829 .loader
830 .memory_format_selection
831 .best_format_for(frame.memory_format)
832 && frame.memory_format != target_format
833 {
834 frame = util::spawn_blocking(move || {
835 glycin_utils::editing::change_memory_format(&mut frame, target_format)?;
836 Ok::<_, Error>(frame)
837 })
838 .await??;
839 }
840
841 frame.final_seal().await?;
842
843 Ok(Self {
844 buffer: frame.texture.into_gbytes()?,
845 width: frame.width,
846 height: frame.height,
847 stride: frame.stride,
848 memory_format: frame.memory_format,
849 delay: frame.delay.into(),
850 details: Arc::new(frame.details.into_other()?),
851 image_details: image.details(),
852 color_state,
853 })
854 }
855}
856
857#[derive(Debug, Clone)]
858#[must_use]
859pub struct FrameRequest {
861 pub(crate) request: glycin_utils::FrameRequest,
862}
863
864impl Default for FrameRequest {
865 fn default() -> Self {
866 Self::new()
867 }
868}
869
870fn validate_frame<B: ByteData>(
871 frame: &glycin_utils::Frame<B>,
872 limits: &Limits,
873) -> Result<(), Error> {
874 let img_buf = &frame.texture;
875
876 if img_buf.len() < frame.n_bytes()? {
877 return Err(ErrorKind::TextureWrongSize {
878 texture_size: img_buf.len(),
879 frame: format!("{:?}", frame.desc()),
880 }
881 .err());
882 }
883
884 if frame.stride < frame.width.smul(frame.memory_format.n_bytes().u32())? {
885 return Err(ErrorKind::StrideTooSmall(format!("{:?}", frame.desc())).err());
886 }
887
888 if frame.width < 1 || frame.height < 1 {
889 return Err(ErrorKind::WidgthOrHeightZero(format!("{:?}", frame.desc())).err());
890 }
891
892 if (frame.stride as u64).smul(frame.height as u64)? > MAX_TEXTURE_SIZE {
893 return Err(ErrorKind::TextureTooLarge.err());
894 }
895
896 if frame.width > limits.inner.max_dimensions.0 {
897 return Err(ErrorKind::TextureTooLarge.err());
898 }
899
900 if frame.height > limits.inner.max_dimensions.1 {
901 return Err(ErrorKind::TextureTooLarge.err());
902 }
903
904 frame.width.try_i32()?;
906 frame.height.try_i32()?;
907 frame.stride.try_usize()?;
908
909 Ok(())
910}
911
912impl FrameRequest {
913 pub fn new() -> Self {
914 let mut request = glycin_utils::FrameRequest::default();
915 request.loop_animation = true;
916
917 Self { request }
918 }
919
920 pub fn scale(mut self, width: u32, height: u32) -> Self {
921 self.request.scale = Some((width, height));
922 self
923 }
924
925 pub fn clip(mut self, x: u32, y: u32, width: u32, height: u32) -> Self {
926 self.request.clip = Some((x, y, width, height));
927 self
928 }
929
930 pub fn loop_animation(mut self, loop_animation: bool) -> Self {
935 self.request.loop_animation = loop_animation;
936 self
937 }
938}
939
940#[derive(Debug, Clone)]
942pub struct FrameDetails {
943 inner: Arc<glycin_utils::FrameDetails<FungibleMemory>>,
944 image_details: ImageDetails,
945}
946
947impl FrameDetails {
948 fn new(
949 inner: Arc<glycin_utils::FrameDetails<FungibleMemory>>,
950 image_details: ImageDetails,
951 ) -> Self {
952 Self {
953 inner,
954 image_details,
955 }
956 }
957
958 pub fn color_cicp(&self) -> Option<crate::Cicp> {
959 self.inner
960 .color_cicp
961 .and_then(|x| crate::Cicp::from_bytes(&x).ok())
962 }
963
964 pub fn color_icc_profile(&self) -> Option<&[u8]> {
965 self.inner.color_icc_profile.as_deref()
966 }
967
968 pub fn color_profile_preference(&self) -> ColorProfilePreference {
969 self.inner.color_profile_preference.unwrap_or_default()
970 }
971
972 pub fn info_alpha_channel(&self) -> Option<bool> {
973 self.inner.info_alpha_channel
974 }
975
976 pub fn info_bit_depth(&self) -> Option<u8> {
977 self.inner.info_bit_depth
978 }
979
980 pub fn info_grayscale(&self) -> Option<bool> {
981 self.inner.info_grayscale
982 }
983
984 pub fn n_frame(&self) -> Option<u64> {
985 self.inner.n_frame
986 }
987
988 pub fn pixel_density(&self) -> Option<physical_dimension::PixelDensity> {
989 self.inner
990 .pixel_density
991 .clone()
992 .or_else(|| self.image_details.metadata().resolution())
993 }
994
995 pub fn physical_size(&self) -> Option<physical_dimension::PhysicalSize> {
996 self.inner.physical_size.clone()
997 }
998}
999
1000#[cfg(test)]
1001mod test {
1002 use super::*;
1003 #[allow(dead_code)]
1004 fn ensure_futures_are_send() {
1005 gio::glib::spawn_future(async {
1006 let loader = Loader::new(gio::File::for_uri("invalid"));
1007 let mut image = loader.load().await.unwrap();
1008 image.next_frame().await.unwrap();
1009 });
1010 }
1011}