use super::Color; use crate::AlphaBlending; use crate::color::float_to_srgb_u8; use crate::instances::{Instance, Instances}; use crate::raster_types::Raster; use core::hash::{Hash, Hasher}; use dyn_any::{DynAny, StaticType}; use glam::{DAffine2, DVec2}; use std::vec::Vec; mod base64_serde { //! Basic wrapper for [`serde`] to perform [`base64`] encoding use super::super::Pixel; use base64::Engine; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub fn as_base64(key: &[P], serializer: S) -> Result { let u8_data = bytemuck::cast_slice(key); let string = base64::engine::general_purpose::STANDARD.encode(u8_data); (key.len() as u64, string).serialize(serializer) } pub fn from_base64<'a, D: Deserializer<'a>, P: Pixel>(deserializer: D) -> Result, D::Error> { use serde::de::Error; <(u64, &[u8])>::deserialize(deserializer) .and_then(|(len, str)| { let mut output: Vec

= vec![P::zeroed(); len as usize]; base64::engine::general_purpose::STANDARD .decode_slice(str, bytemuck::cast_slice_mut(output.as_mut_slice())) .map_err(|err| Error::custom(err.to_string()))?; Ok(output) }) .map_err(serde::de::Error::custom) } } #[derive(Clone, PartialEq, Default, specta::Type, serde::Serialize, serde::Deserialize)] pub struct Image { pub width: u32, pub height: u32, #[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")] pub data: Vec

, /// Optional: Stores a base64 string representation of the image which can be used to speed up the conversion /// to an svg string. This is used as a cache in order to not have to encode the data on every graph evaluation. #[serde(skip)] pub base64_string: Option, // TODO: Add an `origin` field to store where in the local space the image is anchored. // TODO: Currently it is always anchored at the top left corner at (0, 0). The bottom right corner of the new origin field would correspond to (1, 1). } impl Debug for Image

{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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: dyn_any::StaticTypeSized + Pixel, P::Static: Pixel, { type Static = Image; } impl Bitmap for Image

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

{ self.data.get((x + y * self.width) as usize).copied() } #[inline(always)] fn width(&self) -> u32 { self.width } #[inline(always)] fn height(&self) -> u32 { self.height } } impl BitmapMut 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 fn new(width: u32, height: u32, color: P) -> Self { Self { width, height, data: vec![color; (width * height) as usize], base64_string: None, } } } 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, base64_string: None, } } pub fn to_png(&self) -> Vec { use ::image::ImageEncoder; let (data, width, height) = self.to_flat_u8(); let mut png = Vec::new(); let encoder = ::image::codecs::png::PngEncoder::new(&mut png); encoder.write_image(&data, width, height, ::image::ExtendedColorType::Rgba8).expect("failed to encode image as png"); png } } use super::*; impl Image

where P::ColorChannel: Linear,

::AlphaChannel: Linear, { /// Flattens each channel cast to a u8 pub fn to_flat_u8(&self) -> (Vec, u32, u32) { let Image { width, height, data, .. } = self; assert_eq!(data.len(), *width as usize * *height as usize); // Cache the last sRGB value we computed, speeds up fills. let mut last_r = 0.; let mut last_r_srgb = 0u8; let mut last_g = 0.; let mut last_g_srgb = 0u8; let mut last_b = 0.; let mut last_b_srgb = 0u8; let mut result = vec![0; data.len() * 4]; let mut i = 0; for color in data { let a = color.a().to_f32(); // Smaller alpha values than this would map to fully transparent // anyway, avoid expensive encoding. if a >= 0.5 / 255. { let undo_premultiply = 1. / a; let r = color.r().to_f32() * undo_premultiply; let g = color.g().to_f32() * undo_premultiply; let b = color.b().to_f32() * undo_premultiply; // Compute new sRGB value if necessary. if r != last_r { last_r = r; last_r_srgb = float_to_srgb_u8(r); } if g != last_g { last_g = g; last_g_srgb = float_to_srgb_u8(g); } if b != last_b { last_b = b; last_b_srgb = float_to_srgb_u8(b); } result[i] = last_r_srgb; result[i + 1] = last_g_srgb; result[i + 2] = last_b_srgb; result[i + 3] = (a * 255. + 0.5) as u8; } i += 4; } (result, *width, *height) } } impl IntoIterator for Image

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

; fn into_iter(self) -> Self::IntoIter { self.data.into_iter() } } // TODO: Eventually remove this migration document upgrade code pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { use serde::Deserialize; type ImageFrameTable

= Instances>; #[derive(Clone, Debug, Hash, PartialEq, DynAny)] enum RasterFrame { /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image ImageFrame(ImageFrameTable), } impl<'de> serde::Deserialize<'de> for RasterFrame { fn deserialize>(deserializer: D) -> Result { Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) } } impl serde::Serialize for RasterFrame { fn serialize(&self, serializer: S) -> Result { match self { RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer), } } } #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] pub enum GraphicElement { /// Equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g GraphicGroup(GraphicGroupTable), /// A vector shape, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path VectorData(VectorDataTable), RasterFrame(RasterFrame), } #[derive(Clone, Default, Debug, PartialEq, specta::Type, serde::Serialize, serde::Deserialize)] pub struct ImageFrame { pub image: Image

, } impl From> for GraphicElement { fn from(image_frame: ImageFrame) -> Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image))) } } impl From for ImageFrame { fn from(element: GraphicElement) -> Self { match element { GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self { image: image.instance_ref_iter().next().unwrap().instance.clone(), }, _ => panic!("Expected Image, found {:?}", element), } } } unsafe impl

StaticType for ImageFrame

