Skip to content

Commit

Permalink
Add explicit clip area to footprint
Browse files Browse the repository at this point in the history
  • Loading branch information
TrueDoctor committed Aug 9, 2024
1 parent c5dde18 commit 019ce71
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
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),
clip: raster::bbox::AxisAlignedBbox {
start: DVec2::ZERO,
end: DVec2::new(100., 100.),
},
..Default::default()
}),
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::imaginate_input::{ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus};
use graphene_core::memo::IORecord;
use graphene_core::raster::bbox::AxisAlignedBbox;
use graphene_core::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, ImageFrame, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice,
Expand Down Expand Up @@ -144,7 +145,7 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)
if let Some(&TaggedValue::Footprint(footprint)) = &document_node.inputs[index].as_non_exposed_value() {
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
let bounds = footprint.scale();
let oversample = footprint.resolution.as_dvec2() / bounds;
let oversample = footprint.clip.size() / bounds;

location_widgets.extend_from_slice(&[
NumberInput::new(Some(top_left.x))
Expand All @@ -159,7 +160,7 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)

let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale, 0., offset),
resolution: (oversample * scale).as_uvec2(),
clip: AxisAlignedBbox::from_size(oversample * scale),
..footprint
};

Expand All @@ -183,7 +184,7 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)

let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale, 0., offset),
resolution: (oversample * scale).as_uvec2(),
clip: AxisAlignedBbox::from_size(oversample * scale),
..footprint
};

Expand All @@ -206,7 +207,7 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)

let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale, 0., offset),
resolution: (oversample * scale).as_uvec2(),
clip: AxisAlignedBbox::from_size(oversample * scale),
..footprint
};

Expand All @@ -227,7 +228,7 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)

let footprint = Footprint {
transform: DAffine2::from_scale_angle_translation(scale, 0., offset),
resolution: (oversample * scale).as_uvec2(),
clip: AxisAlignedBbox::from_size(oversample * scale),
..footprint
};

Expand All @@ -241,14 +242,15 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize)
]);

