use super::discrete_srgb::float_to_srgb_u8; use super::{Color, ImageSlice}; use crate::Node; use alloc::vec::Vec; use core::hash::{Hash, Hasher}; use dyn_any::StaticType; use glam::{DAffine2, DVec2}; #[cfg(feature = "serde")] mod base64_serde { //! Basic wrapper for [`serde`] to perform [`base64`] encoding use super::super::Pixel; use serde::{Deserialize, Deserializer, Serializer}; pub fn as_base64(key: &Vec

, serializer: S) -> Result where S: Serializer, { let u8_data = key.iter().flat_map(|color| color.to_bytes()).collect::>(); serializer.serialize_str(&base64::encode(u8_data)) } pub fn from_base64<'a, D, P: Pixel>(deserializer: D) -> Result, D::Error> where D: Deserializer<'a>, { use serde::de::Error; let color_from_chunk = |chunk: &[u8]| P::from_bytes(chunk.try_into().unwrap()); let colors_from_bytes = |bytes: Vec| bytes.chunks_exact(P::byte_size()).map(color_from_chunk).collect(); String::deserialize(deserializer) .and_then(|string| base64::decode(string).map_err(|err| Error::custom(err.to_string()))) .map(colors_from_bytes) .map_err(serde::de::Error::custom) } } #[derive(Clone, PartialEq, Default, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Image { pub width: u32, pub height: u32, #[cfg_attr(feature = "serde", serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64"))] pub data: Vec

, } impl Debug for Image

{ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let length = self.data.len(); f.debug_struct("Image") .field("width", &self.width) .field("height", &self.height) .field("data", if length < 100 { &self.data } else { &length }) .finish() } } unsafe impl StaticType for Image

where P::Static: Pixel, { type Static = Image; } impl Raster for Image

{ type Pixel = P; fn get_pixel(&self, x: u32, y: u32) -> Option

{ self.data.get((x + y * self.width) as usize).copied() } fn width(&self) -> u32 { self.width } fn height(&self) -> u32 { self.height } } impl RasterMut for Image

{ fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut P> { self.data.get_mut((x + y * self.width) as usize) } } // TODO: Evaluate if this will be a problem for our use case. /// Warning: This is an approximation of a hash, and is not guaranteed to not collide. impl Hash for Image

{ fn hash(&self, state: &mut H) { const HASH_SAMPLES: u64 = 1000; let data_length = self.data.len() as u64; self.width.hash(state); self.height.hash(state); for i in 0..HASH_SAMPLES.min(data_length) { self.data[(i * data_length / HASH_SAMPLES) as usize].hash(state); } } } impl Image

{ pub const fn empty() -> Self { Self { width: 0, height: 0, data: Vec::new(), } } pub fn new(width: u32, height: u32, color: P) -> Self { Self { width, height, data: vec![color; (width * height) as usize], } } pub fn as_slice(&self) -> ImageSlice

{ ImageSlice { width: self.width, height: self.height, data: self.data.as_slice(), } } } impl Image { /// Generate Image from some frontend image data (the canvas pixels as u8s in a flat array) pub fn from_image_data(image_data: &[u8], width: u32, height: u32) -> Self { let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8_srgb(v[0], v[1], v[2], v[3])).collect(); Image { width, height, data } } } use super::*; impl Image

where P::ColorChannel: Linear,

::AlphaChannel: Linear, { /// Flattens each channel cast to a u8 pub fn into_flat_u8(self) -> (Vec, u32, u32) { let Image { width, height, data } = self; assert!(data.len() == width as usize * height as usize); let mut result = Vec::with_capacity(data.len() * 4); for color in data { let a = color.a().to_f32(); if a < 0.5 / 255.0 { // This would map to fully transparent anyway, avoid expensive encoding. result.push(0); result.push(0); result.push(0); result.push(0); } else { let undo_premultiply = 1.0 / a; let r = float_to_srgb_u8(color.r().to_f32() * undo_premultiply); let g = float_to_srgb_u8(color.g().to_f32() * undo_premultiply); let b = float_to_srgb_u8(color.b().to_f32() * undo_premultiply); result.push(r); result.push(g); result.push(b); result.push((a * 255.0 + 0.5) as u8); } } (result, width, height) } } impl IntoIterator for Image

{ type Item = P; type IntoIter = alloc::vec::IntoIter

; fn into_iter(self) -> Self::IntoIter { self.data.into_iter() } } #[derive(Debug, Clone, Copy, Default)] pub struct ImageRefNode

{ _p: PhantomData

, } #[node_macro::node_fn(ImageRefNode<_P>)] fn image_ref_node<_P: Pixel>(image: &'input Image<_P>) -> ImageSlice<'input, _P> { image.as_slice() } #[derive(Debug, Clone)] pub struct CollectNode {} #[node_macro::node_fn(CollectNode)] fn collect_node<_Iter>(input: _Iter) -> Vec<_Iter::Item> where _Iter: Iterator, { input.collect() } #[derive(Debug)] pub struct MapImageSliceNode { data: Data, } #[node_macro::node_fn(MapImageSliceNode)] fn map_node(input: (u32, u32), data: Vec

) -> Image

{ Image { width: input.0, height: input.1, data, } } #[derive(Clone, Debug, PartialEq, Default, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ImageFrame { pub image: Image

, pub transform: DAffine2, } impl Sample for ImageFrame

