mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-08 15:28:00 +00:00
Instance tables refactor part 2: move the transform and alpha_blending fields up a level (#2249)
* Fix domain data structure field plural naming * Rename method one_item to one_instance Rename method one_item to one_instance * Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T> Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T> * Add transform and alpha_blending fields to Instances<T> * Finish the refactor (Brush tool is broken though) * Add test for brush node * Fix brush node * Fix default empty images being 1x1 instead of 0x0 as they should be * Fix tests * Fix path transform * Add correct upgrading to move the transform/blending up a level --------- Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
parent
4ff2bdb04f
commit
f1160e1ca6
33 changed files with 1099 additions and 984 deletions
|
@ -6,19 +6,18 @@ use graphene_core::raster::adjustments::blend_colors;
|
|||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample};
|
||||
use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Image, Pixel, Sample};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::{Ctx, Node};
|
||||
use graphene_core::{Ctx, GraphicElement, Node};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
|
||||
let vector_data = vector_data.one_item();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
vector_data.point_domain.positions().to_vec()
|
||||
}
|
||||
|
@ -89,17 +88,24 @@ fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64)
|
|||
}
|
||||
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn blit<P: Alpha + Pixel + std::fmt::Debug, BlendFn>(mut target: ImageFrame<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrame<P>
|
||||
fn blit<P, BlendFn>(mut target: ImageFrameTable<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrameTable<P>
|
||||
where
|
||||
P: Pixel + Alpha + std::fmt::Debug + dyn_any::StaticType,
|
||||
P::Static: Pixel,
|
||||
BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>,
|
||||
GraphicElement: From<ImageFrame<P>>,
|
||||
{
|
||||
if positions.is_empty() {
|
||||
return target;
|
||||
}
|
||||
|
||||
let target_size = DVec2::new(target.image.width as f64, target.image.height as f64);
|
||||
let target_width = target.one_instance().instance.image.width;
|
||||
let target_height = target.one_instance().instance.image.height;
|
||||
let target_size = DVec2::new(target_width as f64, target_height as f64);
|
||||
|
||||
let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
|
||||
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target.transform.inverse();
|
||||
|
||||
let document_to_target = DAffine2::from_translation(-texture_size / 2.) * DAffine2::from_scale(target_size) * target.transform().inverse();
|
||||
|
||||
for position in positions {
|
||||
let start = document_to_target.transform_point2(position).round();
|
||||
|
@ -114,17 +120,17 @@ where
|
|||
|
||||
// Tight blitting loop. Eagerly assert bounds to hopefully eliminate bounds check inside loop.
|
||||
let texture_index = |x: u32, y: u32| -> usize { (y as usize * texture.width as usize) + (x as usize) };
|
||||
let target_index = |x: u32, y: u32| -> usize { (y as usize * target.image.width as usize) + (x as usize) };
|
||||
let target_index = |x: u32, y: u32| -> usize { (y as usize * target_width as usize) + (x as usize) };
|
||||
|
||||
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
|
||||
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
|
||||
assert!(texture_index(max_x, max_y) < texture.data.len());
|
||||
assert!(target_index(max_x, max_y) < target.image.data.len());
|
||||
assert!(target_index(max_x, max_y) < target.one_instance().instance.image.data.len());
|
||||
|
||||
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {
|
||||
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
|
||||
let src_pixel = texture.data[texture_index(x, y)];
|
||||
let dst_pixel = &mut target.image.data[target_index(x + clamp_start.x, y + clamp_start.y)];
|
||||
let dst_pixel = &mut target.one_instance_mut().instance.image.data[target_index(x + clamp_start.x, y + clamp_start.y)];
|
||||
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
|
||||
}
|
||||
}
|
||||
|
@ -138,16 +144,9 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
|
|||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
|
||||
use crate::raster::empty_image;
|
||||
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
|
||||
// let normal_blend = BlendColorPairNode::new(
|
||||
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))),
|
||||
// FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))),
|
||||
// );
|
||||
// normal_blend.eval((Color::default(), Color::default()));
|
||||
// use crate::raster::blend_image_tuple;
|
||||
// blend_image_tuple((blank_texture, stamp), &normal_blend).await.image;
|
||||
crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image
|
||||
// let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend)));
|
||||
// blend_executor.eval((blank_texture, stamp)).image
|
||||
let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
|
||||
|
||||
image.one_instance().instance.image.clone()
|
||||
}
|
||||
|
||||
macro_rules! inline_blend_funcs {
|
||||
|
@ -162,7 +161,7 @@ macro_rules! inline_blend_funcs {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrame<Color> {
|
||||
pub fn blend_with_mode(background: ImageFrameTable<Color>, foreground: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
let opacity = opacity / 100.;
|
||||
inline_blend_funcs!(
|
||||
background,
|
||||
|
@ -211,21 +210,21 @@ pub fn blend_with_mode(background: ImageFrame<Color>, foreground: ImageFrame<Col
|
|||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||
let image = image.one_item().clone();
|
||||
|
||||
async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: ImageFrameTable<Color>, strokes: Vec<BrushStroke>, cache: BrushCache) -> ImageFrameTable<Color> {
|
||||
let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
|
||||
let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox();
|
||||
let image_bbox = Bbox::from_transform(image_frame_table.transform()).to_axis_aligned_bbox();
|
||||
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
|
||||
|
||||
let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
|
||||
let erase_restore_strokes: Vec<_> = strokes.iter().filter(|&s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect();
|
||||
|
||||
let mut brush_plan = cache.compute_brush_plan(image, &draw_strokes);
|
||||
let mut brush_plan = cache.compute_brush_plan(image_frame_table, &draw_strokes);
|
||||
|
||||
let mut background_bounds = bbox.to_transform();
|
||||
|
||||
if bounds.transform() != DAffine2::ZERO {
|
||||
// If the bounds are empty (no size on images or det(transform) = 0), keep the target bounds
|
||||
let bounds_empty = bounds.instances().all(|bounds| bounds.instance.width() == 0 || bounds.instance.height() == 0);
|
||||
if bounds.transform().matrix2.determinant() != 0. && !bounds_empty {
|
||||
background_bounds = bounds.transform();
|
||||
}
|
||||
|
||||
|
@ -289,10 +288,10 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
|
|||
if has_erase_strokes {
|
||||
let opaque_image = ImageFrame {
|
||||
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE),
|
||||
transform: background_bounds,
|
||||
alpha_blending: Default::default(),
|
||||
};
|
||||
let mut erase_restore_mask = opaque_image;
|
||||
let mut erase_restore_mask = ImageFrameTable::new(opaque_image);
|
||||
*erase_restore_mask.transform_mut() = background_bounds;
|
||||
*erase_restore_mask.one_instance_mut().alpha_blending = Default::default();
|
||||
|
||||
for stroke in erase_restore_strokes {
|
||||
let mut brush_texture = cache.get_cached_brush(&stroke.style);
|
||||
|
@ -314,7 +313,6 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
|
|||
);
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||
}
|
||||
|
||||
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
|
||||
BlendMode::Restore => {
|
||||
let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.));
|
||||
|
@ -325,7 +323,6 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
|
|||
);
|
||||
erase_restore_mask = blit_node.eval(erase_restore_mask).await;
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -335,13 +332,14 @@ async fn brush(_: impl Ctx, image: ImageFrameTable<Color>, bounds: ImageFrameTab
|
|||
actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await;
|
||||
}
|
||||
|
||||
ImageFrameTable::new(actual_image)
|
||||
actual_image
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use graphene_core::raster::Bitmap;
|
||||
use graphene_core::transform::Transform;
|
||||
|
||||
use glam::DAffine2;
|
||||
|
@ -354,4 +352,27 @@ mod test {
|
|||
// center pixel should be BLACK
|
||||
assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_brush_output_size() {
|
||||
let image = brush(
|
||||
(),
|
||||
ImageFrameTable::<Color>::default(),
|
||||
ImageFrameTable::<Color>::default(),
|
||||
vec![BrushStroke {
|
||||
trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||
style: BrushStyle {
|
||||
color: Color::BLACK,
|
||||
diameter: 20.,
|
||||
hardness: 20.,
|
||||
flow: 20.,
|
||||
spacing: 20.,
|
||||
blend_mode: BlendMode::Normal,
|
||||
},
|
||||
}],
|
||||
BrushCache::new_proto(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(image.width(), 20);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use graph_craft::proto::types::Percentage;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
||||
|
@ -9,7 +10,10 @@ use std::cmp::{max, min};
|
|||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Percentage) -> ImageFrameTable<Color> {
|
||||
let image_frame = image_frame.one_item();
|
||||
let image_frame_transform = image_frame.transform();
|
||||
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
|
||||
|
||||
let image_frame = image_frame.one_instance().instance;
|
||||
|
||||
// Prepare the image data for processing
|
||||
let image = &image_frame.image;
|
||||
|
@ -30,13 +34,11 @@ async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Perc
|
|||
base64_string: None,
|
||||
};
|
||||
|
||||
let result = ImageFrame {
|
||||
image: dehazed_image,
|
||||
transform: image_frame.transform,
|
||||
alpha_blending: image_frame.alpha_blending,
|
||||
};
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image: dehazed_image });
|
||||
*result.transform_mut() = image_frame_transform;
|
||||
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
// There is no real point in modifying these values because they do not change the final result all that much.
|
||||
|
|
|
@ -6,6 +6,8 @@ use graph_craft::proto::*;
|
|||
use graphene_core::application_io::ApplicationIo;
|
||||
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use graphene_core::raster::{BlendMode, Image, Pixel};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::*;
|
||||
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor, WgpuShaderInput};
|
||||
|
||||
|
@ -64,7 +66,7 @@ impl Clone for ComputePass {
|
|||
#[node_macro::old_node_impl(MapGpuNode)]
|
||||
async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi<WasmApplicationIo>) -> ImageFrameTable<Color> {
|
||||
let image_frame_table = ℑ
|
||||
let image = image.one_item();
|
||||
let image = image.one_instance().instance;
|
||||
|
||||
log::debug!("Executing gpu node");
|
||||
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
|
||||
|
@ -81,7 +83,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
|
|||
let name = "placeholder".to_string();
|
||||
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
|
||||
log::error!("Error creating compute pass descriptor in 'map_gpu()");
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
};
|
||||
self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
|
||||
log::error!("created compute pass");
|
||||
|
@ -109,18 +111,17 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
|
|||
#[cfg(feature = "image-compare")]
|
||||
log::debug!("score: {:?}", score.score);
|
||||
|
||||
let result = ImageFrame {
|
||||
image: Image {
|
||||
data: colors,
|
||||
width: image.image.width,
|
||||
height: image.image.height,
|
||||
..Default::default()
|
||||
},
|
||||
transform: image.transform,
|
||||
alpha_blending: image.alpha_blending,
|
||||
let new_image = Image {
|
||||
data: colors,
|
||||
width: image.image.width,
|
||||
height: image.image.height,
|
||||
..Default::default()
|
||||
};
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image: new_image });
|
||||
*result.transform_mut() = image_frame_table.transform();
|
||||
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance().alpha_blending;
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
|
||||
|
@ -138,7 +139,7 @@ where
|
|||
GraphicElement: From<ImageFrame<T>>,
|
||||
T::Static: Pixel,
|
||||
{
|
||||
let image = image.one_item();
|
||||
let image = image.one_instance().instance;
|
||||
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
let inner_network = NodeNetwork::value_network(node);
|
||||
|
@ -280,14 +281,19 @@ where
|
|||
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, background: ImageFrameTable<Color>, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable<Color> {
|
||||
let foreground = foreground.one_item();
|
||||
let background = background.one_item();
|
||||
let foreground_transform = foreground.transform();
|
||||
let background_transform = background.transform();
|
||||
|
||||
let background_alpha_blending = background.one_instance().alpha_blending;
|
||||
|
||||
let foreground = foreground.one_instance().instance;
|
||||
let background = background.one_instance().instance;
|
||||
|
||||
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
|
||||
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
|
||||
|
||||
// Transforms a point from the background image to the foreground image
|
||||
let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
|
||||
let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2();
|
||||
let translation: Vec2 = bg_to_fg.translation.as_vec2();
|
||||
|
@ -334,7 +340,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
|
||||
let Ok(proto_networks_result) = proto_networks else {
|
||||
log::error!("Error compiling network in 'blend_gpu_image()");
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
};
|
||||
let proto_networks = proto_networks_result;
|
||||
log::debug!("compiling shader");
|
||||
|
@ -444,16 +450,16 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
let result = executor.read_output_buffer(readback_buffer).await.unwrap();
|
||||
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
|
||||
|
||||
let result = ImageFrame {
|
||||
image: Image {
|
||||
data: colors,
|
||||
width: background.image.width,
|
||||
height: background.image.height,
|
||||
..Default::default()
|
||||
},
|
||||
transform: background.transform,
|
||||
alpha_blending: background.alpha_blending,
|
||||
let created_image = Image {
|
||||
data: colors,
|
||||
width: background.image.width,
|
||||
height: background.image.height,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image: created_image });
|
||||
*result.transform_mut() = background_transform;
|
||||
*result.one_instance_mut().alpha_blending = *background_alpha_blending;
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ async fn image_color_palette(
|
|||
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
|
||||
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
|
||||
|
||||
let image = image.one_item();
|
||||
let image = image.one_instance().instance;
|
||||
|
||||
for pixel in image.image.data.iter() {
|
||||
let r = pixel.r() * GRID;
|
||||
|
@ -79,7 +79,6 @@ mod test {
|
|||
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
|
||||
base64_string: None,
|
||||
},
|
||||
..Default::default()
|
||||
}),
|
||||
1,
|
||||
);
|
||||
|
|
|
@ -327,7 +327,7 @@ pub async fn imaginate<'a, P: Pixel>(
|
|||
set_progress(ImaginateStatus::Failed(err.to_string()));
|
||||
}
|
||||
};
|
||||
Image::empty()
|
||||
Image::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ use graphene_core::raster::{
|
|||
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
|
||||
Sample,
|
||||
};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node};
|
||||
use graphene_core::transform::{Transform, TransformMut};
|
||||
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
|
||||
|
||||
use fastnoise_lite;
|
||||
use glam::{DAffine2, DVec2, Vec2};
|
||||
|
@ -30,7 +30,10 @@ impl From<std::io::Error> for Error {
|
|||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable<Color>) -> ImageFrameTable<Color> {
|
||||
let image_frame = image_frame.one_item();
|
||||
let image_frame_transform = image_frame.transform();
|
||||
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
|
||||
|
||||
let image_frame = image_frame.one_instance().instance;
|
||||
|
||||
// Resize the image using the image crate
|
||||
let image = &image_frame.image;
|
||||
|
@ -38,7 +41,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
|
|||
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox();
|
||||
let image_bounds = Bbox::from_transform(image_frame_transform).to_axis_aligned_bbox();
|
||||
let intersection = viewport_bounds.intersect(&image_bounds);
|
||||
let image_size = DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64));
|
||||
let size = intersection.size();
|
||||
|
@ -46,7 +49,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
|
|||
|
||||
// If the image would not be visible, return an empty image
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
}
|
||||
|
||||
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
|
||||
|
@ -81,15 +84,13 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
|
|||
};
|
||||
// we need to adjust the offset if we truncate the offset calculation
|
||||
|
||||
let new_transform = image_frame.transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
|
||||
let result = ImageFrame {
|
||||
image,
|
||||
transform: new_transform,
|
||||
alpha_blending: image_frame.alpha_blending,
|
||||
};
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image });
|
||||
*result.transform_mut() = new_transform;
|
||||
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -256,33 +257,35 @@ fn mask_image<
|
|||
// }
|
||||
|
||||
#[node_macro::node(skip_impl)]
|
||||
async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P>
|
||||
async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P>
|
||||
where
|
||||
_P: Alpha + Pixel + Debug + Send + dyn_any::StaticType,
|
||||
_P::Static: Pixel,
|
||||
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
|
||||
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
|
||||
GraphicElement: From<ImageFrame<_P>>,
|
||||
{
|
||||
let (background, foreground) = images;
|
||||
|
||||
blend_image(foreground, background, map_fn)
|
||||
}
|
||||
|
||||
fn blend_image<'input, _P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
|
||||
foreground: Frame,
|
||||
background: Background,
|
||||
map_fn: &'input MapFn,
|
||||
) -> Background
|
||||
fn blend_image<'input, _P, MapFn, Frame, Background>(foreground: Frame, background: Background, map_fn: &'input MapFn) -> Background
|
||||
where
|
||||
MapFn: Node<'input, (_P, _P), Output = _P>,
|
||||
_P: Pixel + Alpha + Debug,
|
||||
Frame: Sample<Pixel = _P> + Transform,
|
||||
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
|
||||
{
|
||||
blend_image_closure(foreground, background, |a, b| map_fn.eval((a, b)))
|
||||
}
|
||||
|
||||
pub fn blend_image_closure<_P: Alpha + Pixel + Debug, MapFn, Frame: Sample<Pixel = _P> + Transform, Background: BitmapMut<Pixel = _P> + Transform + Sample<Pixel = _P>>(
|
||||
foreground: Frame,
|
||||
mut background: Background,
|
||||
map_fn: MapFn,
|
||||
) -> Background
|
||||
pub fn blend_image_closure<_P, MapFn, Frame, Background>(foreground: Frame, mut background: Background, map_fn: MapFn) -> Background
|
||||
where
|
||||
MapFn: Fn(_P, _P) -> _P,
|
||||
_P: Pixel + Alpha + Debug,
|
||||
Frame: Sample<Pixel = _P> + Transform,
|
||||
Background: BitmapMut<Pixel = _P> + Sample<Pixel = _P> + Transform,
|
||||
{
|
||||
let background_size = DVec2::new(background.width() as f64, background.height() as f64);
|
||||
|
||||
|
@ -319,19 +322,20 @@ pub struct ExtendImageToBoundsNode<Bounds> {
|
|||
}
|
||||
|
||||
#[node_macro::old_node_fn(ExtendImageToBoundsNode)]
|
||||
fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFrame<Color> {
|
||||
fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> ImageFrameTable<Color> {
|
||||
let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox();
|
||||
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
|
||||
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
|
||||
return image;
|
||||
}
|
||||
|
||||
if image.image.width == 0 || image.image.height == 0 {
|
||||
let image_instance = image.one_instance().instance;
|
||||
if image_instance.image.width == 0 || image_instance.image.height == 0 {
|
||||
return empty_image((), bounds, Color::TRANSPARENT);
|
||||
}
|
||||
|
||||
let orig_image_scale = DVec2::new(image.image.width as f64, image.image.height as f64);
|
||||
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform.inverse();
|
||||
let orig_image_scale = DVec2::new(image_instance.image.width as f64, image_instance.image.height as f64);
|
||||
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse();
|
||||
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();
|
||||
|
||||
let new_start = bounds_in_image_space.start.floor().min(DVec2::ZERO);
|
||||
|
@ -341,36 +345,37 @@ fn extend_image_to_bounds(image: ImageFrame<Color>, bounds: DAffine2) -> ImageFr
|
|||
// Copy over original image into enlarged image.
|
||||
let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT);
|
||||
let offset_in_new_image = (-new_start).as_uvec2();
|
||||
for y in 0..image.image.height {
|
||||
let old_start = y * image.image.width;
|
||||
for y in 0..image_instance.image.height {
|
||||
let old_start = y * image_instance.image.width;
|
||||
let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x;
|
||||
let old_row = &image.image.data[old_start as usize..(old_start + image.image.width) as usize];
|
||||
let new_row = &mut new_img.data[new_start as usize..(new_start + image.image.width) as usize];
|
||||
let old_row = &image_instance.image.data[old_start as usize..(old_start + image_instance.image.width) as usize];
|
||||
let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.image.width) as usize];
|
||||
new_row.copy_from_slice(old_row);
|
||||
}
|
||||
|
||||
// Compute new transform.
|
||||
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
|
||||
let new_texture_to_layer_space = image.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
|
||||
ImageFrame {
|
||||
image: new_img,
|
||||
transform: new_texture_to_layer_space,
|
||||
alpha_blending: image.alpha_blending,
|
||||
}
|
||||
let new_texture_to_layer_space = image.transform() * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
|
||||
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image: new_img });
|
||||
*result.transform_mut() = new_texture_to_layer_space;
|
||||
*result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn empty_image<P: Pixel>(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame<P> {
|
||||
fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTable<Color> {
|
||||
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||
|
||||
let image = Image::new(width, height, color);
|
||||
|
||||
ImageFrame {
|
||||
image,
|
||||
transform,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
}
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image });
|
||||
*result.transform_mut() = transform;
|
||||
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// #[cfg(feature = "serde")]
|
||||
|
@ -510,7 +515,7 @@ fn noise_pattern(
|
|||
|
||||
// If the image would not be visible, return an empty image
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
}
|
||||
|
||||
let footprint_scale = footprint.scale();
|
||||
|
@ -554,13 +559,11 @@ fn noise_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
let result = ImageFrame {
|
||||
image,
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
};
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image });
|
||||
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
|
||||
|
||||
return ImageFrameTable::new(result);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
noise.set_noise_type(Some(noise_type));
|
||||
|
@ -618,13 +621,11 @@ fn noise_pattern(
|
|||
}
|
||||
}
|
||||
|
||||
let result = ImageFrame {
|
||||
image,
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
};
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image });
|
||||
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
|
@ -640,7 +641,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
|||
|
||||
// If the image would not be visible, return an empty image
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
}
|
||||
|
||||
let scale = footprint.scale();
|
||||
|
@ -662,18 +663,17 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
|
|||
}
|
||||
}
|
||||
|
||||
let result = ImageFrame {
|
||||
image: Image {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
..Default::default()
|
||||
},
|
||||
transform: DAffine2::from_translation(offset) * DAffine2::from_scale(size),
|
||||
alpha_blending: Default::default(),
|
||||
let image = Image {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
..Default::default()
|
||||
};
|
||||
let mut result = ImageFrameTable::new(ImageFrame { image });
|
||||
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
|
||||
*result.one_instance_mut().alpha_blending = Default::default();
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::vector::misc::BooleanOperation;
|
||||
use graphene_core::vector::style::Fill;
|
||||
pub use graphene_core::vector::*;
|
||||
use graphene_core::{transform::Transform, GraphicGroup};
|
||||
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
|
||||
pub use path_bool as path_bool_lib;
|
||||
use path_bool::{FillRule, PathBooleanOperation};
|
||||
|
@ -12,7 +13,7 @@ use std::ops::Mul;
|
|||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable {
|
||||
fn vector_from_image<T: Transform>(image_frame: T) -> VectorData {
|
||||
fn vector_from_image<T: Transform>(image_frame: T) -> VectorDataTable {
|
||||
let corner1 = DVec2::ZERO;
|
||||
let corner2 = DVec2::new(1., 1.);
|
||||
|
||||
|
@ -22,18 +23,14 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
|
|||
let mut vector_data = VectorData::from_subpath(subpath);
|
||||
vector_data.style.set_fill(Fill::Solid(Color::from_rgb_str("777777").unwrap().to_gamma_srgb()));
|
||||
|
||||
vector_data
|
||||
VectorDataTable::new(vector_data)
|
||||
}
|
||||
|
||||
fn union_vector_data(graphic_element: &GraphicElement) -> VectorData {
|
||||
fn union_vector_data(graphic_element: &GraphicElement) -> VectorDataTable {
|
||||
match graphic_element {
|
||||
GraphicElement::VectorData(vector_data) => {
|
||||
let vector_data = vector_data.one_item();
|
||||
vector_data.clone()
|
||||
}
|
||||
GraphicElement::VectorData(vector_data) => vector_data.clone(),
|
||||
// Union all vector data in the graphic group into a single vector
|
||||
GraphicElement::GraphicGroup(graphic_group) => {
|
||||
let graphic_group = graphic_group.one_item();
|
||||
let vector_data = collect_vector_data(graphic_group);
|
||||
|
||||
boolean_operation_on_vector_data(&vector_data, BooleanOperation::Union)
|
||||
|
@ -42,28 +39,32 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> {
|
||||
fn collect_vector_data(graphic_group_table: &GraphicGroupTable) -> Vec<VectorDataTable> {
|
||||
let graphic_group = graphic_group_table.one_instance();
|
||||
|
||||
// Ensure all non vector data in the graphic group is converted to vector data
|
||||
let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element));
|
||||
let vector_data_tables = graphic_group.instance.iter().map(|(element, _)| union_vector_data(element));
|
||||
|
||||
// Apply the transform from the parent graphic group
|
||||
let transformed_vector_data = vector_data.map(|mut vector_data| {
|
||||
vector_data.transform = graphic_group.transform * vector_data.transform;
|
||||
vector_data
|
||||
let transformed_vector_data = vector_data_tables.map(|mut vector_data_table| {
|
||||
*vector_data_table.transform_mut() = graphic_group.transform() * vector_data_table.transform();
|
||||
vector_data_table
|
||||
});
|
||||
transformed_vector_data.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorData>) -> VectorData {
|
||||
fn subtract<'a>(vector_data: impl Iterator<Item = &'a VectorDataTable>) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.into_iter();
|
||||
let mut result = vector_data.next().cloned().unwrap_or_default();
|
||||
let mut next_vector_data = vector_data.next();
|
||||
|
||||
while let Some(lower_vector_data) = next_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform;
|
||||
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let upper_path_string = to_path(&result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
|
||||
let result = result.one_instance_mut().instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
|
||||
|
@ -76,49 +77,58 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
|
|||
|
||||
next_vector_data = vector_data.next();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn boolean_operation_on_vector_data(vector_data: &[VectorData], boolean_operation: BooleanOperation) -> VectorData {
|
||||
fn boolean_operation_on_vector_data(vector_data_table: &[VectorDataTable], boolean_operation: BooleanOperation) -> VectorDataTable {
|
||||
match boolean_operation {
|
||||
BooleanOperation::Union => {
|
||||
// Reverse vector data so that the result style is the style of the first vector data
|
||||
let mut vector_data = vector_data.iter().rev();
|
||||
let mut result = vector_data.next().cloned().unwrap_or_default();
|
||||
let mut second_vector_data = Some(vector_data.next().unwrap_or(const { &VectorData::empty() }));
|
||||
let mut vector_data_table = vector_data_table.iter().rev();
|
||||
let mut result_vector_data_table = vector_data_table.next().cloned().unwrap_or_default();
|
||||
|
||||
// Loop over all vector data and union it with the result
|
||||
let default = VectorDataTable::default();
|
||||
let mut second_vector_data = Some(vector_data_table.next().unwrap_or(&default));
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform;
|
||||
let transform_of_lower_into_space_of_upper = result_vector_data_table.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let upper_path_string = to_path(&result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
|
||||
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
|
||||
|
||||
let upper_path_string = to_path(result_vector_data, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
|
||||
let boolean_operation_result = from_path(&boolean_operation_string);
|
||||
|
||||
result.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
second_vector_data = vector_data.next();
|
||||
result_vector_data.colinear_manipulators = boolean_operation_result.colinear_manipulators;
|
||||
result_vector_data.point_domain = boolean_operation_result.point_domain;
|
||||
result_vector_data.segment_domain = boolean_operation_result.segment_domain;
|
||||
result_vector_data.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
second_vector_data = vector_data_table.next();
|
||||
}
|
||||
result
|
||||
|
||||
result_vector_data_table
|
||||
}
|
||||
BooleanOperation::SubtractFront => subtract(vector_data.iter()),
|
||||
BooleanOperation::SubtractBack => subtract(vector_data.iter().rev()),
|
||||
BooleanOperation::SubtractFront => subtract(vector_data_table.iter()),
|
||||
BooleanOperation::SubtractBack => subtract(vector_data_table.iter().rev()),
|
||||
BooleanOperation::Intersect => {
|
||||
let mut vector_data = vector_data.iter().rev();
|
||||
let mut vector_data = vector_data_table.iter().rev();
|
||||
let mut result = vector_data.next().cloned().unwrap_or_default();
|
||||
let mut second_vector_data = Some(vector_data.next().unwrap_or(const { &VectorData::empty() }));
|
||||
let default = VectorDataTable::default();
|
||||
let mut second_vector_data = Some(vector_data.next().unwrap_or(&default));
|
||||
|
||||
// For each vector data, set the result to the intersection of that data and the result
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform;
|
||||
let transform_of_lower_into_space_of_upper = result.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let upper_path_string = to_path(&result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
|
||||
let result = result.one_instance_mut().instance;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
|
@ -130,63 +140,67 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
|
|||
result.region_domain = boolean_operation_result.region_domain;
|
||||
second_vector_data = vector_data.next();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
BooleanOperation::Difference => {
|
||||
let mut vector_data_iter = vector_data.iter().rev();
|
||||
let mut any_intersection = VectorData::empty();
|
||||
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(const { &VectorData::empty() }));
|
||||
let mut vector_data_iter = vector_data_table.iter().rev();
|
||||
let mut any_intersection = VectorDataTable::default();
|
||||
let default = VectorDataTable::default();
|
||||
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(&default));
|
||||
|
||||
// Find where all vector data intersect at least once
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
|
||||
let all_other_vector_data = boolean_operation_on_vector_data(&vector_data_table.iter().filter(|v| v != &lower_vector_data).cloned().collect::<Vec<_>>(), BooleanOperation::Union);
|
||||
let all_other_vector_data_instance = all_other_vector_data.one_instance();
|
||||
|
||||
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform.inverse() * lower_vector_data.transform;
|
||||
let transform_of_lower_into_space_of_upper = all_other_vector_data.transform().inverse() * lower_vector_data.transform();
|
||||
|
||||
let upper_path_string = to_path(&all_other_vector_data, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data, transform_of_lower_into_space_of_upper);
|
||||
let upper_path_string = to_path(all_other_vector_data_instance.instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.one_instance().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
let mut boolean_intersection_result = from_path(&boolean_intersection_string);
|
||||
let mut boolean_intersection_result = VectorDataTable::new(from_path(&boolean_intersection_string));
|
||||
*boolean_intersection_result.transform_mut() = all_other_vector_data_instance.transform();
|
||||
|
||||
boolean_intersection_result.transform = all_other_vector_data.transform;
|
||||
boolean_intersection_result.style = all_other_vector_data.style.clone();
|
||||
boolean_intersection_result.alpha_blending = all_other_vector_data.alpha_blending;
|
||||
boolean_intersection_result.one_instance_mut().instance.style = all_other_vector_data_instance.instance.style.clone();
|
||||
*boolean_intersection_result.one_instance_mut().alpha_blending = *all_other_vector_data_instance.alpha_blending;
|
||||
|
||||
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform;
|
||||
let transform_of_lower_into_space_of_upper = boolean_intersection_result.one_instance_mut().transform().inverse() * any_intersection.transform();
|
||||
|
||||
let upper_path_string = to_path(&boolean_intersection_result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(&any_intersection, transform_of_lower_into_space_of_upper);
|
||||
let upper_path_string = to_path(boolean_intersection_result.one_instance_mut().instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(any_intersection.one_instance_mut().instance, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
|
||||
any_intersection = union_result;
|
||||
*any_intersection.one_instance_mut().instance = union_result;
|
||||
|
||||
any_intersection.transform = boolean_intersection_result.transform;
|
||||
any_intersection.style = boolean_intersection_result.style.clone();
|
||||
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
|
||||
*any_intersection.transform_mut() = boolean_intersection_result.transform();
|
||||
any_intersection.one_instance_mut().instance.style = boolean_intersection_result.one_instance_mut().instance.style.clone();
|
||||
any_intersection.one_instance_mut().alpha_blending = boolean_intersection_result.one_instance_mut().alpha_blending;
|
||||
|
||||
second_vector_data = vector_data_iter.next();
|
||||
}
|
||||
// Subtract the area where they intersect at least once from the union of all vector data
|
||||
let union = boolean_operation_on_vector_data(vector_data, BooleanOperation::Union);
|
||||
let union = boolean_operation_on_vector_data(vector_data_table, BooleanOperation::Union);
|
||||
boolean_operation_on_vector_data(&[union, any_intersection], BooleanOperation::SubtractFront)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let group_of_paths = group_of_paths.one_item();
|
||||
// The first index is the bottom of the stack
|
||||
let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation);
|
||||
let mut result_vector_data_table = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation);
|
||||
|
||||
let transform = boolean_operation_result.transform;
|
||||
VectorData::transform(&mut boolean_operation_result, transform);
|
||||
boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
boolean_operation_result.transform = DAffine2::IDENTITY;
|
||||
boolean_operation_result.upstream_graphic_group = Some(GraphicGroupTable::new(group_of_paths.clone()));
|
||||
// Replace the transformation matrix with a mutation of the vector points themselves
|
||||
let result_vector_data_table_transform = result_vector_data_table.transform();
|
||||
*result_vector_data_table.transform_mut() = DAffine2::IDENTITY;
|
||||
let result_vector_data = result_vector_data_table.one_instance_mut().instance;
|
||||
VectorData::transform(result_vector_data, result_vector_data_table_transform);
|
||||
result_vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector_data.upstream_graphic_group = Some(group_of_paths.clone());
|
||||
|
||||
VectorDataTable::new(boolean_operation_result)
|
||||
result_vector_data_table
|
||||
}
|
||||
|
||||
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
|
||||
|
|
|
@ -11,6 +11,8 @@ use graphene_core::raster::Image;
|
|||
use graphene_core::renderer::RenderMetadata;
|
||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
|
||||
use graphene_core::transform::Footprint;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use graphene_core::transform::TransformMut;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
|
||||
|
||||
|
@ -40,7 +42,7 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
|
|||
// image: ImageFrameTable<graphene_core::raster::SRGBA8>,
|
||||
// surface_handle: Arc<WasmSurfaceHandle>,
|
||||
// ) -> graphene_core::application_io::SurfaceHandleFrame<HtmlCanvasElement> {
|
||||
// let image = image.one_item();
|
||||
// let image = image.one_instance().instance;
|
||||
// let image_data = image.image.data;
|
||||
// let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
|
||||
// if image.image.width > 0 && image.image.height > 0 {
|
||||
|
@ -76,7 +78,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
|
|||
#[node_macro::node(category("Network"))]
|
||||
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
};
|
||||
let image = image.to_rgba32f();
|
||||
let image = ImageFrame {
|
||||
|
@ -86,8 +88,6 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
|
|||
height: image.height(),
|
||||
..Default::default()
|
||||
},
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
alpha_blending: Default::default(),
|
||||
};
|
||||
|
||||
ImageFrameTable::new(image)
|
||||
|
@ -130,7 +130,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen
|
|||
let mut child = Scene::new();
|
||||
|
||||
let mut context = wgpu_executor::RenderContext::default();
|
||||
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context);
|
||||
data.render_to_vello(&mut child, Default::default(), &mut context);
|
||||
|
||||
// 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())));
|
||||
|
@ -167,7 +167,7 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
|||
) -> ImageFrameTable<Color> {
|
||||
if footprint.transform.matrix2.determinant() == 0. {
|
||||
log::trace!("Invalid footprint received for rasterization");
|
||||
return ImageFrameTable::default();
|
||||
return ImageFrameTable::empty();
|
||||
}
|
||||
|
||||
let mut render = SvgRender::new();
|
||||
|
@ -204,13 +204,12 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
|
|||
|
||||
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
|
||||
|
||||
let result = ImageFrame {
|
||||
let mut result = ImageFrameTable::new(ImageFrame {
|
||||
image: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32),
|
||||
transform: footprint.transform,
|
||||
..Default::default()
|
||||
};
|
||||
});
|
||||
*result.transform_mut() = footprint.transform;
|
||||
|
||||
ImageFrameTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
|
@ -242,8 +241,10 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
|||
|
||||
let data = data.eval(ctx.clone()).await;
|
||||
let editor_api = editor_api.eval(ctx.clone()).await;
|
||||
|
||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||
let surface_handle = _surface_handle.eval(ctx.clone()).await;
|
||||
|
||||
let use_vello = editor_api.editor_preferences.use_vello();
|
||||
#[cfg(all(feature = "vello", target_arch = "wasm32"))]
|
||||
let use_vello = use_vello && surface_handle.is_some();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue