Skip to content

Commit 0ba273d

Browse files
Testing native export with raster convert implementation
1 parent ffc7427 commit 0ba273d

File tree

9 files changed

+155
-31
lines changed

9 files changed

+155
-31
lines changed

desktop/wrapper/src/intercept_frontend_message.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,61 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
6161
context: SaveFileDialogContext::File { content },
6262
});
6363
}
64+
FrontendMessage::TriggerExportImageBuffer {
65+
data,
66+
width,
67+
height,
68+
transparent,
69+
file_type,
70+
name,
71+
} => {
72+
let mut encoded = Vec::new();
73+
74+
use graphite_editor::messages::frontend::utility_types::FileType;
75+
use image::ColorType;
76+
use image::ImageEncoder;
77+
match file_type {
78+
FileType::Png => {
79+
let result = image::codecs::png::PngEncoder::new(std::io::Cursor::new(&mut encoded)).write_image(&data, width, height, ColorType::Rgba8.into());
80+
if let Err(err) = result {
81+
tracing::error!("Failed to encode PNG: {err}");
82+
return None;
83+
}
84+
}
85+
FileType::Jpg => {
86+
let result = image::codecs::jpeg::JpegEncoder::new(std::io::Cursor::new(&mut encoded)).write_image(&data, width, height, ColorType::Rgba8.into());
87+
if let Err(err) = result {
88+
tracing::error!("Failed to encode JPG: {err}");
89+
return None;
90+
}
91+
}
92+
FileType::Svg => {
93+
tracing::error!("SVG cannot be exported from an image buffer");
94+
return None;
95+
}
96+
}
97+
let file_extension = match file_type {
98+
FileType::Png => "png",
99+
FileType::Jpg => "jpg",
100+
FileType::Svg => unreachable!(),
101+
};
102+
103+
let default_filename = if name.ends_with(&format!(".{file_extension}")) {
104+
name
105+
} else {
106+
format!("{name}.{file_extension}")
107+
};
108+
109+
println!("Successfully encoded image, size: {} bytes", encoded.len());
110+
111+
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
112+
title: "Export".to_string(),
113+
default_filename,
114+
default_folder: None,
115+
filters: Vec::new(),
116+
context: SaveFileDialogContext::File { content: encoded },
117+
});
118+
}
64119
FrontendMessage::TriggerVisitLink { url } => {
65120
dispatcher.respond(DesktopFrontendMessage::OpenUrl(url));
66121
}

desktop/wrapper/src/messages.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,8 @@ pub enum Platform {
135135
Mac,
136136
Linux,
137137
}
138+
139+
pub enum ExportType {
140+
Png,
141+
Jpg,
142+
}