{ type Pixel = P; // TODO: Improve sampling logic fn sample(&self, pos: DVec2, _area: DVec2) -> Option { let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64); let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos); if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y { return None; } self.image.get_pixel(pos.x as u32, pos.y as u32) } } impl Raster for ImageFrame

{ type Pixel = P; fn width(&self) -> u32 { self.image.width() } fn height(&self) -> u32 { self.image.height() } fn get_pixel(&self, x: u32, y: u32) -> Option { self.image.get_pixel(x, y) } } impl RasterMut for ImageFrame

{ fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { self.image.get_pixel_mut(x, y) } } unsafe impl StaticType for ImageFrame

where P::Static: Pixel, { type Static = ImageFrame; } impl ImageFrame

{ pub const fn empty() -> Self { Self { image: Image::empty(), transform: DAffine2::ZERO, } } pub const fn identity() -> Self { Self { image: Image::empty(), transform: DAffine2::IDENTITY, } } pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P { &mut self.image.data[y * (self.image.width as usize) + x] } /// Clamps the provided point to ((0, 0), (ImageSize.x, ImageSize.y)) and returns the closest pixel pub fn sample(&self, position: DVec2) -> P { let x = position.x.clamp(0., self.image.width as f64 - 1.) as usize; let y = position.y.clamp(0., self.image.height as f64 - 1.) as usize; self.image.data[x + y * self.image.width as usize] } } impl AsRef> for ImageFrame

{ fn as_ref(&self) -> &ImageFrame

{ self } } impl Hash for ImageFrame

{ fn hash(&self, state: &mut H) { self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)); 0.hash(state); self.image.hash(state); } } use crate::text::FontCache; #[derive(Clone, Debug, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EditorApi<'a> { #[cfg_attr(feature = "serde", serde(skip))] pub image_frame: Option>, #[cfg_attr(feature = "serde", serde(skip))] pub font_cache: Option<&'a FontCache>, } unsafe impl StaticType for EditorApi<'_> { type Static = EditorApi<'static>; } impl EditorApi<'_> { pub fn empty() -> Self { Self { image_frame: None, font_cache: None } } } impl<'a> AsRef> for EditorApi<'a> { fn as_ref(&self) -> &EditorApi<'a> { self } } pub struct ExtractImageFrame; impl<'a: 'input, 'input> Node<'input, EditorApi<'a>> for ExtractImageFrame { type Output = ImageFrame; fn eval(&'input self, mut editor_api: EditorApi<'a>) -> Self::Output { editor_api.image_frame.take().unwrap_or(ImageFrame::identity()) } } impl ExtractImageFrame { pub fn new() -> Self { Self } }