mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 03:18:06 +08:00
Serialize images as base64 by rounding channels from floats to u8 (#1120)
Serialise images as base64
This commit is contained in:
committed by
Keavon Chambers
parent
79dade24e5
commit
0e97f352e9
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user