editor/src/messages/frontend/frontend_message.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ pub enum FrontendMessage {
8282
mime: String,
8383
size: (f64, f64),
8484
},
85+
#[cfg(feature = "gpu")]
86+
#[serde(skip)]
87+
TriggerExportImageBuffer {
88+
data: Vec<u8>,
89+
width: u32,
90+
height: u32,
91+
transparent: bool,
92+
file_type: super::utility_types::FileType,
93+
name: String,
94+
},
8595
TriggerFetchAndOpenDocument {
8696
name: String,
8797
filename: String,

editor/src/node_graph_executor.rs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,20 @@ impl NodeGraphExecutor {
190190
let size = bounds[1] - bounds[0];
191191
let transform = DAffine2::from_translation(bounds[0]).inverse();
192192

193+
let export_format = if export_config.file_type == FileType::Svg || cfg!(not(feature = "gpu")) {
194+
graphene_std::application_io::ExportFormat::Svg
195+
} else {
196+
graphene_std::application_io::ExportFormat::Buffer
197+
};
198+
193199
let render_config = RenderConfig {
194200
viewport: Footprint {
195201
transform: DAffine2::from_scale(DVec2::splat(export_config.scale_factor)) * transform,
196202
resolution: (size * export_config.scale_factor).as_uvec2(),
197203
..Default::default()
198204
},
199205
time: Default::default(),
200-
export_format: graphene_std::application_io::ExportFormat::Svg,
206+
export_format,
201207
render_mode: document.render_mode,
202208
hide_artboards: export_config.transparent_background,
203209
for_export: true,
@@ -219,28 +225,49 @@ impl NodeGraphExecutor {
219225
}
220226

221227
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
222-
let TaggedValue::RenderOutput(RenderOutput {
223-
data: RenderOutputType::Svg { svg, .. },
224-
..
225-
}) = node_graph_output
226-
else {
227-
return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string());
228-
};
229-
230228
let ExportConfig {
231-
file_type, name, size, scale_factor, ..
229+
file_type,
230+
name,
231+
size,
232+
scale_factor,
233+
transparent_background,
234+
..
232235
} = export_config;
233236

234237
let file_suffix = &format!(".{file_type:?}").to_lowercase();
235238
let name = name + file_suffix;
236239

237-
if file_type == FileType::Svg {
238-
responses.add(FrontendMessage::TriggerSaveFile { name, content: svg.into_bytes() });
239-
} else {
240-
let mime = file_type.to_mime().to_string();
241-
let size = (size * scale_factor).into();
242-
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
243-
}
240+
match node_graph_output {
241+
TaggedValue::RenderOutput(RenderOutput {
242+
data: RenderOutputType::Svg { svg, .. },
243+
..
244+
}) => {
245+
if file_type == FileType::Svg {
246+
responses.add(FrontendMessage::TriggerSaveFile { name, content: svg.into_bytes() });
247+
} else {
248+
let mime = file_type.to_mime().to_string();
249+
let size = (size * scale_factor).into();
250+
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
251+
}
252+
}
253+
#[cfg(feature = "gpu")]
254+
TaggedValue::RenderOutput(RenderOutput {
255+
data: RenderOutputType::Buffer { data, width, height },
256+
..
257+
}) if file_type != FileType::Svg => {
258+
responses.add(FrontendMessage::TriggerExportImageBuffer {
259+
data,
260+
width,
261+
height,
262+
transparent: transparent_background,
263+
name,
264+
file_type,
265+
});
266+
}
267+
_ => {
268+
return Err(format!("Incorrect render type for exporting to an SVG ({file_type:?}, {node_graph_output})"));
269+
}
270+
};
244271

245272
Ok(())
246273
}

node-graph/gapplication-io/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,8 @@ pub trait GetEditorPreferences {
222222
pub enum ExportFormat {
223223
#[default]
224224
Svg,
225-
Png {
226-
transparent: bool,
227-
},
228-
Jpeg,
229225
Canvas,
226+
Buffer,
230227
Texture,
231228
}
232229

node-graph/graph-craft/src/document/value.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,16 @@ pub enum RenderOutputType {
441441
CanvasFrame(SurfaceFrame),
442442
#[serde(skip)]
443443
Texture(ImageTexture),
444+
#[serde(skip)]
445+
Buffer {
446+
data: Vec<u8>,
447+
width: u32,
448+
height: u32,
449+
},
444450
Svg {
445451
svg: String,
446452
image_data: Vec<(u64, Image<Color>)>,
447453
},
448-
Image(Vec<u8>),
449454
}
450455

451456
impl Hash for RenderOutput {

node-graph/gstd/src/render_node.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub use graph_craft::document::value::RenderOutputType;
33
pub use graph_craft::wasm_application_io::*;
44
use graphene_application_io::{ApplicationIo, ExportFormat, ImageTexture, RenderConfig, SurfaceFrame};
55
use graphene_core::gradient::GradientStops;
6+
use graphene_core::ops::Convert;
67
use graphene_core::raster::image::Image;
78
use graphene_core::raster_types::{CPU, Raster};
89
use graphene_core::table::Table;
@@ -44,7 +45,6 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
4445
data: impl Node<Context<'static>, Output = T>,
4546
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
4647
) -> RenderIntermediate {
47-
let mut render = SvgRender::new();
4848
let render_params = ctx
4949
.vararg(0)
5050
.expect("Did not find var args")
@@ -61,7 +61,9 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
6161

6262
let editor_api = editor_api.eval(None).await;
6363

64-
if !render_params.for_export && editor_api.editor_preferences.use_vello() && matches!(render_params.render_output_type, graphene_svg_renderer::RenderOutputType::Vello) {
64+
if (!render_params.for_export && editor_api.editor_preferences.use_vello() && matches!(render_params.render_output_type, graphene_svg_renderer::RenderOutputType::Texture))
65+
|| matches!(render_params.render_output_type, RenderOutputTypeRequest::Buffer)
66+
{
6567
let mut scene = vello::Scene::new();
6668

6769
let mut context = wgpu_executor::RenderContext::default();
@@ -73,6 +75,8 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send +
7375
contains_artboard,
7476
}
7577
} else {
78+
let mut render = SvgRender::new();
79+
7680
data.render_svg(&mut render, render_params);
7781

7882
RenderIntermediate {
@@ -93,11 +97,11 @@ async fn create_context<'a: 'n>(
9397

9498
let render_output_type = match render_config.export_format {
9599
ExportFormat::Svg => RenderOutputTypeRequest::Svg,
96-
ExportFormat::Png { .. } => todo!(),
97-
ExportFormat::Jpeg => todo!(),
98-
ExportFormat::Canvas => RenderOutputTypeRequest::Vello,
99-
ExportFormat::Texture => RenderOutputTypeRequest::Vello,
100+
ExportFormat::Buffer => RenderOutputTypeRequest::Buffer,
101+
ExportFormat::Texture => RenderOutputTypeRequest::Texture,
102+
ExportFormat::Canvas => RenderOutputTypeRequest::Texture,
100103
};
104+
101105
let render_params = RenderParams {
102106
render_mode: render_config.render_mode,
103107
hide_artboards: render_config.hide_artboards,
@@ -106,6 +110,7 @@ async fn create_context<'a: 'n>(
106110
footprint: Footprint::default(),
107111
..Default::default()
108112
};
113+
109114
let ctx = OwnedContextImpl::default()
110115
.with_footprint(footprint)
111116
.with_real_time(render_config.time.time)
@@ -173,7 +178,7 @@ async fn render<'a: 'n>(
173178
image_data: svg_renderer.image_data,
174179
}
175180
}
176-
(RenderOutputTypeRequest::Vello, RenderIntermediateType::Vello(vello_data)) => {
181+
(render_output_request_type, RenderIntermediateType::Vello(vello_data)) => {
177182
let Some(exec) = editor_api.application_io.as_ref().unwrap().gpu_executor() else {
178183
unreachable!("Attempted to render with Vello when no GPU executor is available");
179184
};
@@ -198,6 +203,23 @@ async fn render<'a: 'n>(
198203
if !contains_artboard && !render_params.hide_artboards {
199204
background = Color::WHITE;
200205
}
206+
207+
if matches!(render_output_request_type, RenderOutputTypeRequest::Buffer) {
208+
let texture = exec
209+
.render_vello_scene_to_texture(&scene, footprint.resolution, context, background)
210+
.await
211+
.expect("Failed to render Vello scene");
212+
213+
let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, exec).await;
214+
215+
let (data, width, height) = raster_cpu.to_flat_u8();
216+
217+
return RenderOutput {
218+
data: RenderOutputType::Buffer { data, width, height },
219+
metadata,
220+
};
221+
}
222+
201223
if let Some(surface_handle) = surface_handle {
202224
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution, context, background)
203225
.await

node-graph/gsvg-renderer/src/renderer.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ pub struct RenderContext {
160160
pub enum RenderOutputType {
161161
#[default]
162162
Svg,
163-
Vello,
163+
Texture,
164+
Buffer,
164165
}
165166

166167
/// Static state used whilst rendering

node-graph/wgpu-executor/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl WgpuExecutor {
124124
mip_level_count: 1,
125125
sample_count: 1,
126126
dimension: wgpu::TextureDimension::D2,
127-
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
127+
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
128128
format: VELLO_SURFACE_FORMAT,
129129
view_formats: &[],
130130
});
@@ -210,6 +210,8 @@ impl WgpuExecutor {
210210
}
211211
}
212212

213+
pub use wgpu::Texture as WgpuTexture;
214+
213215
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
214216

215217
#[node_macro::node(skip_impl)]

0 commit comments

Comments
 (0)