Instance table refactor part 6: remove usage of `one_instance_*` functions (#2672)

* Refactor the spline node

* Refactor the jitter_points node

* Refactor the morph node

* Refactor the merge_by_distance node

* Refactor the area node

* Refactor the centroid node

* Refactor the bevel node

* Refactor the tests

* Code review

* Refactor the morph node

* Refactor the extend_image_to_bounds and sample_image node

* Refactor the dehaze node

* Refactor the blur node

* Refactor the vector_points node

* Refactor the blit node

* Refactor the blend_gpu_image node

* Refactor the path_modify node

* Refactor the image_color_palette

* Fix copy_to_points

* Code review

* Partially make progress toward fixing the Draw Canvas node

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare 2025-05-29 15:38:16 +05:30 committed by GitHub
parent fbefa5b827
commit 4d2e1d57fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 640 additions and 588 deletions

View File

@ -609,7 +609,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrameTable>")),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrameTable<SRGBA8>>")),
..Default::default()
},
DocumentNode {
@ -647,7 +647,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Convert Image Frame".to_string(),
display_name: "Into".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},

View File

@ -36,6 +36,15 @@ impl AlphaBlending {
blend_mode: BlendMode::Normal,
}
}
pub fn lerp(&self, other: &Self, t: f32) -> Self {
let lerp = |a: f32, b: f32, t: f32| a + (b - a) * t;
AlphaBlending {
opacity: lerp(self.opacity, other.opacity, t),
blend_mode: if t < 0.5 { self.blend_mode } else { other.blend_mode },
}
}
}
// TODO: Eventually remove this migration document upgrade code

View File

@ -204,7 +204,7 @@ pub struct InstanceMut<'a, T> {
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Default, Debug)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,

View File

