mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-24 16:13:44 +00:00
Add explicit clip area to footprint
This commit is contained in:
parent
f89a8447a6
commit
d69cbc7dee
9 changed files with 102 additions and 29 deletions
|
@ -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;
|
||||
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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))]
|
||||
|
|
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.;
|
||||
|
@ -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));
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -125,8 +125,10 @@ 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 {
|
||||
|
@ -134,7 +136,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
|
|||
}
|
||||
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)
|
||||
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue