Download node-graph/gcore/src/raster/image.rs from openfree/graphite2: direct link, hf CLI and curl.
- Browser
- Download file 16.2 kB
-
https://huggingface.co/spaces/openfree/graphite2/resolve/main/node-graph/gcore/src/raster/image.rs
- Command line
-
hf download hf://spaces/openfree/graphite2/node-graph/gcore/src/raster/image.rs
-
curl -L -o image.rs https://huggingface.co/spaces/openfree/graphite2/resolve/main/node-graph/gcore/src/raster/image.rs
16.2 kB
| 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<S: Serializer, P: Pixel>(key: &[P], serializer: S) -> Result<S::Ok, S::Error> { | |
| 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<Vec<P>, D::Error> { | |
| use serde::de::Error; | |
| <(u64, &[u8])>::deserialize(deserializer) | |
| .and_then(|(len, str)| { | |
| let mut output: Vec<P> = 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) | |
| } | |
| } | |
| pub struct Image<P: Pixel> { | |
| pub width: u32, | |
| pub height: u32, | |
| pub data: Vec<P>, | |
| /// 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. | |
| pub base64_string: Option<String>, | |
| // 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<P: Pixel + Debug> Debug for Image<P> { | |
| 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<P> StaticType for Image<P> | |
| where | |
| P: dyn_any::StaticTypeSized + Pixel, | |
| P::Static: Pixel, | |
| { | |
| type Static = Image<P::Static>; | |
| } | |
| impl<P: Copy + Pixel> Bitmap for Image<P> { | |
| type Pixel = P; | |
| fn get_pixel(&self, x: u32, y: u32) -> Option<P> { | |
| self.data.get((x + y * self.width) as usize).copied() | |
| } | |
| fn width(&self) -> u32 { | |
| self.width | |
| } | |
| fn height(&self) -> u32 { | |
| self.height | |
| } | |
| } | |
| impl<P: Copy + Pixel> BitmapMut for Image<P> { | |
| 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<P: Hash + Pixel> Hash for Image<P> { | |
| fn hash<H: Hasher>(&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<P: Pixel> Image<P> { | |
| 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<Color> { | |
| /// 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<u8> { | |
| 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<P: Alpha + RGB + AssociatedAlpha> Image<P> | |
| where | |
| P::ColorChannel: Linear, | |
| <P as Alpha>::AlphaChannel: Linear, | |
| { | |
| /// Flattens each channel cast to a u8 | |
| pub fn to_flat_u8(&self) -> (Vec<u8>, 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<P: Pixel> IntoIterator for Image<P> { | |
| type Item = P; | |
| type IntoIter = std::vec::IntoIter<P>; | |
| 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<RasterDataTable<CPU>, D::Error> { | |
| use serde::Deserialize; | |
| type ImageFrameTable<P> = Instances<Image<P>>; | |
| enum RasterFrame { | |
| /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image | |
| ImageFrame(ImageFrameTable<Color>), | |
| } | |
| impl<'de> serde::Deserialize<'de> for RasterFrame { | |
| fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { | |
| Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) | |
| } | |
| } | |
| impl serde::Serialize for RasterFrame { | |
| fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { | |
| match self { | |
| RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer), | |
| } | |
| } | |
| } | |
| pub enum GraphicElement { | |
| /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g | |
| GraphicGroup(GraphicGroupTable), | |
| /// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path | |
| VectorData(VectorDataTable), | |
| RasterFrame(RasterFrame), | |
| } | |
| pub struct ImageFrame<P: Pixel> { | |
| pub image: Image<P>, | |
| } | |
| impl From<ImageFrame<Color>> for GraphicElement { | |
| fn from(image_frame: ImageFrame<Color>) -> Self { | |
| GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image))) | |
| } | |
| } | |
| impl From<GraphicElement> for ImageFrame<Color> { | |
| 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<P> StaticType for ImageFrame<P> | |
| where | |
| P: dyn_any::StaticTypeSized + Pixel, | |
| P::Static: Pixel, | |
| { | |
| type Static = ImageFrame<P::Static>; | |
| } | |
| pub struct OldImageFrame<P: Pixel> { | |
| image: Image<P>, | |
| transform: DAffine2, | |
| alpha_blending: AlphaBlending, | |
| } | |
| enum FormatVersions { | |
| Image(Image<Color>), | |
| OldImageFrame(OldImageFrame<Color>), | |
| ImageFrame(Instances<ImageFrame<Color>>), | |
| ImageFrameTable(ImageFrameTable<Color>), | |
| RasterDataTable(RasterDataTable<CPU>), | |
| } | |
| 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<Instance<Raster<CPU>>, D::Error> { | |
| use serde::Deserialize; | |
| type ImageFrameTable<P> = Instances<Image<P>>; | |
| enum RasterFrame { | |
| /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG <image> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image | |
| ImageFrame(ImageFrameTable<Color>), | |
| } | |
| impl<'de> serde::Deserialize<'de> for RasterFrame { | |
| fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { | |
| Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) | |
| } | |
| } | |
| impl serde::Serialize for RasterFrame { | |
| fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { | |
| match self { | |
| RasterFrame::ImageFrame(image_instances) => image_instances.serialize(serializer), | |
| } | |
| } | |
| } | |
| pub enum GraphicElement { | |
| /// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g | |
| GraphicGroup(GraphicGroupTable), | |
| /// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path | |
| VectorData(VectorDataTable), | |
| RasterFrame(RasterFrame), | |
| } | |
| pub struct ImageFrame<P: Pixel> { | |
| pub image: Image<P>, | |
| } | |
| impl From<ImageFrame<Color>> for GraphicElement { | |
| fn from(image_frame: ImageFrame<Color>) -> Self { | |
| GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image))) | |
| } | |
| } | |
| impl From<GraphicElement> for ImageFrame<Color> { | |
| 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<P> StaticType for ImageFrame<P> | |
| where | |
| P: dyn_any::StaticTypeSized + Pixel, | |
| P::Static: Pixel, | |
| { | |
| type Static = ImageFrame<P::Static>; | |
| } | |
| pub struct OldImageFrame<P: Pixel> { | |
| image: Image<P>, | |
| transform: DAffine2, | |
| alpha_blending: AlphaBlending, | |
| } | |
| enum FormatVersions { | |
| Image(Image<Color>), | |
| OldImageFrame(OldImageFrame<Color>), | |
| ImageFrame(Instances<ImageFrame<Color>>), | |
| RasterDataTable(RasterDataTable<CPU>), | |
| ImageInstance(Instance<Raster<CPU>>), | |
| } | |
| 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<P> = Instances<Image<P>>; | |
| impl<P: Debug + Copy + Pixel> Sample for Image<P> { | |
| type Pixel = P; | |
| // TODO: Improve sampling logic | |
| fn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> { | |
| 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<P: Copy + Pixel> Image<P> { | |
| 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<P: Pixel> AsRef<Image<P>> for Image<P> { | |
| fn as_ref(&self) -> &Image<P> { | |
| self | |
| } | |
| } | |
| impl From<Image<Color>> for Image<SRGBA8> { | |
| fn from(image: Image<Color>) -> 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<RasterDataTable<CPU>> for RasterDataTable<SRGBA8> { | |
| // fn from(image_frame_table: RasterDataTable<CPU>) -> Self { | |
| // let mut result_table = RasterDataTable::<SRGBA8>::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<Image<SRGBA8>> for Image<Color> { | |
| fn from(image: Image<SRGBA8>) -> Self { | |
| let data = image.data.into_iter().map(|x| x.into()).collect(); | |
| Self { | |
| data, | |
| width: image.width, | |
| height: image.height, | |
| base64_string: None, | |
| } | |
| } | |
| } | |
| mod 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<Color> = serde_json::from_str(&serialized).unwrap(); | |
| println!("{:?}", deserialized); | |
| assert_eq!(image, deserialized); | |
| } | |
| } | |