@ -2,7 +2,7 @@ use super::Color;
use super::discrete_srgb::float_to_srgb_u8;
use crate::AlphaBlending;
use crate::GraphicElement;
use crate::instances::Instances;
use crate::instances::{Instance, Instances};
use crate::transform::TransformMut;
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
@ -393,6 +393,23 @@ impl From<Image<Color>> for Image<SRGBA8> {
}
}
impl From<ImageFrameTable<Color>> for ImageFrameTable<SRGBA8> {
fn from(image_frame_table: ImageFrameTable<Color>) -> Self {
let mut result_table = ImageFrameTable::<SRGBA8>::empty();
for image_frame_instance in image_frame_table.instance_iter() {
result_table.push(Instance {
instance: image_frame_instance.instance.into(),
transform: image_frame_instance.transform,
alpha_blending: image_frame_instance.alpha_blending,
source_node_id: image_frame_instance.source_node_id,
});
}
result_table
}
}
impl From<Image<SRGBA8>> for Image<Color> {
fn from(image: Image<SRGBA8>) -> Self {
let data = image.data.into_iter().map(|x| x.into()).collect();

View File

@ -1,6 +1,5 @@
use super::*;
use crate::Ctx;
use crate::transform::TransformMut;
use crate::uuid::generate_uuid;
use bezier_rs::BezierHandles;
use core::hash::BuildHasher;
@ -425,14 +424,10 @@ impl core::hash::Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
let vector_data_transform = *vector_data.one_instance_ref().transform;
let vector_data = vector_data.one_instance_mut().instance;
modification.apply(vector_data);
let mut result = VectorDataTable::new(vector_data.clone());
*result.transform_mut() = vector_data_transform;
result
for mut vector_data_instance in vector_data.instance_mut_iter() {
modification.apply(&mut vector_data_instance.instance);
}
vector_data
}
#[test]

View File

@ -7,7 +7,7 @@ use crate::instances::{Instance, InstanceMut, Instances};
use crate::raster::image::ImageFrameTable;
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, ReferencePoint, Transform, TransformMut};
use crate::transform::{Footprint, ReferencePoint, Transform};
use crate::vector::PointDomain;
use crate::vector::misc::dvec2_to_point;
use crate::vector::style::{LineCap, LineJoin};
@ -312,23 +312,22 @@ async fn copy_to_points<I: 'n + Send>(
where
Instances<I>: GraphicElementRendered,
{
let points_transform = points.transform();
let points_list = points.instance_ref_iter().flat_map(|element| element.instance.point_domain.positions());
let mut result_table = GraphicGroupTable::default();
let random_scale_difference = random_scale_max - random_scale_min;
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY, false).unwrap_or_default();
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
for point_instance in points.instance_iter() {
let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into());
let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into());
let do_scale = random_scale_difference.abs() > 1e-6;
let do_rotation = random_rotation.abs() > 1e-6;
let mut result_table = GraphicGroupTable::default();
for &point in points_list.into_iter() {
let points_transform = point_instance.transform;
for &point in point_instance.instance.point_domain.positions() {
let center_transform = DAffine2::from_translation(instance_center);
let translation = points_transform.transform_point2(point);
@ -363,6 +362,7 @@ where
source_node_id: None,
});
}
}
result_table
}
@ -445,12 +445,11 @@ async fn round_corners(
#[default(5.)]
min_angle_threshold: Angle,
) -> VectorDataTable {
let source_transform = source.transform();
let source_transform_inverse = source_transform.inverse();
let mut result_table = VectorDataTable::empty();
for source in source.instance_ref_iter() {
let source_transform = *source.transform;
let source_transform_inverse = source_transform.inverse();
let source = source.instance;
let upstream_graphic_group = source.upstream_graphic_group.clone();
@ -1418,17 +1417,17 @@ async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> V
}
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
let original_transform = vector_data.transform();
let vector_data = vector_data.one_instance_mut().instance;
async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
let mut result_table = VectorDataTable::empty();
for mut vector_data_instance in vector_data.instance_iter() {
// Exit early if there are no points to generate splines from.
if vector_data.point_domain.positions().is_empty() {
return VectorDataTable::new(VectorData::empty());
if vector_data_instance.instance.point_domain.positions().is_empty() {
continue;
}
let mut segment_domain = SegmentDomain::default();
for subpath in vector_data.stroke_bezier_paths() {
for subpath in vector_data_instance.instance.stroke_bezier_paths() {
let positions = subpath.manipulator_groups().iter().map(|group| group.anchor).collect::<Vec<_>>();
let closed = subpath.closed() && positions.len() > 2;
@ -1445,8 +1444,8 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
for i in 0..(positions.len() - if closed { 0 } else { 1 }) {
let next_index = (i + 1) % positions.len();
let start_index = vector_data.point_domain.resolve_id(subpath.manipulator_groups()[i].id).unwrap();
let end_index = vector_data.point_domain.resolve_id(subpath.manipulator_groups()[next_index].id).unwrap();
let start_index = vector_data_instance.instance.point_domain.resolve_id(subpath.manipulator_groups()[i].id).unwrap();
let end_index = vector_data_instance.instance.point_domain.resolve_id(subpath.manipulator_groups()[next_index].id).unwrap();
let handle_start = first_handles[i];
let handle_end = positions[next_index] * 2. - first_handles[next_index];
@ -1455,43 +1454,50 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id);
}
}
vector_data.segment_domain = segment_domain;
let mut result = VectorDataTable::new(vector_data.clone());
*result.transform_mut() = original_transform;
result
vector_data_instance.instance.segment_domain = segment_domain;
result_table.push(vector_data_instance);
}
// TODO: remove after pt6 of instance table refactor
if result_table.is_empty() {
return VectorDataTable::new(VectorData::empty());
}
result_table
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
let vector_data_transform = vector_data.transform();
let mut vector_data = vector_data.one_instance_ref().instance.clone();
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
let mut result_table = VectorDataTable::empty();
for mut vector_data_instance in vector_data.instance_iter() {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let deltas = (0..vector_data.point_domain.positions().len())
let vector_data_transform = vector_data_instance.transform;
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
let deltas = (0..vector_data_instance.instance.point_domain.positions().len())
.map(|_| {
let angle = rng.random::<f64>() * std::f64::consts::TAU;
inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::<f64>() * amount)
})
.collect::<Vec<_>>();
let mut already_applied = vec![false; vector_data.point_domain.positions().len()];
let mut already_applied = vec![false; vector_data_instance.instance.point_domain.positions().len()];
for (handles, start, end) in vector_data.segment_domain.handles_and_points_mut() {
for (handles, start, end) in vector_data_instance.instance.segment_domain.handles_and_points_mut() {
let start_delta = deltas[*start];
let end_delta = deltas[*end];
if !already_applied[*start] {
let start_position = vector_data.point_domain.positions()[*start];
vector_data.point_domain.set_position(*start, start_position + start_delta);
let start_position = vector_data_instance.instance.point_domain.positions()[*start];
vector_data_instance.instance.point_domain.set_position(*start, start_position + start_delta);
already_applied[*start] = true;
}
if !already_applied[*end] {
let end_position = vector_data.point_domain.positions()[*end];
vector_data.point_domain.set_position(*end, end_position + end_delta);
let end_position = vector_data_instance.instance.point_domain.positions()[*end];
vector_data_instance.instance.point_domain.set_position(*end, end_position + end_delta);
already_applied[*end] = true;
}
@ -1501,41 +1507,39 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
*handle_end += end_delta;
}
bezier_rs::BezierHandles::Quadratic { handle } => {
*handle = vector_data_transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
*handle = vector_data_instance.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
}
bezier_rs::BezierHandles::Linear => {}
}
}
vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
vector_data_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY);
result_table.push(vector_data_instance);
}
let mut result = VectorDataTable::new(vector_data.clone());
*result.transform_mut() = vector_data_transform;
result
result_table
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction) -> VectorDataTable {
let mut source = source;
let mut target = target;
let time = time.clamp(0., 1.);
let mut result_table = VectorDataTable::default();
let mut result_table = VectorDataTable::empty();
for (source_instance, target_instance) in source.instance_iter().zip(target.instance_iter()) {
let mut vector_data_instance = VectorData::default();
// Lerp styles
let source_alpha_blending = source.one_instance_ref().alpha_blending;
let target_alpha_blending = target.one_instance_ref().alpha_blending;
*result_table.one_instance_mut().alpha_blending = if time < 0.5 { *source_alpha_blending } else { *target_alpha_blending };
result_table.one_instance_mut().instance.style = source.one_instance_ref().instance.style.lerp(&target.one_instance_ref().instance.style, time);
let vector_data_alpha_blending = source_instance.alpha_blending.lerp(&target_instance.alpha_blending, time as f32);
vector_data_instance.style = source_instance.instance.style.lerp(&target_instance.instance.style, time);
// Before and after transforms
let source_transform = *source.one_instance_ref().transform;
let target_transform = *target.one_instance_ref().transform;
let source_transform = source_instance.transform;
let target_transform = target_instance.transform;
// Before and after paths
let source_paths = source.one_instance_mut().instance.stroke_bezier_paths();
let target_paths = target.one_instance_mut().instance.stroke_bezier_paths();
let source_paths = source_instance.instance.stroke_bezier_paths();
let target_paths = target_instance.instance.stroke_bezier_paths();
for (mut source_path, mut target_path) in source_paths.zip(target_paths) {
source_path.apply_transform(source_transform);
target_path.apply_transform(target_transform);
@ -1565,14 +1569,14 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat
source_manipulators.out_handle = Some(source_out_handle.lerp(target_out_handle, time));
}
result_table.one_instance_mut().instance.append_subpath(source_path.clone(), true);
vector_data_instance.append_subpath(source_path.clone(), true);
}
// Deal with unmatched extra paths by collapsing them
let source_paths_count = source.one_instance_ref().instance.stroke_bezier_paths().count();
let target_paths_count = target.one_instance_ref().instance.stroke_bezier_paths().count();
let source_paths = source.one_instance_mut().instance.stroke_bezier_paths().skip(target_paths_count);
let target_paths = target.one_instance_mut().instance.stroke_bezier_paths().skip(source_paths_count);
let source_paths_count = source_instance.instance.stroke_bezier_paths().count();
let target_paths_count = target_instance.instance.stroke_bezier_paths().count();
let source_paths = source_instance.instance.stroke_bezier_paths().skip(target_paths_count);
let target_paths = target_instance.instance.stroke_bezier_paths().skip(source_paths_count);
for mut source_path in source_paths {
source_path.apply_transform(source_transform);
@ -1582,7 +1586,7 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat
group.in_handle = group.in_handle.map(|handle| handle.lerp(end, time));
group.out_handle = group.out_handle.map(|handle| handle.lerp(end, time));
}
result_table.one_instance_mut().instance.append_subpath(source_path, true);
vector_data_instance.append_subpath(source_path, true);
}
for mut target_path in target_paths {
target_path.apply_transform(target_transform);
@ -1592,7 +1596,14 @@ async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDat
group.in_handle = group.in_handle.map(|handle| start.lerp(handle, time));
group.out_handle = group.out_handle.map(|handle| start.lerp(handle, time));
}
result_table.one_instance_mut().instance.append_subpath(target_path, true);
vector_data_instance.append_subpath(target_path, true);
}
result_table.push(Instance {
instance: vector_data_instance,
alpha_blending: vector_data_alpha_blending,
..Default::default()
});
}
result_table
@ -1638,7 +1649,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
point_domain.push(next_id.next_id(), pos);
// Add a new segment to be created later
new_segments.push([new_index, original_index])
new_segments.push([new_index, original_index]);
}
}
@ -1707,12 +1718,16 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source_transform = source.transform();
let source = source.one_instance_ref().instance;
let mut result_table = VectorDataTable::empty();
let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
*result.transform_mut() = source_transform;
result
for source_instance in source.instance_iter() {
result_table.push(Instance {
instance: bevel_algorithm(source_instance.instance, source_instance.transform, distance),
..Default::default()
});
}
result_table
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -1734,15 +1749,14 @@ fn point_inside(_: impl Ctx, source: VectorDataTable, point: DVec2) -> bool {
#[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))]
fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
let source_transform = source.transform();
let mut source = source.one_instance_ref().instance.clone();
let mut result_table = VectorDataTable::empty();
source.merge_by_distance(distance);
for mut source_instance in source.instance_iter() {
source_instance.instance.merge_by_distance(distance);
result_table.push(source_instance);
}
let mut result = VectorDataTable::new(source);
*result.transform_mut() = source_transform;
result
result_table
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -1750,16 +1764,13 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance_ref().instance;
let mut area = 0.;
let scale = vector_data_transform.decompose_scale();
for subpath in vector_data.stroke_bezier_paths() {
area += subpath.area(Some(1e-3), Some(1e-3));
}
area * scale[0] * scale[1]
vector_data
.instance_ref_iter()
.map(|vector_data_instance| {
let scale = vector_data_instance.transform.decompose_scale();
vector_data_instance.instance.stroke_bezier_paths().map(|subpath| subpath.area(Some(1e-3), Some(1e-3))).sum::<f64>() * scale.x * scale.y
})
.sum()
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
@ -1767,49 +1778,52 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
let vector_data = vector_data.eval(new_ctx).await;
let vector_data_transform = vector_data.transform();
let vector_data = vector_data.one_instance_ref().instance;
if vector_data.is_empty() {
return DVec2::ZERO;
}
if centroid_type == CentroidType::Area {
let mut area = 0.;
// All subpath centroid positions added together as if they were vectors from the origin.
let mut centroid = DVec2::ZERO;
for subpath in vector_data.stroke_bezier_paths() {
if let Some((subpath_centroid, subpath_area)) = subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)) {
if subpath_area == 0. {
continue;
// Cumulative area or length of all subpaths
let mut sum = 0.;
for vector_data_instance in vector_data.instance_ref_iter() {
for subpath in vector_data_instance.instance.stroke_bezier_paths() {
let partial = match centroid_type {
CentroidType::Area => subpath.area_centroid_and_area(Some(1e-3), Some(1e-3)).filter(|(_, area)| *area > 0.),
CentroidType::Length => subpath.length_centroid_and_length(None, true),
};
if let Some((subpath_centroid, area_or_length)) = partial {
let subpath_centroid = vector_data_instance.transform.transform_point2(subpath_centroid);
sum += area_or_length;
centroid += area_or_length * subpath_centroid;
}
area += subpath_area;
centroid += subpath_area * subpath_centroid;
}
}
if area != 0. {
centroid /= area;
return vector_data_transform.transform_point2(centroid);
}
if sum > 0. {
centroid / sum
}
// Without a summed denominator, return the average of all positions instead
else {
let mut count: usize = 0;
let mut length = 0.;
let mut centroid = DVec2::ZERO;
for subpath in vector_data.stroke_bezier_paths() {
if let Some((subpath_centroid, subpath_length)) = subpath.length_centroid_and_length(None, true) {
length += subpath_length;
centroid += subpath_length * subpath_centroid;
}
}
let summed_positions = vector_data
.instance_ref_iter()
.flat_map(|vector_data_instance| {
vector_data_instance
.instance
.point_domain
.positions()
.iter()
.map(|&p| vector_data_instance.transform.transform_point2(p))
})
.inspect(|_| count += 1)
.sum::<DVec2>();
if length != 0. {
centroid /= length;
return vector_data_transform.transform_point2(centroid);
if count != 0 { summed_positions / (count as f64) } else { DVec2::ZERO }
}
let positions = vector_data.point_domain.positions();
if !positions.is_empty() {
let centroid = positions.iter().sum::<DVec2>() / (positions.len() as f64);
return vector_data_transform.transform_point2(centroid);
}
DVec2::ZERO
}
#[cfg(test)]
@ -1885,7 +1899,7 @@ mod test {
// Test a VectorData with non-zero rotation
let square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
let mut square = VectorDataTable::new(square);
*square.one_instance_mut().transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
*square.get_mut(0).unwrap().transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
let bounding_box = BoundingBoxNode {
vector_data: FutureWrapperNode(square),
}
@ -2044,7 +2058,7 @@ mod test {
let vector_data = VectorData::from_subpath(source);
let mut vector_data_table = VectorDataTable::new(vector_data.clone());
*vector_data_table.one_instance_mut().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
*vector_data_table.get_mut(0).unwrap().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
let beveled = beveled.instance_ref_iter().next().unwrap().instance;

View File

@ -15,9 +15,7 @@ use graphene_core::{Ctx, GraphicElement, Node};
#[node_macro::node(category("Debug"))]
fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec<DVec2> {
let vector_data = vector_data.one_instance_ref().instance;
vector_data.point_domain.positions().to_vec()
vector_data.instance_iter().flat_map(|element| element.instance.point_domain.positions().to_vec()).collect()
}
#[derive(Clone, Copy, Debug, PartialEq)]
@ -96,16 +94,17 @@ where
return target;
}
let target_width = target.one_instance_ref().instance.width;
let target_height = target.one_instance_ref().instance.height;
for target_instance in target.instance_mut_iter() {
let target_width = target_instance.instance.width;
let target_height = target_instance.instance.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_instance.transform.inverse();
for position in positions {
let start = document_to_target.transform_point2(position).round();
for position in &positions {
let start = document_to_target.transform_point2(*position).round();
let stop = start + texture_size;
// Half-open integer ranges [start, stop).
@ -122,16 +121,17 @@ where
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.one_instance_ref().instance.data.len());
assert!(target_index(max_x, max_y) < target_instance.instance.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.one_instance_mut().instance.data[target_index(x + clamp_start.x, y + clamp_start.y)];
let dst_pixel = &mut target_instance.instance.data[target_index(x + clamp_start.x, y + clamp_start.y)];
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
}
}
}
}
target
}

View File

@ -1,6 +1,5 @@
use graph_craft::proto::types::Percentage;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{Color, Ctx};
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
@ -8,11 +7,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_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let image = image_frame.one_instance_ref().instance;
let mut result_table = ImageFrameTable::empty();
for mut image_frame_instance in image_frame.instance_iter() {
let image = image_frame_instance.instance;
// Prepare the image data for processing
let image_data = bytemuck::cast_vec(image.data.clone());
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
@ -31,11 +29,12 @@ async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Perc
base64_string: None,
};
let mut result = ImageFrameTable::new(dehazed_image);
*result.transform_mut() = image_frame_transform;
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
image_frame_instance.instance = dehazed_image;
image_frame_instance.source_node_id = None;
result_table.push(image_frame_instance);
}
result
result_table
}
// There is no real point in modifying these values because they do not change the final result all that much.

