Serialize images as base64 by rounding channels from floats to u8 (#1120)

Serialise images as base64
This commit is contained in:
0HyperCube
2023-04-13 20:03:25 +01:00
committed by Keavon Chambers
parent 79dade24e5
commit 0e97f352e9
12 changed files with 117 additions and 100 deletions

View File

@@ -2,7 +2,7 @@ use crate::boolean_ops::composite_boolean_operation;
use crate::intersection::Quad;
use crate::layers::folder_layer::FolderLayer;
use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
use crate::layers::nodegraph_layer::NodeGraphFrameLayer;
use crate::layers::nodegraph_layer::{CachedOutputData, NodeGraphFrameLayer};
use crate::layers::shape_layer::ShapeLayer;
use crate::layers::style::RenderData;
use crate::layers::text_layer::{Font, TextLayer};
@@ -570,16 +570,6 @@ impl Document {
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
}
Operation::SetNodeGraphFrameImageData { layer_path, image_data } => {
let layer = self.layer_mut(&layer_path).expect("Setting NodeGraphFrame image data for invalid layer");
if let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data {
let image_data = std::sync::Arc::new(image_data);
node_graph_frame.image_data = Some(crate::layers::nodegraph_layer::ImageData { image_data });
} else {
panic!("Incorrectly trying to set image data for a layer that is not an NodeGraphFrame layer type");
}
Some(vec![LayerChanged { path: layer_path.clone() }])
}
Operation::SetLayerPreserveAspect { layer_path, preserve_aspect } => {
if let Ok(layer) = self.layer_mut(&layer_path) {
layer.preserve_aspect = preserve_aspect;
@@ -781,15 +771,14 @@ impl Document {
self.mark_as_dirty(&path)?;
Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat())
}
Operation::SetLayerBlobUrl { layer_path, blob_url, resolution } => {
Operation::SetLayerBlobUrl { layer_path, blob_url, resolution: _ } => {
let layer = self.layer_mut(&layer_path).unwrap_or_else(|_| panic!("Blob URL for invalid layer with path '{:?}'", layer_path));
let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data else {
panic!("Incorrectly trying to set the image blob URL for a layer that is not a NodeGraphFrame layer type");
};
node_graph_frame.blob_url = Some(blob_url);
node_graph_frame.dimensions = resolution.into();
node_graph_frame.cached_output_data = CachedOutputData::BlobURL(blob_url);
self.mark_as_dirty(&layer_path)?;
Some([vec![DocumentChanged, LayerChanged { path: layer_path.clone() }], update_thumbnails_upstream(&layer_path)].concat())
@@ -798,8 +787,7 @@ impl Document {
let layer = self.layer_mut(&path).expect("Clearing node graph image for invalid layer");
match &mut layer.data {
LayerDataType::NodeGraphFrame(node_graph) => {
node_graph.image_data = None;
node_graph.blob_url = None;
node_graph.cached_output_data = CachedOutputData::None;
}
e => panic!("Incorrectly trying to clear the blob URL for layer of type {}", LayerDataTypeDiscriminant::from(&*e)),
}
@@ -828,7 +816,7 @@ impl Document {
}
Operation::SetVectorData { path, vector_data } => {
if let LayerDataType::NodeGraphFrame(graph) = &mut self.layer_mut(&path)?.data {
graph.vector_data = Some(vector_data);
graph.cached_output_data = CachedOutputData::VectorPath(Box::new(vector_data));
}
Some(Vec::new())
}

View File

@@ -438,7 +438,7 @@ impl Layer {
pub fn as_vector_data(&self) -> Option<&VectorData> {
match &self.data {
LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(vector_data),
LayerDataType::NodeGraphFrame(frame) => frame.as_vector_data(),
_ => None,
}
}
@@ -506,7 +506,7 @@ impl Layer {
match &self.data {
LayerDataType::Shape(s) => Ok(&s.style),
LayerDataType::Text(t) => Ok(&t.path_style),
LayerDataType::NodeGraphFrame(t) => t.vector_data.as_ref().map(|vector| &vector.style).ok_or(DocumentError::NotShape),
LayerDataType::NodeGraphFrame(t) => t.as_vector_data().map(|vector| &vector.style).ok_or(DocumentError::NotShape),
_ => Err(DocumentError::NotShape),
}
}

View File

@@ -1,4 +1,3 @@
use super::base64_serde;
use super::layer_info::LayerData;
use super::style::{RenderData, ViewMode};
use crate::intersection::{intersect_quad_bez_path, intersect_quad_subpath, Quad};
@@ -11,27 +10,20 @@ use serde::{Deserialize, Serialize};
use std::fmt::Write;
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct NodeGraphFrameLayer {
// Image stored in layer after generation completes
pub mime: String,
pub enum CachedOutputData {
#[default]
None,
BlobURL(String),
VectorPath(Box<VectorData>),
}
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct NodeGraphFrameLayer {
/// The document node network that this layer contains
pub network: graph_craft::document::NodeNetwork,
// TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted)
#[serde(skip)]
pub blob_url: Option<String>,
#[serde(skip)]
pub dimensions: DVec2,
pub image_data: Option<ImageData>,
pub vector_data: Option<VectorData>,
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, specta::Type)]
pub struct ImageData {
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
#[specta(type = String)]
pub image_data: std::sync::Arc<Vec<u8>>,
pub cached_output_data: CachedOutputData,
}
impl LayerData for NodeGraphFrameLayer {
@@ -59,37 +51,41 @@ impl LayerData for NodeGraphFrameLayer {
.fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," }));
// Render any paths if they exist
if let Some(vector_data) = &self.vector_data {
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
let transfomed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default();
match &self.cached_output_data {
CachedOutputData::VectorPath(vector_data) => {
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
let transfomed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default();
let _ = write!(svg, "<path d=\"");
for subpath in &vector_data.subpaths {
let _ = subpath.subpath_to_svg(svg, transform);
let _ = write!(svg, "<path d=\"");
for subpath in &vector_data.subpaths {
let _ = subpath.subpath_to_svg(svg, transform);
}
svg.push('"');
svg.push_str(&vector_data.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transfomed_bounds));
let _ = write!(svg, "/>");
}
CachedOutputData::BlobURL(blob_url) => {
// Render the image if it exists
let _ = write!(
svg,
r#"<image width="{}" height="{}" preserveAspectRatio="none" href="{}" transform="matrix({})" />"#,
width.abs(),
height.abs(),
blob_url,
matrix
);
}
_ => {
// Render a dotted blue outline if there is no image or vector data
let _ = write!(
svg,
r#"<rect width="{}" height="{}" fill="none" stroke="var(--color-data-vector)" stroke-width="3" stroke-dasharray="8" transform="matrix({})" />"#,
width.abs(),
height.abs(),
matrix,
);
}
svg.push('"');
svg.push_str(&vector_data.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transfomed_bounds));
let _ = write!(svg, "/>");
} else if let Some(blob_url) = &self.blob_url {
// Render the image if it exists
let _ = write!(
svg,
r#"<image width="{}" height="{}" preserveAspectRatio="none" href="{}" transform="matrix({})" />"#,
width.abs(),
height.abs(),
blob_url,
matrix
);
} else {
// Render a dotted blue outline if there is no image or vector data
let _ = write!(
svg,
r#"<rect width="{}" height="{}" fill="none" stroke="var(--color-data-vector)" stroke-width="3" stroke-dasharray="8" transform="matrix({})" />"#,
width.abs(),
height.abs(),
matrix,
);
}
let _ = svg.write_str(r#"</g>"#);
@@ -98,7 +94,7 @@ impl LayerData for NodeGraphFrameLayer {
}
fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> {
if let Some(vector_data) = &self.vector_data {
if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data {
return vector_data.bounding_box_with_transform(transform);
}
@@ -114,7 +110,7 @@ impl LayerData for NodeGraphFrameLayer {
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _render_data: &RenderData) {
if let Some(vector_data) = &self.vector_data {
if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data {
let filled_style = vector_data.style.fill().is_some();
if vector_data.subpaths.iter().any(|subpath| intersect_quad_subpath(quad, subpath, filled_style || subpath.closed())) {
intersections.push(path.clone());
@@ -137,6 +133,21 @@ impl NodeGraphFrameLayer {
fn bounds(&self) -> BezPath {
kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)).to_path(0.)
}
pub fn as_vector_data(&self) -> Option<&VectorData> {
if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data {
Some(vector_data)
} else {
None
}
}
pub fn as_blob_url(&self) -> Option<&String> {
if let CachedOutputData::BlobURL(blob_url) = &self.cached_output_data {
Some(blob_url)
} else {
None
}
}
}
fn glam_to_kurbo(transform: DAffine2) -> Affine {

View File

@@ -51,10 +51,6 @@ pub enum Operation {
transform: [f64; 6],
network: graph_craft::document::NodeNetwork,
},
SetNodeGraphFrameImageData {
layer_path: Vec<LayerId>,
image_data: Vec<u8>,
},
/// Sets a blob URL as the image source for an Image or Imaginate layer type.
/// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.**
SetLayerBlobUrl {