use std::collections::BTreeMap;
use std::pin::Pin;
use std::sync::Arc;
use glib::object::IsA;
use glib::prelude::*;
use glycin_common::MemoryFormatInfo;
use glycin_utils::{ByteData, DimensionTooLargerError, FungibleMemory, MemoryFormat};
#[cfg(feature = "builtin")]
use crate::config;
use crate::config::{Config, ImageEditorConfig};
use crate::error::ResultExt;
use crate::pool::Pool;
use crate::util::CancellableFuture;
use crate::{Error, ErrorKind, MimeType, Processor, ProcessorContext, SandboxSelector};
#[derive(Debug)]
pub struct Creator {
mime_type: MimeType,
config: ImageEditorConfig,
pool: Arc<Pool>,
pub(crate) cancellable: gio::Cancellable,
pub(crate) sandbox_selector: SandboxSelector,
encoding_options: glycin_utils::EncodingOptions,
new_image: glycin_utils::NewImage<FungibleMemory>,
new_frames: Vec<NewFrame>,
}
static_assertions::assert_impl_all!(Creator: Send, Sync);
#[derive(Debug, Clone)]
pub struct FeatureNotSupported;
impl std::fmt::Display for FeatureNotSupported {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Feature not supported by this image format.")
}
}
impl std::error::Error for FeatureNotSupported {}
impl Creator {
pub async fn new(mime_type: MimeType) -> Result<Creator, Error> {
let config = Config::cached().await.editor(&mime_type)?.clone();
Ok(Self {
mime_type,
config,
pool: Pool::global(),
cancellable: gio::Cancellable::new(),
sandbox_selector: SandboxSelector::default(),
encoding_options: glycin_utils::EncodingOptions::default(),
new_image: glycin_utils::NewImage::new(glycin_utils::ImageDetails::new(1, 1), vec![]),
new_frames: vec![],
})
}
pub fn add_frame(
&mut self,
width: u32,
height: u32,
memory_format: MemoryFormat,
texture: Vec<u8>,
) -> Result<&mut NewFrame, Error> {
let stride = memory_format
.n_bytes()
.u32()
.checked_mul(width)
.ok_or(DimensionTooLargerError)?;
let new_frame =
self.add_frame_with_stride(width, height, stride, memory_format, texture)?;
Ok(new_frame)
}
pub fn add_frame_with_stride(
&mut self,
width: u32,
height: u32,
stride: u32,
memory_format: MemoryFormat,
mut texture: Vec<u8>,
) -> Result<&mut NewFrame, Error> {
let pixel_size = memory_format.n_bytes().u32();
let smallest_stride = pixel_size
.checked_mul(width)
.ok_or(DimensionTooLargerError)?;
if smallest_stride > stride {
return Err(ErrorKind::StrideTooSmall(format!(
"Stride is {stride} but must be at least {smallest_stride}"
))
.into());
}
if texture.len() < stride as usize * (height - 1) as usize + smallest_stride as usize {
return Err(ErrorKind::TextureWrongSize {
texture_size: texture.len(),
frame: format!("Stride size: {stride} Image size: {width} x {height}"),
}
.into());
}
if smallest_stride != stride {
let old_stride = stride as usize;
let new_stride = smallest_stride as usize;
let height_ = height as usize;
let mut source = vec![0; new_stride];
for row in 0..height_ {
let old_row_begin = row * old_stride;
let old_row_end = old_row_begin + new_stride;
source.copy_from_slice(&texture[old_row_begin..old_row_end]);
let new_row_begin = row * new_stride;
let new_row_end = new_row_begin + new_stride;
texture[new_row_begin..new_row_end].copy_from_slice(&source);
}
texture.resize(new_stride * height_, 0);
};
let new_frame = NewFrame::new(self.config.clone(), width, height, memory_format, texture);
self.new_frames.push(new_frame);
Ok(self.new_frames.last_mut().unwrap())
}
pub fn create(self) -> Pin<Box<dyn Future<Output = Result<EncodedImage, Error>> + Send>> {
Box::pin(async move {
let cancellable = self.cancellable.clone();
self.load_internal().make_cancellable(cancellable).await
})
}
async fn load_internal(self) -> Result<EncodedImage, Error> {
let mut new_image = self.new_image;
for frame in self.new_frames {
new_image.frames.push(frame.frame()?);
}
let editor_context =
ProcessorContext::new_sourceless(self.mime_type, &self.sandbox_selector).await?;
let editor = editor_context
.editor(self.pool.clone(), &self.cancellable)
.await?;
match editor {
#[cfg(feature = "external")]
Processor::Binary(editor) => {
let process = editor.process.use_();
EncodedImage::new(
process
.create(
&editor.mime_type,
new_image.into_other()?,
self.encoding_options,
)
.await
.map(|x| x.into_fungible())
.err_context(&process)?,
)
.await
}
#[cfg(feature = "builtin")]
Processor::Builtin(builtin) => {
use glycin_utils::EditorImplementation;
use crate::ErrorKind;
let mime_type = builtin.mime_type.to_string();
let encoding_options = self.encoding_options;
let editor_function: Box<dyn FnOnce() -> _ + Send>;
match builtin.builtin {
#[cfg(feature = "builtin-image-rs")]
config::BuiltinProcessor::ImageRs(_) => {
editor_function = Box::new(move || {
glycin_image_rs::ImgEditor::create(
mime_type,
new_image,
encoding_options,
)
});
}
#[cfg(feature = "builtin-test")]
config::BuiltinProcessor::Test(_) => {
editor_function = Box::new(move || {
glycin_test::ImgEditor::create(mime_type, new_image, encoding_options)
});
}
}
let encoded_image = gio::spawn_blocking(|| {
editor_function().map_err(|e| Error::from(e.into_editor_error()))
})
.await
.map_err(|e| ErrorKind::panic(e))??;
EncodedImage::new(encoded_image).await
}
}
}
pub fn set_encoding_quality(&mut self, quality: u8) -> Result<(), FeatureNotSupported> {
if !self.config.creator_encoding_quality {
return Err(FeatureNotSupported);
}
self.encoding_options.quality = Some(quality);
Ok(())
}
pub fn set_encoding_compression(&mut self, compression: u8) -> Result<(), FeatureNotSupported> {
if !self.config.creator_encoding_compression {
return Err(FeatureNotSupported);
}
self.encoding_options.compression = Some(compression);
Ok(())
}
pub fn set_metadata_key_value(
&mut self,
key_value: BTreeMap<String, String>,
) -> Result<(), FeatureNotSupported> {
if !self.config.creator_metadata_key_value {
return Err(FeatureNotSupported);
}
self.new_image.image_info.metadata_key_value = Some(key_value);
Ok(())
}
pub fn add_metadata_key_value(
&mut self,
key: String,
value: String,
) -> Result<(), FeatureNotSupported> {
if !self.config.creator_metadata_key_value {
return Err(FeatureNotSupported);
}
let mut key_value = self
.new_image
.image_info
.metadata_key_value
.clone()
.unwrap_or_default();
key_value.insert(key, value);
self.new_image.image_info.metadata_key_value = Some(key_value);
Ok(())
}
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
}
}
#[derive(Debug)]
pub struct NewFrame {
config: ImageEditorConfig,
width: u32,
height: u32,
memory_format: MemoryFormat,
texture: Vec<u8>,
details: glycin_utils::FrameDetails<FungibleMemory>,
icc_profile: Option<Vec<u8>>,
}
impl NewFrame {
fn new(
config: ImageEditorConfig,
width: u32,
height: u32,
memory_format: MemoryFormat,
texture: Vec<u8>,
) -> NewFrame {
Self {
config,
width,
height,
memory_format,
texture,
details: Default::default(),
icc_profile: Default::default(),
}
}
pub fn set_color_icc_profile(
&mut self,
icc_profile: Option<Vec<u8>>,
) -> Result<(), FeatureNotSupported> {
if !self.config.creator_color_icc_profile {
return Err(FeatureNotSupported);
}
self.icc_profile = icc_profile;
Ok(())
}
fn frame(self) -> Result<glycin_utils::Frame<FungibleMemory>, Error> {
let texture = FungibleMemory::try_from_vec(self.texture)?;
let mut frame = glycin_utils::Frame::<FungibleMemory>::new(
self.width,
self.height,
self.memory_format,
texture,
)?;
frame.details = self.details;
if let Some(icc_profile) = self.icc_profile {
let icc_profile = FungibleMemory::try_from_vec(icc_profile)?;
frame.details.color_icc_profile = Some(icc_profile);
}
Ok(frame)
}
}
#[derive(Debug)]
pub struct EncodedImage {
pub(crate) inner: glycin_utils::EncodedImage<FungibleMemory>,
}
impl EncodedImage {
pub async fn new(mut inner: glycin_utils::EncodedImage<FungibleMemory>) -> Result<Self, Error> {
inner.final_seal().await?;
Ok(Self { inner })
}
pub fn data_ref(&self) -> &[u8] {
&self.inner.data
}
pub fn data_full(&self) -> Vec<u8> {
self.inner.data.to_vec()
}
}