New node: Rasterize (#1755)

* Add RasterizeVector node

* Add document node definition for RasterizeVectorNode

* Add dummy node properties

* Add prototype of footprint widget

* Fix types

* Fix footprint widget to use document space

* Fix aspect ratio issues by making resolution a multiplier

* Fix rebase errors

* Fix rasterization bounds and node definition

* UI and bug fixes but still issues with scaling in the frame

* Rename node to Raster

* Fix RasterizeNode

* Reenable resolution multiplier

* Remove unused variable

* UI fixes

* Fix Footprint default and resolution updates

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert 2024-06-27 10:24:09 +02:00 committed by GitHub
parent cf496668fb
commit c9a33e44bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 330 additions and 44 deletions

View file

@ -483,6 +483,70 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}],
..Default::default()
},
DocumentNodeDefinition {
name: "Rasterize",
category: "Raster",
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 2)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
name: "Rasterize".to_string(),
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode<_, _>")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![
DocumentInputType {
name: "Artwork",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::value(TaggedValue::VectorData(VectorData::default()), true),
},
DocumentInputType {
name: "Footprint",
data_type: FrontendGraphDataType::General,
default: NodeInput::value(
TaggedValue::Footprint(Footprint {
transform: DAffine2::from_scale_angle_translation(DVec2::new(100., 100.), 0., DVec2::new(0., 0.)),
resolution: UVec2::new(100, 100),
..Default::default()
}),
false,
),
},
DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::General,
default: NodeInput::network(concrete!(WasmEditorApi), 0),
},
],
properties: node_properties::rasterize_properties,
outputs: vec![DocumentOutputType {
name: "Canvas",
data_type: FrontendGraphDataType::General,
}],
..Default::default()
},
DocumentNodeDefinition {
// This essentially builds the concept of a closure where we store variables (`let` bindings) so they can be accessed within this scope.
name: "Begin Scope",
@ -720,7 +784,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
name: "Mask",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -733,7 +797,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
name: "Insert Channel",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode<_, _, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -747,7 +811,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
name: "Combine Channels",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"),
inputs: vec![
DocumentInputType::value("None", TaggedValue::None, false),
@ -765,7 +829,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
name: "Blend",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BlendNode<_, _, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -779,7 +843,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Levels",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::LevelsNode<_, _, _, _, _>"),
inputs: vec![
DocumentInputType {
@ -819,7 +883,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Black & White",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BlackAndWhiteNode<_, _, _, _, _, _, _>"),
inputs: vec![
DocumentInputType {
@ -869,7 +933,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Color Channel",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
inputs: vec![DocumentInputType::value("Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false)],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
@ -887,7 +951,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Luminance",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -899,7 +963,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Extract Channel",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ExtractChannelNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -911,7 +975,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Extract Opaque",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ExtractOpaqueNode<>"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
@ -919,7 +983,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Split Channels",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![
NodeInput::node(NodeId(0), 0),
@ -1584,7 +1648,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
name: "GpuImage",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1605,7 +1669,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
name: "Blend (GPU)",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::executor::BlendGpuImageNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1699,7 +1763,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Invert RGB",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::InvertRGBNode"),
inputs: vec![DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true)],
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
@ -1707,7 +1771,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Hue/Saturation",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::HueSaturationNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1721,7 +1785,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Brightness/Contrast",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1735,7 +1799,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Curves",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1747,7 +1811,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Threshold",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ThresholdNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1761,7 +1825,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Vibrance",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::VibranceNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1773,7 +1837,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Channel Mixer",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ChannelMixerNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1808,7 +1872,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Selective Color",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto(
"graphene_core::raster::SelectiveColorNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>",
),
@ -1870,7 +1934,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Opacity",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::OpacityNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1882,7 +1946,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Blend Mode",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BlendModeNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1894,7 +1958,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Posterize",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::PosterizeNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -1906,7 +1970,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Exposure",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ExposureNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -2645,7 +2709,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data.
DocumentNodeDefinition {
name: "Image Segmentation",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::image_segmentation::ImageSegmentationNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -2656,7 +2720,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Index",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::IndexNode<_>"),
inputs: vec![
DocumentInputType::value("Segmentation", TaggedValue::Segments(vec![ImageFrame::empty()]), true),
@ -2669,7 +2733,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Applies the given color to each pixel of an image but maintains the alpha value
DocumentNodeDefinition {
name: "Color Fill",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::adjustments::ColorFillNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -2681,7 +2745,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Color Overlay",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_core::raster::adjustments::ColorOverlayNode<_, _, _>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
@ -2695,7 +2759,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
name: "Image Color Palette",
category: "Image Adjustments",
category: "Raster",
implementation: DocumentNodeImplementation::proto("graphene_std::image_color_palette::ImageColorPaletteNode<_>"),
inputs: vec![
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),

View file

@ -18,7 +18,8 @@ use graphene_core::vector::misc::CentroidType;
use graphene_core::vector::style::{GradientType, LineCap, LineJoin};
use graphene_std::vector::style::{Fill, FillChoice};
use glam::{DVec2, IVec2, UVec2};
use glam::{DAffine2, DVec2, IVec2, UVec2};
use graphene_std::transform::Footprint;
use graphene_std::vector::misc::BooleanOperation;
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
@ -137,6 +138,148 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
widgets
}
fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) -> Vec<LayoutGroup> {
let mut location_widgets = start_widgets(document_node, node_id, index, "Footprint", FrontendGraphDataType::General, true);
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut scale_widgets);
scale_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut resolution_widgets = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut resolution_widgets);
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
if let NodeInput::Value {
tagged_value: TaggedValue::Footprint(footprint),
exposed: false,
} = &document_node.inputs[index]
{
let footprint = *footprint;
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
let bounds = footprint.scale();
let oversample = footprint.resolution.as_dvec2() / bounds;
location_widgets.extend_from_slice(&[
NumberInput::new(Some(top_left.x))
.label("X")
.unit(" px")
.on_update(update_value(
move |x: &NumberInput| {
let (offset, scale) = (move |x: f64| -> (DVec2, DVec2) {
let diff = DVec2::new(top_left.x - x, 0.);
(top_left - diff, bounds)
})(x.value.unwrap_or_default());
let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale.into(), 0., offset.into()),
resolution: (oversample * scale).as_uvec2(),
..footprint
};
TaggedValue::Footprint(footprint)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(top_left.y))
.label("Y")
.unit(" px")
.on_update(update_value(
move |x: &NumberInput| {
let (offset, scale) = (move |y: f64| -> (DVec2, DVec2) {
let diff = DVec2::new(0., top_left.y - y);
(top_left - diff, bounds)
})(x.value.unwrap_or_default());
let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale.into(), 0., offset.into()),
resolution: (oversample * scale).as_uvec2(),
..footprint
};
TaggedValue::Footprint(footprint)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
]);
scale_widgets.extend_from_slice(&[
NumberInput::new(Some(bounds.x))
.label("W")
.unit(" px")
.on_update(update_value(
move |x: &NumberInput| {
let (offset, scale) = (move |x: f64| -> (DVec2, DVec2) { (top_left, DVec2::new(x, bounds.y)) })(x.value.unwrap_or_default());
let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale.into(), 0., offset.into()),
resolution: (oversample * scale).as_uvec2(),
..footprint
};
TaggedValue::Footprint(footprint)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(bounds.y))
.label("H")
.unit(" px")
.on_update(update_value(
move |x: &NumberInput| {
let (offset, scale) = (move |y: f64| -> (DVec2, DVec2) { (top_left, DVec2::new(bounds.x, y)) })(x.value.unwrap_or_default());
let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale.into(), 0., offset.into()),
resolution: (oversample * scale).as_uvec2(),
..footprint
};
TaggedValue::Footprint(footprint)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
]);
resolution_widgets.push(
NumberInput::new(Some((footprint.resolution.as_dvec2() / bounds).x as f64 * 100.))
.label("Resolution")
.unit("%")
.on_update(update_value(
move |x: &NumberInput| {
let resolution = (bounds * x.value.unwrap_or(100.) / 100.).as_uvec2().max((1, 1).into()).min((4000, 4000).into());
let footprint = Footprint { resolution, ..footprint };
TaggedValue::Footprint(footprint)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
);
}
vec![
LayoutGroup::Row { widgets: location_widgets },
LayoutGroup::Row { widgets: scale_widgets },
LayoutGroup::Row { widgets: resolution_widgets },
]
}
fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, unit: &str, min: Option<f64>, mut assist: impl FnMut(&mut Vec<WidgetHolder>)) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, false);
@ -1640,6 +1783,9 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
vec![translation, rotation, scale]
}
pub fn rasterize_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
footprint_widget(document_node, node_id, 1)
}
pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let text = text_area_widget(document_node, node_id, 1, "Text", true);

View file

@ -155,11 +155,14 @@ impl Default for SvgRender {
}
}
#[derive(Default)]
pub enum ImageRenderMode {
#[default]
Base64,
}
/// Static state used whilst rendering
#[derive(Default)]
pub struct RenderParams {
pub view_mode: crate::vector::style::ViewMode,
pub image_render_mode: ImageRenderMode,

View file

@ -200,6 +200,16 @@ impl Footprint {
let end = inverse.transform_point2(self.resolution.as_dvec2());
AxisAlignedBbox { start, end }
}
pub fn scale(&self) -> DVec2 {
let x = self.transform.transform_vector2((1., 0.).into()).length();
let y = self.transform.transform_vector2((0., 1.).into()).length();
DVec2::new(x, y)
}
pub fn offset(&self) -> DVec2 {
self.transform.transform_point2(DVec2::ZERO)
}
}
#[derive(Debug, Clone, Copy)]

View file

@ -733,8 +733,9 @@ fn mandelbrot_node(footprint: Footprint) -> ImageFrame<Color> {
let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);
let width = footprint.transform.transform_vector2(DVec2::X * size.x).length() as u32;
let height = footprint.transform.transform_vector2(DVec2::Y * size.y).length() as u32;
let scale = footprint.scale();
let width = (size.x * scale.x) as u32;
let height = (size.y * scale.y) as u32;
let mut data = Vec::with_capacity(width as usize * height as usize);
let max_iter = 255;

View file

@ -1,14 +1,18 @@
use dyn_any::StaticType;
use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, RenderConfig, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::Image;
use graphene_core::raster::{color::SRGBA8, ImageFrame};
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint;
use graphene_core::transform::{Footprint, TransformMut};
use graphene_core::Color;
use graphene_core::Node;
#[cfg(feature = "wgpu")]
use wgpu_executor::WgpuExecutor;
use base64::Engine;
use glam::DAffine2;
use core::future::Future;
#[cfg(target_arch = "wasm32")]
use js_sys::{Object, Reflect};
@ -362,6 +366,60 @@ fn render_canvas(
RenderOutput::CanvasFrame(frame.into())
}
pub struct RasterizeNode<Footprint, Surface> {
footprint: Footprint,
surface_handle: Surface,
}
#[node_macro::node_fn(RasterizeNode)]
async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, footprint: Footprint, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>) -> ImageFrame<Color> {
let mut render = SvgRender::new();
if footprint.transform.matrix2.determinant() == 0. {
log::trace!("Invalid footprint received for rasterization");
return ImageFrame::default();
}
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
let size = aabb.size();
let resolution = footprint.resolution;
let render_params = RenderParams {
culling_bounds: None,
..Default::default()
};
*data.transform_mut() = DAffine2::from_translation(-aabb.start) * data.transform();
data.render_svg(&mut render, &render_params);
render.format_svg(glam::DVec2::ZERO, size);
let svg_string = render.svg.to_svg_string();
let canvas = &surface_handle.surface;
canvas.set_width(resolution.x);
canvas.set_height(resolution.y);
let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<CanvasRenderingContext2d>().unwrap();
let preamble = "data:image/svg+xml;base64,";
let mut base64_string = String::with_capacity(preamble.len() + svg_string.len() * 4);
base64_string.push_str(preamble);
base64::engine::general_purpose::STANDARD.encode_string(svg_string, &mut base64_string);
let image_data = web_sys::HtmlImageElement::new().unwrap();
image_data.set_src(base64_string.as_str());
wasm_bindgen_futures::JsFuture::from(image_data.decode()).await.unwrap();
context
.draw_image_with_html_image_element_and_dw_and_dh(&image_data, 0., 0., resolution.x as f64, resolution.y as f64)
.unwrap();
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
ImageFrame {
image,
transform: footprint.transform,
..Default::default()
}
}
// Render with the data node taking in Footprint.
impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<Output = T>, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, WasmEditorApi<'a>>
for RenderNode<Data, Surface, Footprint>

View file

@ -80,6 +80,8 @@ macro_rules! register_node {
macro_rules! async_node {
// TODO: we currently need to annotate the type here because the compiler would otherwise (correctly)
// assign a Pin<Box<dyn Future<Output=T>>> type to the node, which is not what we want for now.
//
// This `params` variant of the macro wraps the normal `fn_params` variant and is used as a shorthand for writing `T` instead of `() => T`
($path:ty, input: $input:ty, output: $output:ty, params: [ $($type:ty),*]) => {
async_node!($path, input: $input, output: $output, fn_params: [ $(() => $type),*])
};
@ -686,18 +688,20 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => VectorData, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => bool, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => f32, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => f64, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => String, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Option<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [() => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [ImageFrame<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [VectorData, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [GraphicGroup, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Artboard, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [bool, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f32, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f64, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [String, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: WasmSurfaceHandleFrame, fn_params: [Footprint => WasmSurfaceHandleFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: ImageFrame<Color>, fn_params: [Footprint => ImageFrame<Color>, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),