where P: dyn_any::StaticTypeSized + Pixel, P::Static: Pixel, { type Static = ImageFrame; } #[derive(Clone, Default, Debug, PartialEq, specta::Type, serde::Serialize, serde::Deserialize)] pub struct OldImageFrame { image: Image

, transform: DAffine2, alpha_blending: AlphaBlending, } #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum FormatVersions { Image(Image), OldImageFrame(OldImageFrame), ImageFrame(Instances>), ImageFrameTable(ImageFrameTable), RasterDataTable(RasterDataTable), } Ok(match FormatVersions::deserialize(deserializer)? { FormatVersions::Image(image) => RasterDataTable::new(Raster::new_cpu(image)), FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => { let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending; let mut image_frame_table = RasterDataTable::new(Raster::new_cpu(image)); *image_frame_table.instance_mut_iter().next().unwrap().transform = transform; *image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending; image_frame_table } FormatVersions::ImageFrame(image_frame) => RasterDataTable::new(Raster::new_cpu( image_frame .instance_ref_iter() .next() .unwrap_or(Instances::new(ImageFrame::default()).instance_ref_iter().next().unwrap()) .instance .image .clone(), )), FormatVersions::ImageFrameTable(image_frame_table) => RasterDataTable::new(Raster::new_cpu(image_frame_table.instance_ref_iter().next().unwrap().instance.clone())), FormatVersions::RasterDataTable(raster_data_table) => raster_data_table, }) } // TODO: Eventually remove this migration document upgrade code pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result>, D::Error> { use serde::Deserialize; type ImageFrameTable

= Instances>; #[derive(Clone, Debug, Hash, PartialEq, DynAny)] enum RasterFrame { /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image ImageFrame(ImageFrameTable), } impl<'de> serde::Deserialize<'de> for RasterFrame { fn deserialize>(deserializer: D) -> Result { Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) } } impl serde::Serialize for RasterFrame { fn serialize(&self, serializer: S) -> Result { match self { RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer), } } } #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] pub enum GraphicElement { /// Equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g GraphicGroup(GraphicGroupTable), /// A vector shape, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path VectorData(VectorDataTable), RasterFrame(RasterFrame), } #[derive(Clone, Default, Debug, PartialEq, specta::Type, serde::Serialize, serde::Deserialize)] pub struct ImageFrame { pub image: Image

, } impl From> for GraphicElement { fn from(image_frame: ImageFrame) -> Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image))) } } impl From for ImageFrame { fn from(element: GraphicElement) -> Self { match element { GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self { image: image.instance_ref_iter().next().unwrap().instance.clone(), }, _ => panic!("Expected Image, found {:?}", element), } } } unsafe impl

StaticType for ImageFrame

where P: dyn_any::StaticTypeSized + Pixel, P::Static: Pixel, { type Static = ImageFrame; } #[derive(Clone, Default, Debug, PartialEq, specta::Type, serde::Serialize, serde::Deserialize)] pub struct OldImageFrame { image: Image

, transform: DAffine2, alpha_blending: AlphaBlending, } #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum FormatVersions { Image(Image), OldImageFrame(OldImageFrame), ImageFrame(Instances>), RasterDataTable(RasterDataTable), ImageInstance(Instance>), } Ok(match FormatVersions::deserialize(deserializer)? { FormatVersions::Image(image) => Instance { instance: Raster::new_cpu(image), ..Default::default() }, FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance { instance: Raster::new_cpu(image_frame_with_transform_and_blending.image), transform: image_frame_with_transform_and_blending.transform, alpha_blending: image_frame_with_transform_and_blending.alpha_blending, source_node_id: None, }, FormatVersions::ImageFrame(image_frame) => Instance { instance: Raster::new_cpu(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()), ..Default::default() }, FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(), FormatVersions::ImageInstance(image_instance) => image_instance, }) } // pub type RasterDataTable

= Instances>; impl Sample for Image

{ type Pixel = P; // TODO: Improve sampling logic #[inline(always)] fn sample(&self, pos: DVec2, _area: DVec2) -> Option { let image_size = DVec2::new(self.width() as f64, self.height() as f64); if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y { return None; } self.get_pixel(pos.x as u32, pos.y as u32) } } impl Image

{ pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P { &mut self.data[y * (self.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.width as f64 - 1.) as usize; let y = position.y.clamp(0., self.height as f64 - 1.) as usize; self.data[x + y * self.width as usize] } } impl AsRef> for Image

{ fn as_ref(&self) -> &Image

{ self } } impl From> for Image { fn from(image: Image) -> Self { let data = image.data.into_iter().map(|x| x.into()).collect(); Self { data, width: image.width, height: image.height, base64_string: None, } } } // impl From> for RasterDataTable { // fn from(image_frame_table: RasterDataTable) -> Self { // let mut result_table = RasterDataTable::::default(); // for image_frame_instance in image_frame_table.instance_iter() { // result_table.push(Instance { // instance: image_frame_instance.instance, // transform: image_frame_instance.transform, // alpha_blending: image_frame_instance.alpha_blending, // source_node_id: image_frame_instance.source_node_id, // }); // } // result_table // } // } impl From> for Image { fn from(image: Image) -> Self { let data = image.data.into_iter().map(|x| x.into()).collect(); Self { data, width: image.width, height: image.height, base64_string: None, } } } #[cfg(test)] mod test { #[test] fn test_image_serialization_roundtrip() { use super::*; use crate::Color; let image = Image { width: 2, height: 2, data: vec![Color::WHITE, Color::BLACK, Color::RED, Color::GREEN], base64_string: None, }; let serialized = serde_json::to_string(&image).unwrap(); println!("{}", serialized); let deserialized: Image = serde_json::from_str(&serialized).unwrap(); println!("{:?}", deserialized); assert_eq!(image, deserialized); } }