Add explicit clip area to footprint

This commit is contained in:
dennis@kobert.dev 2024-08-09 12:03:21 +02:00
parent f89a8447a6
commit d69cbc7dee
No known key found for this signature in database
GPG key ID: 5A4358CB9530F933
9 changed files with 102 additions and 29 deletions

View file

@ -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,

View file

@ -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))]

View file

@ -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)
}
}

View file

@ -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(),
}
}

View file

@ -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()

View file

@ -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(),
}
}