resolution_widgets.push(
NumberInput::new(Some((footprint.resolution.as_dvec2() / bounds).x * 100.))
NumberInput::new(Some((footprint.clip.size() / bounds).x * 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 resolution = (bounds * x.value.unwrap_or(100.) / 100.).max((1., 1.).into()).min((4000., 4000.).into());
let clip = AxisAlignedBbox::from_size(resolution);

let footprint = Footprint { resolution, ..footprint };
let footprint = Footprint { clip, ..footprint };
TaggedValue::Footprint(footprint)
},
node_id,
Expand Down
5 changes: 3 additions & 2 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences;
use graphene_core::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_core::memo::IORecord;
use graphene_core::raster::bbox::AxisAlignedBbox;
use graphene_core::raster::ImageFrame;
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
Expand Down Expand Up @@ -479,7 +480,7 @@ impl NodeGraphExecutor {
let render_config = RenderConfig {
viewport: Footprint {
transform: document.metadata().document_to_viewport,
resolution: viewport_resolution,
clip: AxisAlignedBbox::from_size(viewport_resolution.as_dvec2()),
..Default::default()
},
#[cfg(any(feature = "resvg", feature = "vello"))]
Expand Down Expand Up @@ -516,7 +517,7 @@ impl NodeGraphExecutor {
let render_config = RenderConfig {
viewport: Footprint {
transform: transform * DAffine2::from_scale(DVec2::splat(export_config.scale_factor)),
resolution: (size * export_config.scale_factor).as_uvec2(),
clip: AxisAlignedBbox::from_size(size * export_config.scale_factor),
..Default::default()
},
export_format: graphene_core::application_io::ExportFormat::Svg,
Expand Down
31 changes: 29 additions & 2 deletions node-graph/gcore/src/graphic_element.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::application_io::TextureFrame;
use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::{BlendMode, ImageFrame};
use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::VectorData;
Expand Down Expand Up @@ -221,8 +222,34 @@ async fn construct_artboard(
background: Color,
clip: bool,
) -> Artboard {
footprint.transform *= DAffine2::from_translation(location.as_dvec2());
let graphic_group = self.contents.eval(footprint).await;
let mut new_footprint = footprint;

let viewport_bounds = footprint.viewport_bounds_in_local_space();
let artboard_bounds = AxisAlignedBbox {
start: location.as_dvec2(),
end: (location + dimensions).as_dvec2(),
};
let intersection = viewport_bounds.intersect(&artboard_bounds);
let offset = intersection.start;
let scale = footprint.scale();
let intersection = intersection.transformed(footprint.transform);
let resolution = (scale * intersection.size()).as_uvec2();
log::debug!("offset: {offset:?}, resolution: {resolution:?}");

if clip {
new_footprint = Footprint {
transform: DAffine2::IDENTITY,
clip: intersection,
..footprint
};
}

let mut graphic_group = self.contents.eval(new_footprint).await;

if clip {
let mut data_transform = graphic_group.transform_mut();
// *data_transform = DAffine2::from_translation(offset) * *data_transform;
}

Artboard {
graphic_group,
Expand Down
29 changes: 27 additions & 2 deletions node-graph/gcore/src/raster/bbox.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
use glam::{DAffine2, DVec2, Vec2Swizzles};

#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
#[derive(Clone, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, dyn_any::DynAny, PartialEq)]
pub struct AxisAlignedBbox {
pub start: DVec2,
pub end: DVec2,
}

impl core::hash::Hash for AxisAlignedBbox {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.start.x.to_bits().hash(state);
self.start.y.to_bits().hash(state);
self.end.x.to_bits().hash(state);
self.end.y.to_bits().hash(state);
}
}

impl AxisAlignedBbox {
pub const ZERO: Self = Self { start: DVec2::ZERO, end: DVec2::ZERO };
pub const ONE: Self = Self { start: DVec2::ZERO, end: DVec2::ONE };

pub fn from_size(size: DVec2) -> Self {
Self { start: DVec2::ZERO, end: size }
}

pub fn size(&self) -> DVec2 {
self.end - self.start
}
Expand All @@ -28,12 +42,14 @@ impl AxisAlignedBbox {
other.start.x <= self.end.x && other.end.x >= self.start.x && other.start.y <= self.end.y && other.end.y >= self.start.y
}

#[must_use]
pub fn union(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox {
AxisAlignedBbox {
start: DVec2::new(self.start.x.min(other.start.x), self.start.y.min(other.start.y)),
end: DVec2::new(self.end.x.max(other.end.x), self.end.y.max(other.end.y)),
}
}
#[must_use]
pub fn union_non_empty(&self, other: &AxisAlignedBbox) -> Option<AxisAlignedBbox> {
match (self.size() == DVec2::ZERO, other.size() == DVec2::ZERO) {
(true, true) => None,
Expand All @@ -46,12 +62,21 @@ impl AxisAlignedBbox {
}
}

#[must_use]
pub fn intersect(&self, other: &AxisAlignedBbox) -> AxisAlignedBbox {
AxisAlignedBbox {
start: DVec2::new(self.start.x.max(other.start.x), self.start.y.max(other.start.y)),
end: DVec2::new(self.end.x.min(other.end.x), self.end.y.min(other.end.y)),
}
}

#[must_use]
pub fn transformed(&self, transform: DAffine2) -> Self {
Self {
start: transform.transform_point2(self.start),
end: transform.transform_point2(self.end),
}
}
}

#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
Expand Down
20 changes: 14 additions & 6 deletions node-graph/gcore/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use dyn_any::StaticType;
use glam::DAffine2;

use glam::DVec2;
use glam::UVec2;

use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::ImageFrame;
Expand Down Expand Up @@ -140,8 +141,8 @@ pub enum RenderQuality {
pub struct Footprint {
/// Inverse of the transform which will be applied to the node output during the rendering process
pub transform: DAffine2,
/// Resolution of the target output area in pixels
pub resolution: glam::UVec2,
/// Target area which is displayed on the screen
pub clip: AxisAlignedBbox,
/// Quality of the render, this may be used by caching nodes to decide if the cached render is sufficient
pub quality: RenderQuality,
/// When the transform is set downstream, all upstream modifications have to be ignored
Expand All @@ -152,7 +153,10 @@ impl Default for Footprint {
fn default() -> Self {
Self {
transform: DAffine2::IDENTITY,
resolution: glam::UVec2::new(1920, 1080),
clip: AxisAlignedBbox {
start: DVec2::ZERO,
end: DVec2::new(1920., 1080.),
},
quality: RenderQuality::Full,
ignore_modifications: false,
}
Expand All @@ -162,8 +166,8 @@ impl Default for Footprint {
impl Footprint {
pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox {
let inverse = self.transform.inverse();
let start = inverse.transform_point2((0., 0.).into());
let end = inverse.transform_point2(self.resolution.as_dvec2());
let start = inverse.transform_point2(self.clip.start);
let end = inverse.transform_point2(self.clip.end);
AxisAlignedBbox { start, end }
}

Expand All @@ -174,6 +178,10 @@ impl Footprint {
pub fn offset(&self) -> DVec2 {
self.transform.transform_point2(DVec2::ZERO)
}

pub fn resolution(&self) -> UVec2 {
self.clip.size().as_uvec2()
}
}

#[derive(Debug, Clone, Copy)]
Expand All @@ -190,7 +198,7 @@ fn cull_vector_data<T>(footprint: Footprint, vector_data: T) -> T {
impl core::hash::Hash for Footprint {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.transform.to_cols_array().iter().for_each(|x| x.to_le_bytes().hash(state));
self.resolution.hash(state)
self.clip.hash(state)
}
}

Expand Down
7 changes: 6 additions & 1 deletion node-graph/gstd/src/raster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,7 @@ fn noise_pattern(

let mut size = viewport_bounds.size();
let mut offset = viewport_bounds.start;
log::debug!("size: {size:?}, offset: {offset:?}");
if clip {
// TODO: Remove "clip" entirely (and its arbitrary 100x100 clipping square) once we have proper resolution-aware layer clipping
const CLIPPING_SQUARE_SIZE: f64 = 100.;
Expand All @@ -661,6 +662,9 @@ fn noise_pattern(
let footprint_scale = footprint.scale();
let width = (size.x * footprint_scale.x) as u32;
let height = (size.y * footprint_scale.y) as u32;
log::debug!("resolution: {:?}", footprint.resolution());
let width = footprint.resolution().x;
let height = footprint.resolution().y;

// All
let mut image = Image::new(width, height, Color::from_luminance(0.5));
Expand Down Expand Up @@ -761,10 +765,11 @@ fn noise_pattern(
}
}

log::debug!("clip: {:?}", footprint.clip);
// Return the coherent noise image
ImageFrame::<Color> {
image,
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
transform: DAffine2::from_translation(footprint.clip.start) * DAffine2::from_scale(footprint.clip.size() * footprint.scale()),
alpha_blending: AlphaBlending::default(),
}
}
Expand Down
14 changes: 8 additions & 6 deletions node-graph/gstd/src/wasm_application_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
render.leaf_tag("rect", |attributes| {
attributes.push("x", "0");
attributes.push("y", "0");
attributes.push("width", footprint.resolution.x.to_string());
attributes.push("height", footprint.resolution.y.to_string());
attributes.push("width", footprint.resolution().x.to_string());
attributes.push("height", footprint.resolution().y.to_string());
let matrix = format_transform_matrix(footprint.transform.inverse());
if !matrix.is_empty() {
attributes.push("transform", matrix);
Expand All @@ -102,7 +102,7 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
}

data.render_svg(&mut render, &render_params);
render.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2()));
render.wrap_with_transform(footprint.transform, Some(footprint.clip.size()));

RenderOutput::Svg(render.svg.to_svg_string())
}
Expand All @@ -125,16 +125,18 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen

// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
let resolution = footprint.resolution();
log::debug!("rendering using resolution: {resolution:?}");

exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context)
exec.render_vello_scene(&scene, &surface_handle, resolution.x, resolution.y, &context)
.await
.expect("Failed to render Vello scene");
} else {
unreachable!("Attempted to render with Vello when no GPU executor is available");
}
let frame = SurfaceFrame {
surface_id: surface_handle.window_id,
resolution: render_config.viewport.resolution,
resolution: render_config.viewport.resolution(),
transform: glam::DAffine2::IDENTITY,
};
RenderOutput::CanvasFrame(frame)
Expand All @@ -161,7 +163,7 @@ async fn rasterize<_T: GraphicElementRendered + graphene_core::transform::Transf
}
let aabb = Bbox::from_transform(footprint.transform).to_axis_aligned_bbox();
let size = aabb.size();
let resolution = footprint.resolution;
let resolution = footprint.resolution();
let render_params = RenderParams {
culling_bounds: None,
..Default::default()
Expand Down
2 changes: 1 addition & 1 deletion node-graph/wgpu-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ async fn render_texture_node<'a: 'input>(footprint: Footprint, image: impl Node<
SurfaceFrame {
surface_id,
transform,
resolution: footprint.resolution,
resolution: footprint.resolution(),
}
}

Expand Down

0 comments on commit 019ce71

Please sign in to comment.