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::{
Hsla, Rgba,
gradient::{ExtendMode, GradientStops},
};
use zng_layout::{
context::{LAYOUT, LayoutMetrics, LayoutPassId},
unit::{ByteLength, ByteUnits, FactorUnits as _, LayoutAxis, Px, PxDensity2d, PxLine, PxPoint, PxRect, PxSize},
};
use zng_task::{self as task, SignalOnce, channel::IpcBytes};
use zng_txt::Txt;
use zng_var::{Var, animation::Transitionable, impl_from_and_into_var};
use zng_view_api::image::ImageTextureId;
use crate::render::ImageRenderWindowRoot;
pub use zng_view_api::image::{ImageDataFormat, ImageDownscale, ImageMaskMode};
pub trait ImageCacheProxy: Send + Sync {
fn get(
&mut self,
key: &ImageHash,
source: &ImageSource,
mode: ImageCacheMode,
downscale: Option<ImageDownscale>,
mask: Option<ImageMaskMode>,
) -> ProxyGetResult {
let r = match source {
ImageSource::Data(_, data, image_format) => self.data(key, data, image_format, mode, downscale, mask, false),
_ => return ProxyGetResult::None,
};
match r {
Some(img) => ProxyGetResult::Image(img),
None => ProxyGetResult::None,
}
}
#[allow(clippy::too_many_arguments)]
fn data(
&mut self,
key: &ImageHash,
data: &[u8],
image_format: &ImageDataFormat,
mode: ImageCacheMode,
downscale: Option<ImageDownscale>,
mask: Option<ImageMaskMode>,
is_loaded: bool,
) -> Option<ImageVar> {
let _ = (key, data, image_format, mode, downscale, mask, is_loaded);
None
}
fn remove(&mut self, key: &ImageHash, purge: bool) -> ProxyRemoveResult {
let _ = (key, purge);
ProxyRemoveResult::None
}
fn clear(&mut self, purge: bool) {
let _ = purge;
}
fn is_data_proxy(&self) -> bool {
false
}
}
pub enum ProxyGetResult {
None,
Cache(ImageSource, ImageCacheMode, Option<ImageDownscale>, Option<ImageMaskMode>),
Image(ImageVar),
}
pub enum ProxyRemoveResult {
None,
Remove(ImageHash, bool),
Removed,
}
pub type ImageVar = Var<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(),
None => None,
}
}
pub fn wait_done(&self) -> impl 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 density(&self) -> Option<PxDensity2d> {
self.view.get().and_then(|v| v.density())
}
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, PxDensity2d::splat(ctx.screen_density()), false)
}
pub fn calc_size(&self, ctx: &LayoutMetrics, fallback_density: PxDensity2d, ignore_image_density: bool) -> PxSize {
let dpi = if ignore_image_density {
fallback_density
} else {
self.density().unwrap_or(fallback_density)
};
let s_density = ctx.screen_density();
let mut size = self.size();
let fct = ctx.scale_factor().0;
size.width *= (s_density.ppcm() / dpi.width.ppcm()) * fct;
size.height *= (s_density.ppcm() / dpi.height.ppcm()) * fct;
size
}
pub fn pixels(&self) -> Option<zng_task::channel::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_task::channel::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
}
pub(crate) fn inner_set_or_replace(&mut self, img: ViewImage, done: bool) {
match self.view.set(img) {
Ok(()) => {
if done {
self.done_signal.set();
}
}
Err(img) => {
let cache_key = self.cache_key;
*self = Self {
view: OnceCell::with_value(img),
render_ids: Arc::default(),
done_signal: if done { SignalOnce::new_set() } else { SignalOnce::new() },
cache_key,
};
}
}
}
}
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(_) => {
tracing::debug!("respawned `add_image`, will return INVALID");
return ImageTextureId::INVALID;
}
};
rms.push(RenderImage {
image_id: key,
renderer: renderer.clone(),
});
key
} else {
ImageTextureId::INVALID
}
}
fn size(&self) -> PxSize {
self.size()
}
}
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: &[u8]) {
use sha2::Digest;
const NUM_SAMPLES: usize = 1000;
const SAMPLE_CHUNK_SIZE: usize = 1024;
let total_size = data.len();
if total_size == 0 {
return;
}
if total_size < 1000 * 1000 * 4 {
return self.0.update(data);
}
let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
for n in 0..NUM_SAMPLES {
let start_index = n * step_size;
if start_index >= total_size {
break;
}
let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
let s = &data[start_index..end_index];
self.0.update(s);
}
}
pub fn finish(self) -> ImageHash {
use sha2::Digest;
#[allow(deprecated)]
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)]
#[non_exhaustive]
pub struct ImageRenderArgs {
pub parent: Option<WindowId>,
}
impl ImageRenderArgs {
pub fn new(parent: WindowId) -> Self {
Self { parent: Some(parent) }
}
}
#[derive(Clone)]
#[non_exhaustive]
pub enum ImageSource {
Read(PathBuf),
#[cfg(feature = "http")]
Download(crate::task::http::Uri, Option<Txt>),
Data(ImageHash, IpcBytes, ImageDataFormat),
Render(RenderFn, Option<ImageRenderArgs>),
Image(ImageVar),
}
impl ImageSource {
pub fn from_data(data: IpcBytes, format: ImageDataFormat) -> Self {
let mut hasher = ImageHasher::default();
hasher.update(&data[..]);
let hash = hasher.finish();
Self::Data(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::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 ImageSource {
pub fn flood(size: impl Into<PxSize>, color: impl Into<Rgba>, density: Option<PxDensity2d>) -> Self {
Self::flood_impl(size.into(), color.into(), density)
}
fn flood_impl(size: PxSize, color: Rgba, density: Option<PxDensity2d>) -> Self {
let pixels = size.width.0 as usize * size.height.0 as usize;
let bgra = color.to_bgra_bytes();
let mut b = IpcBytes::new_mut_blocking(pixels * 4).expect("cannot allocate IpcBytes");
for b in b.chunks_exact_mut(4) {
b.copy_from_slice(&bgra);
}
Self::from_data(
b.finish_blocking().expect("cannot allocate IpcBytes"),
ImageDataFormat::Bgra8 { size, density },
)
}
pub fn linear_vertical(
size: impl Into<PxSize>,
stops: impl Into<GradientStops>,
density: Option<PxDensity2d>,
mask: Option<ImageMaskMode>,
) -> Self {
Self::linear_vertical_impl(size.into(), stops.into(), density, mask)
}
fn linear_vertical_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
assert!(size.width > Px(0));
assert!(size.height > Px(0));
let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(Px(0), size.height));
let mut render_stops = vec![];
LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
});
let line_a = line.start.y.0 as f32;
let line_b = line.end.y.0 as f32;
let mut bgra = Vec::with_capacity(size.height.0 as usize);
let mut render_stops = render_stops.into_iter();
let mut stop_a = render_stops.next().unwrap();
let mut stop_b = render_stops.next().unwrap();
'outer: for y in 0..size.height.0 {
let yf = y as f32;
let yf = (yf - line_a) / (line_b - line_a);
if yf < stop_a.offset {
bgra.push(stop_a.color.to_bgra_bytes());
continue;
}
while yf > stop_b.offset {
if let Some(next_b) = render_stops.next() {
stop_a = stop_b;
stop_b = next_b;
} else {
for _ in y..size.height.0 {
bgra.push(stop_b.color.to_bgra_bytes());
}
break 'outer;
}
}
let yf = (yf - stop_a.offset) / (stop_b.offset - stop_a.offset);
let sample = stop_a.color.lerp(&stop_b.color, yf.fct());
bgra.push(sample.to_bgra_bytes());
}
match mask {
Some(m) => {
let len = size.width.0 as usize * size.height.0 as usize;
let mut data = Vec::with_capacity(len);
for y in 0..size.height.0 {
let c = bgra[y as usize];
let c = match m {
ImageMaskMode::A => c[3],
ImageMaskMode::B => c[0],
ImageMaskMode::G => c[1],
ImageMaskMode::R => c[2],
ImageMaskMode::Luminance => {
let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
(hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
}
_ => unreachable!(),
};
for _x in 0..size.width.0 {
data.push(c);
}
}
Self::from_data(
IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
ImageDataFormat::A8 { size },
)
}
None => {
let len = size.width.0 as usize * size.height.0 as usize * 4;
let mut data = Vec::with_capacity(len);
for y in 0..size.height.0 {
let c = bgra[y as usize];
for _x in 0..size.width.0 {
data.extend_from_slice(&c);
}
}
Self::from_data(
IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
ImageDataFormat::Bgra8 { size, density },
)
}
}
}
pub fn linear_horizontal(
size: impl Into<PxSize>,
stops: impl Into<GradientStops>,
density: Option<PxDensity2d>,
mask: Option<ImageMaskMode>,
) -> Self {
Self::linear_horizontal_impl(size.into(), stops.into(), density, mask)
}
fn linear_horizontal_impl(size: PxSize, stops: GradientStops, density: Option<PxDensity2d>, mask: Option<ImageMaskMode>) -> Self {
assert!(size.width > Px(0));
assert!(size.height > Px(0));
let mut line = PxLine::new(PxPoint::splat(Px(0)), PxPoint::new(size.width, Px(0)));
let mut render_stops = vec![];
LAYOUT.with_root_context(LayoutPassId::new(), LayoutMetrics::new(1.fct(), size, Px(14)), || {
stops.layout_linear(LayoutAxis::Y, ExtendMode::Clamp, &mut line, &mut render_stops);
});
let line_a = line.start.x.0 as f32;
let line_b = line.end.x.0 as f32;
let mut bgra = Vec::with_capacity(size.width.0 as usize);
let mut render_stops = render_stops.into_iter();
let mut stop_a = render_stops.next().unwrap();
let mut stop_b = render_stops.next().unwrap();
'outer: for x in 0..size.width.0 {
let xf = x as f32;
let xf = (xf - line_a) / (line_b - line_a);
if xf < stop_a.offset {
bgra.push(stop_a.color.to_bgra_bytes());
continue;
}
while xf > stop_b.offset {
if let Some(next_b) = render_stops.next() {
stop_a = stop_b;
stop_b = next_b;
} else {
for _ in x..size.width.0 {
bgra.push(stop_b.color.to_bgra_bytes());
}
break 'outer;
}
}
let xf = (xf - stop_a.offset) / (stop_b.offset - stop_a.offset);
let sample = stop_a.color.lerp(&stop_b.color, xf.fct());
bgra.push(sample.to_bgra_bytes());
}
match mask {
Some(m) => {
let len = size.width.0 as usize * size.height.0 as usize;
let mut data = Vec::with_capacity(len);
for _y in 0..size.height.0 {
for c in &bgra {
let c = match m {
ImageMaskMode::A => c[3],
ImageMaskMode::B => c[0],
ImageMaskMode::G => c[1],
ImageMaskMode::R => c[2],
ImageMaskMode::Luminance => {
let hsla = Hsla::from(Rgba::new(c[2], c[1], c[0], c[3]));
(hsla.lightness * 255.0).round().clamp(0.0, 255.0) as u8
}
_ => unreachable!(),
};
data.push(c);
}
}
Self::from_data(
IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
ImageDataFormat::A8 { size },
)
}
None => {
let len = size.width.0 as usize * size.height.0 as usize * 4;
let mut data = Vec::with_capacity(len);
for _y in 0..size.height.0 {
for c in &bgra {
data.extend_from_slice(c);
}
}
Self::from_data(
IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"),
ImageDataFormat::Bgra8 { size, density },
)
}
}
}
}
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_eq(r),
(l, r) => {
let l_hash = match l {
ImageSource::Data(h, _, _) => h,
_ => return false,
};
let r_hash = match r {
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::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).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::*;
if let Ok(uri) = Uri::try_from(s)
&& let Some(scheme) = uri.scheme()
{
if scheme == &uri::Scheme::HTTPS || scheme == &uri::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: &[u8]) -> ImageSource {
ImageSource::Data(
ImageHash::compute(data),
IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
ImageDataFormat::Unknown,
)
}
fn from<const N: usize>(data: &[u8; N]) -> ImageSource {
(&data[..]).into()
}
fn from(data: IpcBytes) -> ImageSource {
ImageSource::Data(ImageHash::compute(&data[..]), data, ImageDataFormat::Unknown)
}
fn from(data: Vec<u8>) -> ImageSource {
IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (&[u8], F)) -> ImageSource {
ImageSource::Data(
ImageHash::compute(data),
IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
format.into(),
)
}
fn from<F: Into<ImageDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> ImageSource {
(&data[..], format).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (Vec<u8>, F)) -> ImageSource {
(IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
}
fn from<F: Into<ImageDataFormat>>((data, format): (IpcBytes, 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)
&& p.pop()
{
return Self::allow_dir(p);
}
Self::custom(|_| false)
}
pub fn allow_res() -> Self {
Self::allow_dir(zng_env::res(""))
}
}
#[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)]
#[non_exhaustive]
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_res(),
#[cfg(feature = "http")]
allow_uri: UriFilter::BlockAll,
}
}
}
impl_from_and_into_var! {
fn from(some: ImageLimits) -> Option<ImageLimits>;
}