View File

@ -1,7 +1,6 @@
use graph_craft::proto::types::PixelLength;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::raster::{Bitmap, BitmapMut};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{Color, Ctx};
/// Blurs the image with a Gaussian or blur kernel filter.
@ -19,10 +18,9 @@ async fn blur(
/// Opt to incorrectly apply the filter with color calculations in gamma space for compatibility with the results from other software.
gamma: bool,
) -> ImageFrameTable<Color> {
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let image = image_frame.one_instance_ref().instance.clone();
let mut result_table = ImageFrameTable::empty();
for mut image_instance in image_frame.instance_iter() {
let image = image_instance.instance.clone();
// Run blur algorithm
let blurred_image = if radius < 0.1 {
@ -34,11 +32,11 @@ async fn blur(
gaussian_blur_algorithm(image, radius, gamma)
};
let mut result = ImageFrameTable::new(blurred_image);
*result.transform_mut() = image_frame_transform;
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
result
image_instance.instance = blurred_image;
image_instance.source_node_id = None;
result_table.push(image_instance);
}
result_table
}
// 1D gaussian kernel

View File

@ -5,8 +5,6 @@ use graph_craft::document::*;
use graph_craft::proto::*;
use graphene_core::raster::BlendMode;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::transform::Transform;
use graphene_core::transform::TransformMut;
use graphene_core::*;
use std::sync::Arc;
use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, WgpuExecutor};
@ -37,13 +35,13 @@ async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context
#[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_transform = foreground.transform();
let background_transform = background.transform();
let mut result_table = ImageFrameTable::empty();
for (foreground_instance, mut background_instance) in foreground.instance_iter().zip(background.instance_iter()) {
let foreground_transform = foreground_instance.transform;
let background_transform = background_instance.transform;
let background_alpha_blending = background.one_instance_ref().alpha_blending;
let foreground = foreground.one_instance_ref().instance;
let background = background.one_instance_ref().instance;
let foreground = foreground_instance.instance;
let background = background_instance.instance;
let foreground_size = DVec2::new(foreground.width as f64, foreground.height as f64);
let background_size = DVec2::new(background.width as f64, background.height as f64);
@ -213,11 +211,11 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
..Default::default()
};
let mut result = ImageFrameTable::new(created_image);
*result.transform_mut() = background_transform;
*result.one_instance_mut().alpha_blending = *background_alpha_blending;
result
background_instance.instance = created_image;
background_instance.source_node_id = None;
result_table.push(background_instance);
}
result_table
}
// struct ComputePass {

