use std::fmt;
use serde::{Deserialize, Serialize};
use zng_txt::Txt;
use crate::ipc::IpcBytes;
use zng_unit::{Px, PxSize};
crate::declare_id! {
pub struct ImageId(_);
pub struct ImageTextureId(_);
}
#[derive(Debug, Copy, Clone, Serialize, PartialEq, Eq, Hash, Deserialize, Default)]
pub enum ImageMaskMode {
#[default]
A,
B,
G,
R,
Luminance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageRequest<D> {
pub format: ImageDataFormat,
pub data: D,
pub max_decoded_len: u64,
pub downscale: Option<ImageDownscale>,
pub mask: Option<ImageMaskMode>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum ImageDownscale {
Fit(PxSize),
Fill(PxSize),
}
impl From<PxSize> for ImageDownscale {
fn from(fit: PxSize) -> Self {
ImageDownscale::Fit(fit)
}
}
impl From<Px> for ImageDownscale {
fn from(fit: Px) -> Self {
ImageDownscale::Fit(PxSize::splat(fit))
}
}
#[cfg(feature = "var")]
zng_var::impl_from_and_into_var! {
fn from(fit: PxSize) -> ImageDownscale;
fn from(fit: Px) -> ImageDownscale;
fn from(some: ImageDownscale) -> Option<ImageDownscale>;
}
impl ImageDownscale {
pub fn resize_dimensions(self, source_size: PxSize) -> PxSize {
fn resize_dimensions(width: u32, height: u32, n_width: u32, n_height: u32, fill: bool) -> (u32, u32) {
use std::cmp::max;
let w_ratio = n_width as f64 / width as f64;
let h_ratio = n_height as f64 / height as f64;
let ratio = if fill {
f64::max(w_ratio, h_ratio)
} else {
f64::min(w_ratio, h_ratio)
};
let nw = max((width as f64 * ratio).round() as u64, 1);
let nh = max((height as f64 * ratio).round() as u64, 1);
if nw > u64::from(u32::MAX) {
let ratio = u32::MAX as f64 / width as f64;
(u32::MAX, max((height as f64 * ratio).round() as u32, 1))
} else if nh > u64::from(u32::MAX) {
let ratio = u32::MAX as f64 / height as f64;
(max((width as f64 * ratio).round() as u32, 1), u32::MAX)
} else {
(nw as u32, nh as u32)
}
}
let (x, y) = match self {
ImageDownscale::Fit(s) => resize_dimensions(
source_size.width.0.max(0) as _,
source_size.height.0.max(0) as _,
s.width.0.max(0) as _,
s.height.0.max(0) as _,
false,
),
ImageDownscale::Fill(s) => resize_dimensions(
source_size.width.0.max(0) as _,
source_size.height.0.max(0) as _,
s.width.0.max(0) as _,
s.height.0.max(0) as _,
true,
),
};
PxSize::new(Px(x as _), Px(y as _))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ImageDataFormat {
Bgra8 {
size: PxSize,
ppi: Option<ImagePpi>,
},
A8 {
size: PxSize,
},
FileExtension(Txt),
MimeType(Txt),
Unknown,
}
impl From<Txt> for ImageDataFormat {
fn from(ext_or_mime: Txt) -> Self {
if ext_or_mime.contains('/') {
ImageDataFormat::MimeType(ext_or_mime)
} else {
ImageDataFormat::FileExtension(ext_or_mime)
}
}
}
impl From<&str> for ImageDataFormat {
fn from(ext_or_mime: &str) -> Self {
Txt::from_str(ext_or_mime).into()
}
}
impl From<PxSize> for ImageDataFormat {
fn from(bgra8_size: PxSize) -> Self {
ImageDataFormat::Bgra8 {
size: bgra8_size,
ppi: None,
}
}
}
impl PartialEq for ImageDataFormat {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::FileExtension(l0), Self::FileExtension(r0)) => l0 == r0,
(Self::MimeType(l0), Self::MimeType(r0)) => l0 == r0,
(Self::Bgra8 { size: s0, ppi: p0 }, Self::Bgra8 { size: s1, ppi: p1 }) => s0 == s1 && ppi_key(*p0) == ppi_key(*p1),
(Self::Unknown, Self::Unknown) => true,
_ => false,
}
}
}
impl Eq for ImageDataFormat {}
impl std::hash::Hash for ImageDataFormat {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
ImageDataFormat::Bgra8 { size, ppi } => {
size.hash(state);
ppi_key(*ppi).hash(state);
}
ImageDataFormat::A8 { size } => {
size.hash(state);
}
ImageDataFormat::FileExtension(ext) => ext.hash(state),
ImageDataFormat::MimeType(mt) => mt.hash(state),
ImageDataFormat::Unknown => {}
}
}
}
fn ppi_key(ppi: Option<ImagePpi>) -> Option<(u16, u16)> {
ppi.map(|s| ((s.x * 3.0) as u16, (s.y * 3.0) as u16))
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct ImageLoadedData {
pub id: ImageId,
pub size: PxSize,
pub ppi: Option<ImagePpi>,
pub is_opaque: bool,
pub is_mask: bool,
pub pixels: IpcBytes,
}
impl fmt::Debug for ImageLoadedData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ImageLoadedData")
.field("id", &self.id)
.field("size", &self.size)
.field("ppi", &self.ppi)
.field("is_opaque", &self.is_opaque)
.field("is_mask", &self.is_mask)
.field("pixels", &format_args!("<{} bytes shared memory>", self.pixels.len()))
.finish()
}
}
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ImagePpi {
pub x: f32,
pub y: f32,
}
impl ImagePpi {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub const fn splat(xy: f32) -> Self {
Self::new(xy, xy)
}
}
impl Default for ImagePpi {
fn default() -> Self {
Self::splat(96.0)
}
}
impl fmt::Debug for ImagePpi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() || self.x != self.y {
f.debug_struct("ImagePpi").field("x", &self.x).field("y", &self.y).finish()
} else {
write!(f, "{}", self.x)
}
}
}
impl From<f32> for ImagePpi {
fn from(xy: f32) -> Self {
ImagePpi::splat(xy)
}
}
impl From<(f32, f32)> for ImagePpi {
fn from((x, y): (f32, f32)) -> Self {
ImagePpi::new(x, y)
}
}
#[cfg(feature = "var")]
zng_var::impl_from_and_into_var! {
fn from(xy: f32) -> ImagePpi;
fn from(xy: (f32, f32)) -> ImagePpi;
}