use std::sync::{Arc, Mutex};
use gio::glib;
use gio::glib::clone::Downgrade;
use gio::prelude::*;
#[cfg(feature = "gdk4")]
use glycin_utils::safe_math::*;
pub use glycin_utils::{FrameDetails, MemoryFormat};
use glycin_utils::{ImageInfo, MemoryFormatSelection, RemoteImage};
use zbus::zvariant::OwnedObjectPath;
use crate::api_common::*;
pub use crate::config::MimeType;
use crate::dbus::*;
use crate::error::ResultExt;
use crate::pool::{Pool, PooledProcess};
use crate::util::spawn_detached;
use crate::{config, ErrorCtx};
#[derive(Debug)]
pub struct Loader {
source: Source,
pool: Arc<Pool>,
cancellable: gio::Cancellable,
use_expose_base_dir: bool,
pub(crate) apply_transformations: bool,
pub(crate) sandbox_selector: SandboxSelector,
pub(crate) memory_format_selection: MemoryFormatSelection,
}
static_assertions::assert_impl_all!(Loader: Send, Sync);
impl Loader {
pub fn new(file: gio::File) -> Self {
Self::new_source(Source::File(file))
}
pub unsafe fn new_stream(stream: impl IsA<gio::InputStream>) -> Self {
Self::new_source(Source::Stream(GInputStreamSend::new(stream.upcast())))
}
pub fn new_bytes(bytes: glib::Bytes) -> Self {
let stream = gio::MemoryInputStream::from_bytes(&bytes);
unsafe { Self::new_stream(stream) }
}
pub fn new_vec(buf: Vec<u8>) -> Self {
let bytes = glib::Bytes::from_owned(buf);
Self::new_bytes(bytes)
}
pub(crate) fn new_source(source: Source) -> Self {
Self {
source,
pool: Pool::global(),
cancellable: gio::Cancellable::new(),
apply_transformations: true,
use_expose_base_dir: false,
sandbox_selector: SandboxSelector::default(),
memory_format_selection: MemoryFormatSelection::all(),
}
}
pub fn sandbox_selector(&mut self, sandbox_selector: SandboxSelector) -> &mut Self {
self.sandbox_selector = sandbox_selector;
self
}
pub fn cancellable(&mut self, cancellable: impl IsA<gio::Cancellable>) -> &mut Self {
self.cancellable = cancellable.upcast();
self
}
pub fn apply_transformations(&mut self, apply_transformations: bool) -> &mut Self {
self.apply_transformations = apply_transformations;
self
}
pub fn accepted_memory_formats(
&mut self,
memory_format_selection: MemoryFormatSelection,
) -> &mut Self {
self.memory_format_selection = memory_format_selection;
self
}
pub fn use_expose_base_dir(&mut self, use_epose_base_dir: bool) -> &mut Self {
self.use_expose_base_dir = use_epose_base_dir;
self
}
pub async fn load(mut self) -> Result<Image, ErrorCtx> {
let source = self.source.send();
let loader_alive = Arc::new(());
let process_basics = spin_up_loader(
source,
self.use_expose_base_dir,
&self.pool,
&self.cancellable,
&self.sandbox_selector,
loader_alive.downgrade(),
)
.await
.err_no_context(&self.cancellable)?;
let process = process_basics.process.use_();
let info = process
.init(process_basics.g_file_worker, &process_basics.mime_type)
.await
.err_context(&process, &self.cancellable)?;
let path = info.frame_request.clone();
self.cancellable.connect_cancelled(glib::clone!(
#[strong(rename_to=process)]
process_basics.process,
move |_| {
tracing::debug!("Terminating loader");
crate::util::spawn_detached(process.use_().done(path))
}
));
Ok(Image {
process: process_basics.process,
info,
loader: self,
mime_type: process_basics.mime_type,
active_sandbox_mechanism: process_basics.sandbox_mechanism,
loader_alive: Default::default(),
})
}
pub async fn supported_mime_types() -> Vec<MimeType> {
config::Config::cached()
.await
.image_loader
.keys()
.cloned()
.collect()
}
pub const DEFAULT_MIME_TYPES: &'static [&'static str] = &[
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"image/tiff",
"image/x-tga",
"image/vnd-ms.dds",
"image/x-dds",
"image/bmp",
"image/vnd.microsoft.icon",
"image/vnd.radiance",
"image/x-exr",
"image/x-portable-bitmap",
"image/x-portable-graymap",
"image/x-portable-pixmap",
"image/x-portable-anymap",
"image/x-qoi",
"image/qoi",
"image/avif",
"image/heif",
"image/jxl",
"image/svg+xml",
"image/svg+xml-compressed",
];
}
#[derive(Debug)]
pub struct Image {
pub(crate) loader: Loader,
pub(crate) process: Arc<PooledProcess<LoaderProxy<'static>>>,
info: RemoteImage,
mime_type: MimeType,
active_sandbox_mechanism: SandboxMechanism,
loader_alive: Mutex<Arc<()>>,
}
static_assertions::assert_impl_all!(Image: Send, Sync);
impl Drop for Image {
fn drop(&mut self) {
self.process.use_().done_background(&self);
*self.loader_alive.lock().unwrap() = Arc::new(());
spawn_detached(self.loader.pool.clone().clean_loaders());
}
}
impl Image {
pub async fn next_frame(&self) -> Result<Frame, ErrorCtx> {
let process = self.process.use_();
process
.request_frame(glycin_utils::FrameRequest::default(), self)
.await
.map_err(Into::into)
.err_context(&process, &self.cancellable())
}
pub async fn specific_frame(&self, frame_request: FrameRequest) -> Result<Frame, ErrorCtx> {
let process = self.process.use_();
process
.request_frame(frame_request.request, self)
.await
.map_err(Into::into)
.err_context(&process, &self.cancellable())
}
pub fn info(&self) -> &ImageInfo {
&self.info.details
}
pub(crate) fn frame_request_path(&self) -> OwnedObjectPath {
self.info.frame_request.clone()
}
pub fn mime_type(&self) -> MimeType {
self.mime_type.clone()
}
pub fn format_name(&self) -> Option<String> {
self.info().format_name.as_ref().cloned()
}
pub fn loader_file(&self) -> Option<gio::File> {
self.loader.source.file()
}
#[deprecated]
pub fn file(&self) -> gio::File {
if let Source::File(file) = &self.loader.source {
file.clone()
} else {
gio::File::for_uri("invalid:")
}
}
pub fn cancellable(&self) -> gio::Cancellable {
self.loader.cancellable.clone()
}
pub fn active_sandbox_mechanism(&self) -> SandboxMechanism {
self.active_sandbox_mechanism
}
}
impl Drop for Loader {
fn drop(&mut self) {
}
}
#[derive(Debug, Clone)]
pub struct Frame {
pub(crate) buffer: glib::Bytes,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) stride: u32,
pub(crate) memory_format: MemoryFormat,
pub(crate) delay: Option<std::time::Duration>,
pub(crate) details: FrameDetails,
pub(crate) color_state: ColorState,
}
impl Frame {
pub fn buf_bytes(&self) -> glib::Bytes {
self.buffer.clone()
}
pub fn buf_slice(&self) -> &[u8] {
self.buffer.as_ref()
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn stride(&self) -> u32 {
self.stride
}
pub fn memory_format(&self) -> MemoryFormat {
self.memory_format
}
pub fn color_state(&self) -> &ColorState {
&self.color_state
}
pub fn delay(&self) -> Option<std::time::Duration> {
self.delay
}
pub fn details(&self) -> &FrameDetails {
&self.details
}
#[cfg(feature = "gdk4")]
pub fn texture(&self) -> gdk::Texture {
use crate::memory_texture_builder::MemoryTextureBuilder;
let builder = MemoryTextureBuilder::new();
builder.set_bytes(Some(&self.buffer));
builder.set_width(self.width().try_i32().unwrap());
builder.set_height(self.height().try_i32().unwrap());
builder.set_stride(self.stride().try_usize().unwrap());
builder.set_format(crate::util::gdk_memory_format(self.memory_format()));
let color_state = crate::util::gdk_color_state(&self.color_state).unwrap_or_else(|_| {
tracing::warn!("Unsupported color state: {:?}", self.color_state);
gdk::ColorState::srgb()
});
builder.set_color_state(Some(&color_state));
builder.build()
}
}
#[derive(Default, Debug, Clone)]
#[must_use]
pub struct FrameRequest {
request: glycin_utils::FrameRequest,
}
impl FrameRequest {
pub fn new() -> Self {
Self::default()
}
pub fn scale(mut self, width: u32, height: u32) -> Self {
self.request.scale = Some((width, height));
self
}
pub fn clip(mut self, x: u32, y: u32, width: u32, height: u32) -> Self {
self.request.clip = Some((x, y, width, height));
self
}
}
#[cfg(test)]
mod test {
use super::*;
#[allow(dead_code)]
fn ensure_futures_are_send() {
gio::glib::spawn_future(async {
let loader = Loader::new(gio::File::for_uri("invalid"));
let image = loader.load().await.unwrap();
image.next_frame().await.unwrap();
});
}
}