View File

@ -16,9 +16,8 @@ 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_instance_ref().instance;
for pixel in image.data.iter() {
for image_instance in image.instance_ref_iter() {
for pixel in image_instance.instance.data.iter() {
let r = pixel.r() * GRID;
let g = pixel.g() * GRID;
let b = pixel.b() * GRID;
@ -28,6 +27,7 @@ async fn image_color_palette(
histogram[bin] += 1;
colors[bin].push(pixel.to_gamma_srgb());
}
}
let shorted = histogram.iter().enumerate().filter(|&(_, &count)| count > 0).map(|(i, _)| i).collect::<Vec<usize>>();

View File

@ -27,10 +27,11 @@ 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_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance_ref().alpha_blending;
let mut result_table = ImageFrameTable::empty();
let image = image_frame.one_instance_ref().instance;
for mut image_frame_instance in image_frame.instance_iter() {
let image_frame_transform = image_frame_instance.transform;
let image = image_frame_instance.instance;
// Resize the image using the image crate
let data = bytemuck::cast_vec(image.data.clone());
@ -43,9 +44,9 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
let size = intersection.size();
let size_px = image_size.transform_vector2(size).as_uvec2();
// If the image would not be visible, return an empty image
// If the image would not be visible, add nothing.
if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::one_empty_image();
continue;
}
let image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
@ -82,11 +83,18 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = new_transform;
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
image_frame_instance.transform = new_transform;
image_frame_instance.source_node_id = None;
image_frame_instance.instance = image;
result_table.push(image_frame_instance)
}
result
// TODO: Remove when we've completed part 6 of the instance tables refactor
if result_table.is_empty() {
return ImageFrameTable::one_empty_image();
}
result_table
}
#[node_macro::node(category("Raster"))]
@ -251,19 +259,27 @@ where
#[node_macro::node(category(""))]
fn extend_image_to_bounds(_: impl Ctx, image: ImageFrameTable<Color>, bounds: DAffine2) -> ImageFrameTable<Color> {
let image_aabb = Bbox::unit().affine_transform(image.transform()).to_axis_aligned_bbox();
let mut result_table = ImageFrameTable::empty();
for mut image_instance in image.instance_iter() {
let image_aabb = Bbox::unit().affine_transform(image_instance.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;
result_table.push(image_instance);
continue;
}
let image_instance = image.one_instance_ref().instance;
if image_instance.width == 0 || image_instance.height == 0 {
return empty_image((), bounds, Color::TRANSPARENT);
let image_data = image_instance.instance.data;
let (image_width, image_height) = (image_instance.instance.width, image_instance.instance.height);
if image_width == 0 || image_height == 0 {
for image_instance in empty_image((), bounds, Color::TRANSPARENT).instance_iter() {
result_table.push(image_instance);
}
continue;
}
let orig_image_scale = DVec2::new(image_instance.width as f64, image_instance.height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse();
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image_instance.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);
@ -271,25 +287,27 @@ fn extend_image_to_bounds(_: impl Ctx, image: ImageFrameTable<Color>, bounds: DA
let new_scale = new_end - new_start;
// 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 mut new_image = 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_instance.height {
let old_start = y * image_instance.width;
let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x;
let old_row = &image_instance.data[old_start as usize..(old_start + image_instance.width) as usize];
let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.width) as usize];
for y in 0..image_height {
let old_start = y * image_width;
let new_start = (y + offset_in_new_image.y) * new_image.width + offset_in_new_image.x;
let old_row = &image_data[old_start as usize..(old_start + image_width) as usize];
let new_row = &mut new_image.data[new_start as usize..(new_start + 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);
let new_texture_to_layer_space = image_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
let mut result = ImageFrameTable::new(new_img);
*result.transform_mut() = new_texture_to_layer_space;
*result.one_instance_mut().alpha_blending = *image.one_instance_ref().alpha_blending;
image_instance.instance = new_image;
image_instance.transform = new_texture_to_layer_space;
image_instance.source_node_id = None;
result_table.push(image_instance);
}
result
result_table
}
#[node_macro::node(category("Debug: Raster"))]
@ -299,11 +317,13 @@ fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTabl
let image = Image::new(width, height, color);
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = transform;
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
let mut result_table = ImageFrameTable::new(image);
let image_instance = result_table.get_mut(0).unwrap();
*image_instance.transform = transform;
*image_instance.alpha_blending = AlphaBlending::default();
result
// Callers of empty_image can safely unwrap on returned table
result_table
}
/// Constructs a raster image.

View File

@ -30,9 +30,10 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
#[node_macro::node(category("Debug: GPU"))]
async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
return Arc::new(editor.application_io.as_ref().unwrap().create_window());
Arc::new(editor.application_io.as_ref().unwrap().create_window())
}
// TODO: Fix and reenable in order to get the 'Draw Canvas' node working again.
// #[cfg(target_arch = "wasm32")]
// use wasm_bindgen::Clamped;
//

View File

@ -27,16 +27,17 @@ use wgpu_executor::{WgpuSurface, WindowHandle};
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: f64, to: f64),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
into_node!(from: f64, to: f64),
into_node!(from: u32, to: f64),
into_node!(from: u8, to: u32),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: VectorDataTable, to: VectorDataTable),
into_node!(from: VectorDataTable, to: GraphicElement),
into_node!(from: ImageFrameTable<Color>, to: GraphicElement),
into_node!(from: GraphicGroupTable, to: GraphicElement),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
into_node!(from: GraphicGroupTable, to: GraphicGroupTable),
into_node!(from: GraphicGroupTable, to: GraphicElement),
into_node!(from: ImageFrameTable<Color>, to: ImageFrameTable<Color>),
into_node!(from: ImageFrameTable<Color>, to: ImageFrameTable<SRGBA8>),
into_node!(from: ImageFrameTable<Color>, to: GraphicElement),
into_node!(from: ImageFrameTable<Color>, to: GraphicGroupTable),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable<Color>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),