use std::{
env, fmt, fs, io, mem, ops,
path::{Path, PathBuf},
sync::Arc,
};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use zng_app::{
view_process::{EncodeError, ViewImage, ViewRenderer},
window::WindowId,
};
use zng_color::Rgba;
use zng_layout::{
context::LayoutMetrics,
unit::{ByteLength, ByteUnits, PxRect, PxSize},
};
use zng_task::{self as task, SignalOnce};
use zng_txt::Txt;
use zng_var::{impl_from_and_into_var, AnyVar, ReadOnlyArcVar};
use zng_view_api::{image::ImageTextureId, ViewProcessOffline};
use crate::render::ImageRenderWindowRoot;
pub use zng_view_api::image::{ImageDataFormat, ImageDownscale, ImageMaskMode, ImagePpi};
pub trait ImageCacheProxy: Send + Sync {
fn get(
&mut self,
key: &ImageHash,
source: &ImageSource,
mode: ImageCacheMode,
downscale: Option<ImageDownscale>,
mask: Option<ImageMaskMode>,
) -> ProxyGetResult {
let _ = (key, source, mode, downscale, mask);
ProxyGetResult::None
}
fn remove(&mut self, key: &ImageHash, purge: bool) -> ProxyRemoveResult {
let _ = (key, purge);
ProxyRemoveResult::None
}
fn clear(&mut self, purge: bool);
}
pub enum ProxyGetResult {
None,
Cache(ImageSource, ImageCacheMode, Option<ImageDownscale>, Option<ImageMaskMode>),
Image(ImageVar),
}
pub enum ProxyRemoveResult {
None,
Remove(ImageHash, bool),
Removed,
}
pub type ImageVar = ReadOnlyArcVar<Img>;
#[derive(Debug, Clone)]
pub struct Img {
pub(super) view: OnceCell<ViewImage>,
render_ids: Arc<Mutex<Vec<RenderImage>>>,
pub(super) done_signal: SignalOnce,
pub(super) cache_key: Option<ImageHash>,
}
impl PartialEq for Img {
fn eq(&self, other: &Self) -> bool {
self.view == other.view
}
}
impl Img {
pub(super) fn new_none(cache_key: Option<ImageHash>) -> Self {
Img {
view: OnceCell::new(),
render_ids: Arc::default(),
done_signal: SignalOnce::new(),
cache_key,
}
}
pub fn new(view: ViewImage) -> Self {
let sig = view.awaiter();
let v = OnceCell::new();
let _ = v.set(view);
Img {
view: v,
render_ids: Arc::default(),
done_signal: sig,
cache_key: None,
}
}
pub fn dummy(error: Option<Txt>) -> Self {
Self::new(ViewImage::dummy(error))
}
pub fn is_loading(&self) -> bool {
match self.view.get() {
Some(v) => !v.is_loaded() && !v.is_error(),
None => true,
}
}
pub fn is_loaded(&self) -> bool {
match self.view.get() {
Some(v) => v.is_loaded(),
None => false,
}
}
pub fn is_error(&self) -> bool {
match self.view.get() {
Some(v) => v.is_error(),
None => false,
}
}
pub fn error(&self) -> Option<Txt> {
match self.view.get() {
Some(v) => v.error().map(Txt::from),
None => None,
}
}
pub fn wait_done(&self) -> impl std::future::Future<Output = ()> + Send + Sync + 'static {
self.done_signal.clone()
}
pub fn size(&self) -> PxSize {
self.view.get().map(|v| v.size()).unwrap_or_else(PxSize::zero)
}
pub fn ppi(&self) -> Option<ImagePpi> {
self.view.get().and_then(|v| v.ppi())
}
pub fn is_opaque(&self) -> bool {
self.view.get().map(|v| v.is_opaque()).unwrap_or(true)
}
pub fn is_mask(&self) -> bool {
self.view.get().map(|v| v.is_mask()).unwrap_or(false)
}
pub fn view(&self) -> Option<&ViewImage> {
self.view.get().filter(|&v| v.is_loaded())
}
pub fn layout_size(&self, ctx: &LayoutMetrics) -> PxSize {
self.calc_size(ctx, ImagePpi::splat(ctx.screen_ppi().0), false)
}
pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_ppi: ImagePpi, ignore_image_ppi: bool) -> PxSize {
let dpi = if ignore_image_ppi {
fallback_ppi
} else {
self.ppi().unwrap_or(fallback_ppi)
};
let s_ppi = ctx.screen_ppi();
let mut size = self.size();
let fct = ctx.scale_factor().0;
size.width *= (s_ppi.0 / dpi.x) * fct;
size.height *= (s_ppi.0 / dpi.y) * fct;
size
}
pub fn pixels(&self) -> Option<zng_view_api::ipc::IpcBytes> {
self.view.get().and_then(|v| v.pixels())
}
pub fn copy_pixels(&self, rect: PxRect) -> Option<(PxRect, Vec<u8>)> {
self.pixels().map(|pixels| {
let area = PxRect::from_size(self.size()).intersection(&rect).unwrap_or_default();
if area.size.width.0 == 0 || area.size.height.0 == 0 {
(area, vec![])
} else {
let x = area.origin.x.0 as usize;
let y = area.origin.y.0 as usize;
let width = area.size.width.0 as usize;
let height = area.size.height.0 as usize;
let pixel = if self.is_mask() { 1 } else { 4 };
let mut bytes = Vec::with_capacity(width * height * pixel);
let row_stride = self.size().width.0 as usize * pixel;
for l in y..y + height {
let line_start = l * row_stride + x * pixel;
let line_end = line_start + width * pixel;
let line = &pixels[line_start..line_end];
bytes.extend_from_slice(line);
}
(area, bytes)
}
})
}
pub async fn encode(&self, format: Txt) -> std::result::Result<zng_view_api::ipc::IpcBytes, EncodeError> {
self.done_signal.clone().await;
if let Some(e) = self.error() {
Err(EncodeError::Encode(e))
} else {
self.view.get().unwrap().encode(format).await
}
}
pub async fn save(&self, path: impl Into<PathBuf>) -> io::Result<()> {
let path = path.into();
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
self.save_impl(Txt::from_str(ext), path).await
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"could not determinate image format from path extension",
))
}
}
pub async fn save_with_format(&self, format: Txt, path: impl Into<PathBuf>) -> io::Result<()> {
self.save_impl(format, path.into()).await
}
async fn save_impl(&self, format: Txt, path: PathBuf) -> io::Result<()> {
let view = self.view.get().unwrap();
let data = view
.encode(format)
.await
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
task::wait(move || fs::write(path, &data[..])).await
}
}
impl zng_app::render::Img for Img {
fn renderer_id(&self, renderer: &ViewRenderer) -> ImageTextureId {
if self.is_loaded() {
let mut rms = self.render_ids.lock();
if let Some(rm) = rms.iter().find(|k| &k.renderer == renderer) {
return rm.image_id;
}
let key = match renderer.use_image(self.view.get().unwrap()) {
Ok(k) => {
if k == ImageTextureId::INVALID {
tracing::error!("received INVALID from `use_image`");
return k;
}
k
}
Err(ViewProcessOffline) => {
tracing::debug!("respawned `add_image`, will return INVALID");
return ImageTextureId::INVALID;
}
};
rms.push(RenderImage {
image_id: key,
renderer: renderer.clone(),
});
key
} else {
ImageTextureId::INVALID
}
}
}
struct RenderImage {
image_id: ImageTextureId,
renderer: ViewRenderer,
}
impl Drop for RenderImage {
fn drop(&mut self) {
let _ = self.renderer.delete_image_use(self.image_id);
}
}
impl fmt::Debug for RenderImage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.image_id, f)
}
}
#[derive(Clone, Copy)]
pub struct ImageHash([u8; 32]);
impl ImageHash {
pub fn compute(data: &[u8]) -> Self {
let mut h = Self::hasher();
h.update(data);
h.finish()
}
pub fn hasher() -> ImageHasher {
ImageHasher::default()
}
}
impl fmt::Debug for ImageHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_tuple("ImageHash").field(&self.0).finish()
} else {
use base64::*;
write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
}
}
}
impl fmt::Display for ImageHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::hash::Hash for ImageHash {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let h64 = [
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
];
state.write_u64(u64::from_ne_bytes(h64))
}
}
impl PartialEq for ImageHash {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for ImageHash {}
pub struct ImageHasher(sha2::Sha512_256);
impl Default for ImageHasher {
fn default() -> Self {
use sha2::Digest;
Self(sha2::Sha512_256::new())
}
}
impl ImageHasher {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, data: impl AsRef<[u8]>) {
use sha2::Digest;
self.0.update(data);
}
pub fn finish(self) -> ImageHash {
use sha2::Digest;
ImageHash(self.0.finalize().as_slice().try_into().unwrap())
}
}
impl std::hash::Hasher for ImageHasher {
fn finish(&self) -> u64 {
tracing::warn!("Hasher::finish called for ImageHasher");
use sha2::Digest;
let hash = self.0.clone().finalize();
u64::from_le_bytes(hash[..8].try_into().unwrap())
}
fn write(&mut self, bytes: &[u8]) {
self.update(bytes);
}
}
type RenderFn = Arc<Box<dyn Fn(&ImageRenderArgs) -> Box<dyn ImageRenderWindowRoot> + Send + Sync>>;
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ImageRenderArgs {
pub parent: Option<WindowId>,
}
#[derive(Clone)]
pub enum ImageSource {
Read(PathBuf),
#[cfg(feature = "http")]
Download(crate::task::http::Uri, Option<Txt>),
Static(ImageHash, &'static [u8], ImageDataFormat),
Data(ImageHash, Arc<Vec<u8>>, ImageDataFormat),
Render(RenderFn, Option<ImageRenderArgs>),
Image(ImageVar),
}
impl ImageSource {
pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, ppi: Option<ImagePpi>) -> Self {
let size = size.into();
let color = color.into();
let len = size.width.0 as usize * size.height.0 as usize * 4;
let mut data = vec![0; len];
for bgra in data.chunks_exact_mut(4) {
let rgba = color.to_bytes();
bgra[0] = rgba[2];
bgra[1] = rgba[1];
bgra[2] = rgba[0];
bgra[3] = rgba[3];
}
Self::from_data(Arc::new(data), ImageDataFormat::Bgra8 { size, ppi })
}
pub fn from_data(data: Arc<Vec<u8>>, format: ImageDataFormat) -> Self {
let mut hasher = ImageHasher::default();
hasher.update(&data[..]);
let hash = hasher.finish();
Self::Data(hash, data, format)
}
pub fn from_static(data: &'static [u8], format: ImageDataFormat) -> Self {
let mut hasher = ImageHasher::default();
hasher.update(data);
let hash = hasher.finish();
Self::Static(hash, data, format)
}
pub fn hash128(&self, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> Option<ImageHash> {
match self {
ImageSource::Read(p) => Some(Self::hash128_read(p, downscale, mask)),
#[cfg(feature = "http")]
ImageSource::Download(u, a) => Some(Self::hash128_download(u, a, downscale, mask)),
ImageSource::Static(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
ImageSource::Data(h, _, _) => Some(Self::hash128_data(*h, downscale, mask)),
ImageSource::Render(rfn, args) => Some(Self::hash128_render(rfn, args, downscale, mask)),
ImageSource::Image(_) => None,
}
}
pub fn hash128_data(data_hash: ImageHash, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
if downscale.is_some() || mask.is_some() {
use std::hash::Hash;
let mut h = ImageHash::hasher();
data_hash.0.hash(&mut h);
downscale.hash(&mut h);
mask.hash(&mut h);
h.finish()
} else {
data_hash
}
}
pub fn hash128_read(path: &Path, downscale: Option<ImageDownscale>, mask: Option<ImageMaskMode>) -> ImageHash {
use std::hash::Hash;
let mut h = ImageHash::hasher();
0u8.hash(&mut h);
path.hash(&mut h);
downscale.hash(&mut h);
mask.hash(&mut h);
h.finish()
}
#[cfg(feature = "http")]
pub fn hash128_download(
uri: &crate::task::http::Uri,
accept: &Option<Txt>,
downscale: Option<ImageDownscale>,
mask: Option<ImageMaskMode>,
) -> ImageHash {
use std::hash::Hash;
let mut h = ImageHash::hasher();
1u8.hash(&mut h);
uri.hash(&mut h);
accept.hash(&mut h);
downscale.hash(&mut h);
mask.hash(&mut h);
h.finish()
}
pub fn hash128_render(
rfn: &RenderFn,
args: &Option<ImageRenderArgs>,
downscale: Option<ImageDownscale>,
mask: Option<ImageMaskMode>,
) -> ImageHash {
use std::hash::Hash;
let mut h = ImageHash::hasher();
2u8.hash(&mut h);
(Arc::as_ptr(rfn) as usize).hash(&mut h);
args.hash(&mut h);
downscale.hash(&mut h);
mask.hash(&mut h);
h.finish()
}
}
impl PartialEq for ImageSource {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Read(l), Self::Read(r)) => l == r,
#[cfg(feature = "http")]
(Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
(Self::Render(lf, la), Self::Render(rf, ra)) => Arc::ptr_eq(lf, rf) && la == ra,
(Self::Image(l), Self::Image(r)) => l.var_ptr() == r.var_ptr(),
(l, r) => {
let l_hash = match l {
ImageSource::Static(h, _, _) => h,
ImageSource::Data(h, _, _) => h,
_ => return false,
};
let r_hash = match r {
ImageSource::Static(h, _, _) => h,
ImageSource::Data(h, _, _) => h,
_ => return false,
};
l_hash == r_hash
}
}
}
}
impl fmt::Debug for ImageSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "ImageSource::")?;
}
match self {
ImageSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
#[cfg(feature = "http")]
ImageSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
ImageSource::Static(key, _, fmt) => f.debug_tuple("Static").field(key).field(fmt).finish(),
ImageSource::Data(key, _, fmt) => f.debug_tuple("Data").field(key).field(fmt).finish(),
ImageSource::Render(_, _) => write!(f, "Render(_, _)"),
ImageSource::Image(_) => write!(f, "Image(_)"),
}
}
}
#[cfg(feature = "http")]
impl_from_and_into_var! {
fn from(uri: crate::task::http::Uri) -> ImageSource {
ImageSource::Download(uri, None)
}
fn from((uri, accept): (crate::task::http::Uri, &'static str)) -> ImageSource {
ImageSource::Download(uri, Some(accept.into()))
}
fn from(s: &str) -> ImageSource {
use crate::task::http::uri::*;
if let Ok(uri) = Uri::try_from(s) {
if let Some(scheme) = uri.scheme() {
if scheme == &Scheme::HTTPS || scheme == &Scheme::HTTP {
return ImageSource::Download(uri, None);
} else if scheme.as_str() == "file" {
return PathBuf::from(uri.path()).into();
}
}
}
PathBuf::from(s).into()
}
}
#[cfg(not(feature = "http"))]
impl_from_and_into_var! {
fn from(s: &str) -> ImageSource {
PathBuf::from(s).into()
}
}
impl_from_and_into_var! {
fn from(image: ImageVar) -> ImageSource {
ImageSource::Image(image)
}
fn from(path: PathBuf) -> ImageSource {
ImageSource::Read(path)
}
fn from(path: &Path) -> ImageSource {
path.to_owned().into()
}
fn from(s: String) -> ImageSource {
s.as_str().into()
}
fn from(s: Txt) -> ImageSource {
s.as_str().into()
}
fn from(data: &'static [u8]) -> ImageSource {
ImageSource::Static(ImageHash::compute(data), data, ImageDataFormat::Unknown)
}
fn from<const N: usize>(data: &'static [u8; N]) -> ImageSource {
(&data[..]).into()
}
fn from(data: Arc<Vec<u8>>) -> ImageSource {
ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
}
fn from(data: Vec<u8>) -> ImageSource {
Arc::new(data).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (&'static [u8], F)) -> ImageSource {
ImageSource::Static(ImageHash::compute(data), data, format.into())
}
fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&'static [u8; N], F)) -> ImageSource {
(&data[..], format).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
(Arc::new(data), format).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (Arc<Vec<u8>>, F)) -> ImageSource {
ImageSource::Data(ImageHash::compute(&data[..]), data, format.into())
}
}
#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ImageCacheMode {
Ignore,
Cache,
Retry,
Reload,
}
impl fmt::Debug for ImageCacheMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "CacheMode::")?;
}
match self {
Self::Ignore => write!(f, "Ignore"),
Self::Cache => write!(f, "Cache"),
Self::Retry => write!(f, "Retry"),
Self::Reload => write!(f, "Reload"),
}
}
}
#[derive(Clone)]
pub enum ImageSourceFilter<U> {
BlockAll,
AllowAll,
Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
}
impl<U> ImageSourceFilter<U> {
pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
Self::Custom(Arc::new(allow))
}
pub fn and(self, other: Self) -> Self
where
U: 'static,
{
use ImageSourceFilter::*;
match (self, other) {
(BlockAll, _) | (_, BlockAll) => BlockAll,
(AllowAll, _) | (_, AllowAll) => AllowAll,
(Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
}
}
pub fn or(self, other: Self) -> Self
where
U: 'static,
{
use ImageSourceFilter::*;
match (self, other) {
(AllowAll, _) | (_, AllowAll) => AllowAll,
(BlockAll, _) | (_, BlockAll) => BlockAll,
(Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
}
}
pub fn allows(&self, item: &U) -> bool {
match self {
ImageSourceFilter::BlockAll => false,
ImageSourceFilter::AllowAll => true,
ImageSourceFilter::Custom(f) => f(item),
}
}
}
impl<U> fmt::Debug for ImageSourceFilter<U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BlockAll => write!(f, "BlockAll"),
Self::AllowAll => write!(f, "AllowAll"),
Self::Custom(_) => write!(f, "Custom(_)"),
}
}
}
impl<U: 'static> ops::BitAnd for ImageSourceFilter<U> {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
self.and(rhs)
}
}
impl<U: 'static> ops::BitOr for ImageSourceFilter<U> {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
self.or(rhs)
}
}
impl<U: 'static> ops::BitAndAssign for ImageSourceFilter<U> {
fn bitand_assign(&mut self, rhs: Self) {
*self = mem::replace(self, Self::BlockAll).and(rhs);
}
}
impl<U: 'static> ops::BitOrAssign for ImageSourceFilter<U> {
fn bitor_assign(&mut self, rhs: Self) {
*self = mem::replace(self, Self::BlockAll).or(rhs);
}
}
impl<U> PartialEq for ImageSourceFilter<U> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
pub type PathFilter = ImageSourceFilter<PathBuf>;
impl PathFilter {
pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
let dir = crate::absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
PathFilter::custom(move |r| r.starts_with(&dir))
}
pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
let ext = ext.into();
PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
}
pub fn allow_current_dir() -> Self {
PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
}
pub fn allow_exe_dir() -> Self {
if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize) {
if p.pop() {
return Self::allow_dir(p);
}
}
Self::custom(|_| false)
}
}
#[cfg(feature = "http")]
pub type UriFilter = ImageSourceFilter<crate::task::http::Uri>;
#[cfg(feature = "http")]
impl UriFilter {
pub fn allow_host(host: impl Into<Txt>) -> Self {
let host = host.into();
UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
}
}
impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
fn from(custom: F) -> Self {
PathFilter::custom(custom)
}
}
#[cfg(feature = "http")]
impl<F: Fn(&task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
fn from(custom: F) -> Self {
UriFilter::custom(custom)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ImageLimits {
pub max_encoded_len: ByteLength,
pub max_decoded_len: ByteLength,
pub allow_path: PathFilter,
#[cfg(feature = "http")]
pub allow_uri: UriFilter,
}
impl ImageLimits {
pub fn none() -> Self {
ImageLimits {
max_encoded_len: ByteLength::MAX,
max_decoded_len: ByteLength::MAX,
allow_path: PathFilter::AllowAll,
#[cfg(feature = "http")]
allow_uri: UriFilter::AllowAll,
}
}
pub fn with_max_encoded_len(mut self, max_encoded_size: impl Into<ByteLength>) -> Self {
self.max_encoded_len = max_encoded_size.into();
self
}
pub fn with_max_decoded_len(mut self, max_decoded_size: impl Into<ByteLength>) -> Self {
self.max_decoded_len = max_decoded_size.into();
self
}
pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
self.allow_path = allow_path.into();
self
}
#[cfg(feature = "http")]
pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
self.allow_uri = allow_url.into();
self
}
}
impl Default for ImageLimits {
fn default() -> Self {
Self {
max_encoded_len: 100.megabytes(),
max_decoded_len: 4096.megabytes(),
allow_path: PathFilter::allow_exe_dir(),
#[cfg(feature = "http")]
allow_uri: UriFilter::BlockAll,
}
}
}
impl_from_and_into_var! {
fn from(some: ImageLimits) -> Option<ImageLimits>;
}