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:
parent
fbefa5b827
commit
4d2e1d57fd
|
|
@ -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()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,56 +312,56 @@ 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]);
|
||||
|
||||
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());
|
||||
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 do_scale = random_scale_difference.abs() > 1e-6;
|
||||
let do_rotation = random_rotation.abs() > 1e-6;
|
||||
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let points_transform = point_instance.transform;
|
||||
for &point in point_instance.instance.point_domain.positions() {
|
||||
let center_transform = DAffine2::from_translation(instance_center);
|
||||
|
||||
for &point in points_list.into_iter() {
|
||||
let center_transform = DAffine2::from_translation(instance_center);
|
||||
let translation = points_transform.transform_point2(point);
|
||||
|
||||
let translation = points_transform.transform_point2(point);
|
||||
|
||||
let rotation = if do_rotation {
|
||||
let degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation;
|
||||
degrees / 360. * std::f64::consts::TAU
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
let scale = if do_scale {
|
||||
if random_scale_bias.abs() < 1e-6 {
|
||||
// Linear
|
||||
random_scale_min + scale_rng.random::<f64>() * random_scale_difference
|
||||
let rotation = if do_rotation {
|
||||
let degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation;
|
||||
degrees / 360. * std::f64::consts::TAU
|
||||
} else {
|
||||
// Weighted (see <https://www.desmos.com/calculator/gmavd3m9bd>)
|
||||
let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias);
|
||||
let scale_factor = (1. - scale_rng.random::<f64>() * horizontal_scale_factor).log2() / random_scale_bias;
|
||||
random_scale_min + scale_factor * random_scale_difference
|
||||
}
|
||||
} else {
|
||||
random_scale_min
|
||||
};
|
||||
0.
|
||||
};
|
||||
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
|
||||
let scale = if do_scale {
|
||||
if random_scale_bias.abs() < 1e-6 {
|
||||
// Linear
|
||||
random_scale_min + scale_rng.random::<f64>() * random_scale_difference
|
||||
} else {
|
||||
// Weighted (see <https://www.desmos.com/calculator/gmavd3m9bd>)
|
||||
let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias);
|
||||
let scale_factor = (1. - scale_rng.random::<f64>() * horizontal_scale_factor).log2() / random_scale_bias;
|
||||
random_scale_min + scale_factor * random_scale_difference
|
||||
}
|
||||
} else {
|
||||
random_scale_min
|
||||
};
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
|
||||
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
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,181 +1417,193 @@ 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();
|
||||
|
||||
// Exit early if there are no points to generate splines from.
|
||||
if vector_data.point_domain.positions().is_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_instance.instance.point_domain.positions().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut segment_domain = SegmentDomain::default();
|
||||
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;
|
||||
|
||||
// Compute control point handles for Bezier spline.
|
||||
let first_handles = if closed {
|
||||
bezier_rs::solve_spline_first_handle_closed(&positions)
|
||||
} else {
|
||||
bezier_rs::solve_spline_first_handle_open(&positions)
|
||||
};
|
||||
|
||||
let stroke_id = StrokeId::ZERO;
|
||||
|
||||
// Create segments with computed Bezier handles and add them to vector data.
|
||||
for i in 0..(positions.len() - if closed { 0 } else { 1 }) {
|
||||
let next_index = (i + 1) % positions.len();
|
||||
|
||||
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];
|
||||
let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end };
|
||||
|
||||
segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
let mut segment_domain = SegmentDomain::default();
|
||||
for subpath in vector_data.stroke_bezier_paths() {
|
||||
let positions = subpath.manipulator_groups().iter().map(|group| group.anchor).collect::<Vec<_>>();
|
||||
let closed = subpath.closed() && positions.len() > 2;
|
||||
|
||||
// Compute control point handles for Bezier spline.
|
||||
let first_handles = if closed {
|
||||
bezier_rs::solve_spline_first_handle_closed(&positions)
|
||||
} else {
|
||||
bezier_rs::solve_spline_first_handle_open(&positions)
|
||||
};
|
||||
|
||||
let stroke_id = StrokeId::ZERO;
|
||||
|
||||
// Create segments with computed Bezier handles and add them to vector data.
|
||||
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 handle_start = first_handles[i];
|
||||
let handle_end = positions[next_index] * 2. - first_handles[next_index];
|
||||
let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end };
|
||||
|
||||
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
|
||||
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 mut result_table = VectorDataTable::empty();
|
||||
|
||||
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
|
||||
for mut vector_data_instance in vector_data.instance_iter() {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
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.point_domain.positions().len())
|
||||
.map(|_| {
|
||||
let angle = rng.random::<f64>() * std::f64::consts::TAU;
|
||||
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()];
|
||||
inverse_transform.transform_vector2(DVec2::from_angle(angle) * rng.random::<f64>() * amount)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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() {
|
||||
let start_delta = deltas[*start];
|
||||
let end_delta = deltas[*end];
|
||||
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);
|
||||
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);
|
||||
already_applied[*end] = true;
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
*handle_start += start_delta;
|
||||
*handle_end += end_delta;
|
||||
if !already_applied[*start] {
|
||||
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;
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
*handle = vector_data_transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
|
||||
if !already_applied[*end] {
|
||||
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;
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
*handle_start += start_delta;
|
||||
*handle_end += end_delta;
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
*handle = vector_data_instance.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
|
||||
}
|
||||
bezier_rs::BezierHandles::Linear => {}
|
||||
}
|
||||
bezier_rs::BezierHandles::Linear => {}
|
||||
}
|
||||
|
||||
vector_data_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_table.push(vector_data_instance);
|
||||
}
|
||||
|
||||
vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
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();
|
||||
|
||||
// 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);
|
||||
for (source_instance, target_instance) in source.instance_iter().zip(target.instance_iter()) {
|
||||
let mut vector_data_instance = VectorData::default();
|
||||
|
||||
// Before and after transforms
|
||||
let source_transform = *source.one_instance_ref().transform;
|
||||
let target_transform = *target.one_instance_ref().transform;
|
||||
// Lerp styles
|
||||
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 paths
|
||||
let source_paths = source.one_instance_mut().instance.stroke_bezier_paths();
|
||||
let target_paths = target.one_instance_mut().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);
|
||||
// Before and after transforms
|
||||
let source_transform = source_instance.transform;
|
||||
let target_transform = target_instance.transform;
|
||||
|
||||
// Align point counts by inserting mid‐segment points until their counts match
|
||||
while source_path.manipulator_groups().len() < target_path.manipulator_groups().len() {
|
||||
let last = source_path.len() - 1;
|
||||
source_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 });
|
||||
}
|
||||
while target_path.manipulator_groups().len() < source_path.manipulator_groups().len() {
|
||||
let last = target_path.len() - 1;
|
||||
target_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 });
|
||||
// Before and after 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);
|
||||
|
||||
// Align point counts by inserting mid‐segment points until their counts match
|
||||
while source_path.manipulator_groups().len() < target_path.manipulator_groups().len() {
|
||||
let last = source_path.len() - 1;
|
||||
source_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 });
|
||||
}
|
||||
while target_path.manipulator_groups().len() < source_path.manipulator_groups().len() {
|
||||
let last = target_path.len() - 1;
|
||||
target_path.insert(SubpathTValue::Parametric { segment_index: last, t: 0.5 });
|
||||
}
|
||||
|
||||
// Interpolate anchors and handles
|
||||
for (source_manipulators, target_manipulators) in source_path.manipulator_groups_mut().iter_mut().zip(target_path.manipulator_groups()) {
|
||||
let source_anchor = source_manipulators.anchor;
|
||||
let target_anchor = target_manipulators.anchor;
|
||||
source_manipulators.anchor = source_anchor.lerp(target_anchor, time);
|
||||
|
||||
let source_in_handle = source_manipulators.in_handle.unwrap_or(source_anchor);
|
||||
let target_in_handle = target_manipulators.in_handle.unwrap_or(target_anchor);
|
||||
source_manipulators.in_handle = Some(source_in_handle.lerp(target_in_handle, time));
|
||||
|
||||
let source_out_handle = source_manipulators.out_handle.unwrap_or(source_anchor);
|
||||
let target_out_handle = target_manipulators.out_handle.unwrap_or(target_anchor);
|
||||
source_manipulators.out_handle = Some(source_out_handle.lerp(target_out_handle, time));
|
||||
}
|
||||
|
||||
vector_data_instance.append_subpath(source_path.clone(), true);
|
||||
}
|
||||
|
||||
// Interpolate anchors and handles
|
||||
for (source_manipulators, target_manipulators) in source_path.manipulator_groups_mut().iter_mut().zip(target_path.manipulator_groups()) {
|
||||
let source_anchor = source_manipulators.anchor;
|
||||
let target_anchor = target_manipulators.anchor;
|
||||
source_manipulators.anchor = source_anchor.lerp(target_anchor, time);
|
||||
// Deal with unmatched extra paths by collapsing them
|
||||
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);
|
||||
|
||||
let source_in_handle = source_manipulators.in_handle.unwrap_or(source_anchor);
|
||||
let target_in_handle = target_manipulators.in_handle.unwrap_or(target_anchor);
|
||||
source_manipulators.in_handle = Some(source_in_handle.lerp(target_in_handle, time));
|
||||
|
||||
let source_out_handle = source_manipulators.out_handle.unwrap_or(source_anchor);
|
||||
let target_out_handle = target_manipulators.out_handle.unwrap_or(target_anchor);
|
||||
source_manipulators.out_handle = Some(source_out_handle.lerp(target_out_handle, time));
|
||||
for mut source_path in source_paths {
|
||||
source_path.apply_transform(source_transform);
|
||||
let end = source_path.manipulator_groups().last().map(|group| group.anchor).unwrap_or_default();
|
||||
for group in source_path.manipulator_groups_mut() {
|
||||
group.anchor = group.anchor.lerp(end, time);
|
||||
group.in_handle = group.in_handle.map(|handle| handle.lerp(end, time));
|
||||
group.out_handle = group.out_handle.map(|handle| handle.lerp(end, time));
|
||||
}
|
||||
vector_data_instance.append_subpath(source_path, true);
|
||||
}
|
||||
for mut target_path in target_paths {
|
||||
target_path.apply_transform(target_transform);
|
||||
let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
|
||||
for group in target_path.manipulator_groups_mut() {
|
||||
group.anchor = start.lerp(group.anchor, time);
|
||||
group.in_handle = group.in_handle.map(|handle| start.lerp(handle, time));
|
||||
group.out_handle = group.out_handle.map(|handle| start.lerp(handle, time));
|
||||
}
|
||||
vector_data_instance.append_subpath(target_path, true);
|
||||
}
|
||||
|
||||
result_table.one_instance_mut().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);
|
||||
|
||||
for mut source_path in source_paths {
|
||||
source_path.apply_transform(source_transform);
|
||||
let end = source_path.manipulator_groups().last().map(|group| group.anchor).unwrap_or_default();
|
||||
for group in source_path.manipulator_groups_mut() {
|
||||
group.anchor = group.anchor.lerp(end, time);
|
||||
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);
|
||||
}
|
||||
for mut target_path in target_paths {
|
||||
target_path.apply_transform(target_transform);
|
||||
let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
|
||||
for group in target_path.manipulator_groups_mut() {
|
||||
group.anchor = start.lerp(group.anchor, time);
|
||||
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);
|
||||
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.;
|
||||
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;
|
||||
}
|
||||
area += subpath_area;
|
||||
centroid += subpath_area * subpath_centroid;
|
||||
// All subpath centroid positions added together as if they were vectors from the origin.
|
||||
let mut centroid = DVec2::ZERO;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
if area != 0. {
|
||||
centroid /= area;
|
||||
return vector_data_transform.transform_point2(centroid);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if sum > 0. {
|
||||
centroid / sum
|
||||
}
|
||||
// Without a summed denominator, return the average of all positions instead
|
||||
else {
|
||||
let mut count: usize = 0;
|
||||
|
||||
if length != 0. {
|
||||
centroid /= length;
|
||||
return vector_data_transform.transform_point2(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 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;
|
||||
|
|
|
|||
|
|
@ -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,39 +94,41 @@ where
|
|||
return target;
|
||||
}
|
||||
|
||||
let target_width = target.one_instance_ref().instance.width;
|
||||
let target_height = target.one_instance_ref().instance.height;
|
||||
let target_size = DVec2::new(target_width as f64, target_height as f64);
|
||||
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 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();
|
||||
let stop = start + texture_size;
|
||||
for position in &positions {
|
||||
let start = document_to_target.transform_point2(*position).round();
|
||||
let stop = start + texture_size;
|
||||
|
||||
// Half-open integer ranges [start, stop).
|
||||
let clamp_start = start.clamp(DVec2::ZERO, target_size).as_uvec2();
|
||||
let clamp_stop = stop.clamp(DVec2::ZERO, target_size).as_uvec2();
|
||||
// Half-open integer ranges [start, stop).
|
||||
let clamp_start = start.clamp(DVec2::ZERO, target_size).as_uvec2();
|
||||
let clamp_stop = stop.clamp(DVec2::ZERO, target_size).as_uvec2();
|
||||
|
||||
let blit_area_offset = (clamp_start.as_dvec2() - start).as_uvec2().min(texture_size.as_uvec2());
|
||||
let blit_area_dimensions = (clamp_stop - clamp_start).min(texture_size.as_uvec2() - blit_area_offset);
|
||||
let blit_area_offset = (clamp_start.as_dvec2() - start).as_uvec2().min(texture_size.as_uvec2());
|
||||
let blit_area_dimensions = (clamp_stop - clamp_start).min(texture_size.as_uvec2() - blit_area_offset);
|
||||
|
||||
// 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_width as usize) + (x as usize) };
|
||||
// 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_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.one_instance_ref().instance.data.len());
|
||||
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_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)];
|
||||
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
|
||||
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_instance.instance.data[target_index(x + clamp_start.x, y + clamp_start.y)];
|
||||
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,34 +7,34 @@ 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 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 = 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.");
|
||||
let dynamic_image: image::DynamicImage = image_buffer.into();
|
||||
|
||||
// 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.");
|
||||
let dynamic_image: image::DynamicImage = image_buffer.into();
|
||||
// Run the dehaze algorithm
|
||||
let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.);
|
||||
|
||||
// Run the dehaze algorithm
|
||||
let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.);
|
||||
// Prepare the image data for returning
|
||||
let buffer = dehazed_dynamic_image.to_rgba32f().into_raw();
|
||||
let color_vec = bytemuck::cast_vec(buffer);
|
||||
let dehazed_image = Image {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
data: color_vec,
|
||||
base64_string: None,
|
||||
};
|
||||
|
||||
// Prepare the image data for returning
|
||||
let buffer = dehazed_dynamic_image.to_rgba32f().into_raw();
|
||||
let color_vec = bytemuck::cast_vec(buffer);
|
||||
let dehazed_image = Image {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
data: color_vec,
|
||||
base64_string: None,
|
||||
};
|
||||
image_frame_instance.instance = dehazed_image;
|
||||
image_frame_instance.source_node_id = None;
|
||||
result_table.push(image_frame_instance);
|
||||
}
|
||||
|
||||
let mut result = ImageFrameTable::new(dehazed_image);
|
||||
*result.transform_mut() = image_frame_transform;
|
||||
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
|
||||
|
||||
result
|
||||
result_table
|
||||
}
|
||||
|
||||
// There is no real point in modifying these values because they do not change the final result all that much.
|
||||
|
|
|
|||
|
|
@ -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,26 +18,25 @@ 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 mut result_table = ImageFrameTable::empty();
|
||||
for mut image_instance in image_frame.instance_iter() {
|
||||
let image = image_instance.instance.clone();
|
||||
|
||||
let image = image_frame.one_instance_ref().instance.clone();
|
||||
// Run blur algorithm
|
||||
let blurred_image = if radius < 0.1 {
|
||||
// Minimum blur radius
|
||||
image.clone()
|
||||
} else if box_blur {
|
||||
box_blur_algorithm(image, radius, gamma)
|
||||
} else {
|
||||
gaussian_blur_algorithm(image, radius, gamma)
|
||||
};
|
||||
|
||||
// Run blur algorithm
|
||||
let blurred_image = if radius < 0.1 {
|
||||
// Minimum blur radius
|
||||
image.clone()
|
||||
} else if box_blur {
|
||||
box_blur_algorithm(image, radius, gamma)
|
||||
} else {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,32 +35,32 @@ 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_instance.instance;
|
||||
let background = background_instance.instance;
|
||||
|
||||
let foreground = foreground.one_instance_ref().instance;
|
||||
let background = background.one_instance_ref().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);
|
||||
|
||||
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);
|
||||
// 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);
|
||||
|
||||
// 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 transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2();
|
||||
let translation: Vec2 = bg_to_fg.translation.as_vec2();
|
||||
|
||||
let transform_matrix: Mat2 = bg_to_fg.matrix2.as_mat2();
|
||||
let translation: Vec2 = bg_to_fg.translation.as_vec2();
|
||||
log::debug!("Executing gpu blend node!");
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
|
||||
log::debug!("Executing gpu blend node!");
|
||||
let compiler = graph_craft::graphene_compiler::Compiler {};
|
||||
|
||||
let network = NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(0), 0)],
|
||||
nodes: [DocumentNode {
|
||||
inputs: vec![NodeInput::Inline(InlineRust::new(
|
||||
format!(
|
||||
r#"graphene_core::raster::adjustments::BlendNode::new(
|
||||
let network = NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(0), 0)],
|
||||
nodes: [DocumentNode {
|
||||
inputs: vec![NodeInput::Inline(InlineRust::new(
|
||||
format!(
|
||||
r#"graphene_core::raster::adjustments::BlendNode::new(
|
||||
graphene_core::value::CopiedNode::new({}),
|
||||
graphene_core::value::CopiedNode::new({}),
|
||||
).eval((
|
||||
|
|
@ -78,146 +76,146 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
|
|||
}},
|
||||
i1[(_global_index.y * i0 + _global_index.x) as usize],
|
||||
))"#,
|
||||
TaggedValue::BlendMode(blend_mode).to_primitive_string(),
|
||||
TaggedValue::F64(opacity).to_primitive_string(),
|
||||
),
|
||||
concrete![Color],
|
||||
))],
|
||||
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
|
||||
TaggedValue::BlendMode(blend_mode).to_primitive_string(),
|
||||
TaggedValue::F64(opacity).to_primitive_string(),
|
||||
),
|
||||
concrete![Color],
|
||||
))],
|
||||
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
|
||||
..Default::default()
|
||||
}]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
log::debug!("compiling network");
|
||||
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::one_empty_image();
|
||||
};
|
||||
let proto_networks = proto_networks_result;
|
||||
log::debug!("compiling shader");
|
||||
};
|
||||
log::debug!("compiling network");
|
||||
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::one_empty_image();
|
||||
};
|
||||
let proto_networks = proto_networks_result;
|
||||
log::debug!("compiling shader");
|
||||
|
||||
let shader = compilation_client::compile(
|
||||
proto_networks,
|
||||
vec![
|
||||
concrete!(u32),
|
||||
concrete!(Color),
|
||||
concrete!(Color),
|
||||
concrete!(u32),
|
||||
concrete_with_name!(Mat2, "Mat2"),
|
||||
concrete_with_name!(Vec2, "Vec2"),
|
||||
],
|
||||
vec![concrete!(Color)],
|
||||
ShaderIO {
|
||||
inputs: vec![
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)), // width of the output image
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)), // background image
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)), // foreground image
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)), // width of the foreground image
|
||||
ShaderInput::UniformBuffer((), concrete_with_name!(Mat2, "Mat2")), // bg_to_fg.matrix2
|
||||
ShaderInput::UniformBuffer((), concrete_with_name!(Vec2, "Vec2")), // bg_to_fg.translation
|
||||
ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
let shader = compilation_client::compile(
|
||||
proto_networks,
|
||||
vec![
|
||||
concrete!(u32),
|
||||
concrete!(Color),
|
||||
concrete!(Color),
|
||||
concrete!(u32),
|
||||
concrete_with_name!(Mat2, "Mat2"),
|
||||
concrete_with_name!(Vec2, "Vec2"),
|
||||
],
|
||||
output: ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let len = background.data.len();
|
||||
|
||||
let executor = WgpuExecutor::new()
|
||||
vec![concrete!(Color)],
|
||||
ShaderIO {
|
||||
inputs: vec![
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)), // width of the output image
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)), // background image
|
||||
ShaderInput::StorageBuffer((), concrete!(Color)), // foreground image
|
||||
ShaderInput::UniformBuffer((), concrete!(u32)), // width of the foreground image
|
||||
ShaderInput::UniformBuffer((), concrete_with_name!(Mat2, "Mat2")), // bg_to_fg.matrix2
|
||||
ShaderInput::UniformBuffer((), concrete_with_name!(Vec2, "Vec2")), // bg_to_fg.translation
|
||||
ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
],
|
||||
output: ShaderInput::OutputBuffer((), concrete!(Color)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser.");
|
||||
log::debug!("creating buffer");
|
||||
let width_uniform = executor.create_uniform_buffer(background.width).unwrap();
|
||||
let bg_storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
background.data.clone(),
|
||||
StorageBufferOptions {
|
||||
cpu_writable: false,
|
||||
gpu_writable: true,
|
||||
cpu_readable: false,
|
||||
storage: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let fg_storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
foreground.data.clone(),
|
||||
StorageBufferOptions {
|
||||
cpu_writable: false,
|
||||
gpu_writable: true,
|
||||
cpu_readable: false,
|
||||
storage: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let fg_width_uniform = executor.create_uniform_buffer(foreground.width).unwrap();
|
||||
let transform_uniform = executor.create_uniform_buffer(transform_matrix).unwrap();
|
||||
let translation_uniform = executor.create_uniform_buffer(translation).unwrap();
|
||||
let width_uniform = Arc::new(width_uniform);
|
||||
let bg_storage_buffer = Arc::new(bg_storage_buffer);
|
||||
let fg_storage_buffer = Arc::new(fg_storage_buffer);
|
||||
let fg_width_uniform = Arc::new(fg_width_uniform);
|
||||
let transform_uniform = Arc::new(transform_uniform);
|
||||
let translation_uniform = Arc::new(translation_uniform);
|
||||
let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap();
|
||||
let output_buffer = Arc::new(output_buffer);
|
||||
let readback_buffer = executor.create_output_buffer(len, concrete!(Color), true).unwrap();
|
||||
let readback_buffer = Arc::new(readback_buffer);
|
||||
log::debug!("created buffer");
|
||||
let bind_group = Bindgroup {
|
||||
buffers: vec![
|
||||
width_uniform.clone(),
|
||||
bg_storage_buffer.clone(),
|
||||
fg_storage_buffer.clone(),
|
||||
fg_width_uniform.clone(),
|
||||
transform_uniform.clone(),
|
||||
translation_uniform.clone(),
|
||||
],
|
||||
};
|
||||
let len = background.data.len();
|
||||
|
||||
let shader = Shader {
|
||||
source: shader.spirv_binary.into(),
|
||||
name: "gpu::eval",
|
||||
io: shader.io,
|
||||
};
|
||||
log::debug!("loading shader");
|
||||
log::debug!("shader: {:?}", shader.source);
|
||||
let shader = executor.load_shader(shader).unwrap();
|
||||
log::debug!("loaded shader");
|
||||
let pipeline = PipelineLayout {
|
||||
shader: shader.into(),
|
||||
entry_point: "eval".to_string(),
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer: output_buffer.clone(),
|
||||
};
|
||||
log::debug!("created pipeline");
|
||||
let compute_pass = executor
|
||||
.create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(background.width, background.height))
|
||||
.unwrap();
|
||||
executor.execute_compute_pipeline(compute_pass).unwrap();
|
||||
log::debug!("executed pipeline");
|
||||
log::debug!("reading buffer");
|
||||
let result = executor.read_output_buffer(readback_buffer).await.unwrap();
|
||||
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
|
||||
let executor = WgpuExecutor::new()
|
||||
.await
|
||||
.expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser.");
|
||||
log::debug!("creating buffer");
|
||||
let width_uniform = executor.create_uniform_buffer(background.width).unwrap();
|
||||
let bg_storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
background.data.clone(),
|
||||
StorageBufferOptions {
|
||||
cpu_writable: false,
|
||||
gpu_writable: true,
|
||||
cpu_readable: false,
|
||||
storage: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let fg_storage_buffer = executor
|
||||
.create_storage_buffer(
|
||||
foreground.data.clone(),
|
||||
StorageBufferOptions {
|
||||
cpu_writable: false,
|
||||
gpu_writable: true,
|
||||
cpu_readable: false,
|
||||
storage: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let fg_width_uniform = executor.create_uniform_buffer(foreground.width).unwrap();
|
||||
let transform_uniform = executor.create_uniform_buffer(transform_matrix).unwrap();
|
||||
let translation_uniform = executor.create_uniform_buffer(translation).unwrap();
|
||||
let width_uniform = Arc::new(width_uniform);
|
||||
let bg_storage_buffer = Arc::new(bg_storage_buffer);
|
||||
let fg_storage_buffer = Arc::new(fg_storage_buffer);
|
||||
let fg_width_uniform = Arc::new(fg_width_uniform);
|
||||
let transform_uniform = Arc::new(transform_uniform);
|
||||
let translation_uniform = Arc::new(translation_uniform);
|
||||
let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap();
|
||||
let output_buffer = Arc::new(output_buffer);
|
||||
let readback_buffer = executor.create_output_buffer(len, concrete!(Color), true).unwrap();
|
||||
let readback_buffer = Arc::new(readback_buffer);
|
||||
log::debug!("created buffer");
|
||||
let bind_group = Bindgroup {
|
||||
buffers: vec![
|
||||
width_uniform.clone(),
|
||||
bg_storage_buffer.clone(),
|
||||
fg_storage_buffer.clone(),
|
||||
fg_width_uniform.clone(),
|
||||
transform_uniform.clone(),
|
||||
translation_uniform.clone(),
|
||||
],
|
||||
};
|
||||
|
||||
let created_image = Image {
|
||||
data: colors,
|
||||
width: background.width,
|
||||
height: background.height,
|
||||
..Default::default()
|
||||
};
|
||||
let shader = Shader {
|
||||
source: shader.spirv_binary.into(),
|
||||
name: "gpu::eval",
|
||||
io: shader.io,
|
||||
};
|
||||
log::debug!("loading shader");
|
||||
log::debug!("shader: {:?}", shader.source);
|
||||
let shader = executor.load_shader(shader).unwrap();
|
||||
log::debug!("loaded shader");
|
||||
let pipeline = PipelineLayout {
|
||||
shader: shader.into(),
|
||||
entry_point: "eval".to_string(),
|
||||
bind_group: bind_group.into(),
|
||||
output_buffer: output_buffer.clone(),
|
||||
};
|
||||
log::debug!("created pipeline");
|
||||
let compute_pass = executor
|
||||
.create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(background.width, background.height))
|
||||
.unwrap();
|
||||
executor.execute_compute_pipeline(compute_pass).unwrap();
|
||||
log::debug!("executed pipeline");
|
||||
log::debug!("reading buffer");
|
||||
let result = executor.read_output_buffer(readback_buffer).await.unwrap();
|
||||
let colors = bytemuck::pod_collect_to_vec::<u8, Color>(result.as_slice());
|
||||
|
||||
let mut result = ImageFrameTable::new(created_image);
|
||||
*result.transform_mut() = background_transform;
|
||||
*result.one_instance_mut().alpha_blending = *background_alpha_blending;
|
||||
let created_image = Image {
|
||||
data: colors,
|
||||
width: background.width,
|
||||
height: background.height,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
result
|
||||
background_instance.instance = created_image;
|
||||
background_instance.source_node_id = None;
|
||||
result_table.push(background_instance);
|
||||
}
|
||||
result_table
|
||||
}
|
||||
|
||||
// struct ComputePass {
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ 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 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;
|
||||
|
||||
for pixel in image.data.iter() {
|
||||
let r = pixel.r() * GRID;
|
||||
let g = pixel.g() * GRID;
|
||||
let b = pixel.b() * GRID;
|
||||
let bin = (r * GRID + g * GRID + b * GRID) as usize;
|
||||
|
||||
let bin = (r * GRID + g * GRID + b * GRID) as usize;
|
||||
|
||||
histogram[bin] += 1;
|
||||
colors[bin].push(pixel.to_gamma_srgb());
|
||||
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>>();
|
||||
|
|
|
|||
|
|
@ -27,66 +27,74 @@ 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());
|
||||
// Resize the image using the image crate
|
||||
let data = bytemuck::cast_vec(image.data.clone());
|
||||
|
||||
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 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();
|
||||
let size_px = image_size.transform_vector2(size).as_uvec2();
|
||||
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 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();
|
||||
let size_px = image_size.transform_vector2(size).as_uvec2();
|
||||
|
||||
// If the image would not be visible, return an empty image
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
// If the image would not be visible, add nothing.
|
||||
if size.x <= 0. || size.y <= 0. {
|
||||
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.");
|
||||
|
||||
let dynamic_image: ::image::DynamicImage = image_buffer.into();
|
||||
let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);
|
||||
let offset_px = image_size.transform_vector2(offset).as_uvec2();
|
||||
let cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y);
|
||||
|
||||
let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length();
|
||||
let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length();
|
||||
let mut new_width = size_px.x;
|
||||
let mut new_height = size_px.y;
|
||||
|
||||
// Only downscale the image for now
|
||||
let resized = if new_width < image.width || new_height < image.height {
|
||||
new_width = viewport_resolution_x as u32;
|
||||
new_height = viewport_resolution_y as u32;
|
||||
// TODO: choose filter based on quality requirements
|
||||
cropped.resize_exact(new_width, new_height, ::image::imageops::Triangle)
|
||||
} else {
|
||||
cropped
|
||||
};
|
||||
let buffer = resized.to_rgba32f();
|
||||
let buffer = buffer.into_raw();
|
||||
let vec = bytemuck::cast_vec(buffer);
|
||||
let image = Image {
|
||||
width: new_width,
|
||||
height: new_height,
|
||||
data: vec,
|
||||
base64_string: None,
|
||||
};
|
||||
// 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);
|
||||
|
||||
image_frame_instance.transform = new_transform;
|
||||
image_frame_instance.source_node_id = None;
|
||||
image_frame_instance.instance = image;
|
||||
result_table.push(image_frame_instance)
|
||||
}
|
||||
|
||||
// TODO: Remove when we've completed part 6 of the instance tables refactor
|
||||
if result_table.is_empty() {
|
||||
return ImageFrameTable::one_empty_image();
|
||||
}
|
||||
|
||||
let image_buffer = ::image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
|
||||
|
||||
let dynamic_image: ::image::DynamicImage = image_buffer.into();
|
||||
let offset = (intersection.start - image_bounds.start).max(DVec2::ZERO);
|
||||
let offset_px = image_size.transform_vector2(offset).as_uvec2();
|
||||
let cropped = dynamic_image.crop_imm(offset_px.x, offset_px.y, size_px.x, size_px.y);
|
||||
|
||||
let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length();
|
||||
let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length();
|
||||
let mut new_width = size_px.x;
|
||||
let mut new_height = size_px.y;
|
||||
|
||||
// Only downscale the image for now
|
||||
let resized = if new_width < image.width || new_height < image.height {
|
||||
new_width = viewport_resolution_x as u32;
|
||||
new_height = viewport_resolution_y as u32;
|
||||
// TODO: choose filter based on quality requirements
|
||||
cropped.resize_exact(new_width, new_height, ::image::imageops::Triangle)
|
||||
} else {
|
||||
cropped
|
||||
};
|
||||
let buffer = resized.to_rgba32f();
|
||||
let buffer = buffer.into_raw();
|
||||
let vec = bytemuck::cast_vec(buffer);
|
||||
let image = Image {
|
||||
width: new_width,
|
||||
height: new_height,
|
||||
data: vec,
|
||||
base64_string: None,
|
||||
};
|
||||
// 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 mut result = ImageFrameTable::new(image);
|
||||
*result.transform_mut() = new_transform;
|
||||
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
|
||||
|
||||
result
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
|
|
@ -251,45 +259,55 @@ 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 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;
|
||||
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) {
|
||||
result_table.push(image_instance);
|
||||
continue;
|
||||
}
|
||||
|
||||
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_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);
|
||||
let new_end = bounds_in_image_space.end.ceil().max(orig_image_scale);
|
||||
let new_scale = new_end - new_start;
|
||||
|
||||
// Copy over original image into enlarged image.
|
||||
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_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_instance.transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
|
||||
|
||||
image_instance.instance = new_image;
|
||||
image_instance.transform = new_texture_to_layer_space;
|
||||
image_instance.source_node_id = None;
|
||||
result_table.push(image_instance);
|
||||
}
|
||||
|
||||
let image_instance = image.one_instance_ref().instance;
|
||||
if image_instance.width == 0 || image_instance.height == 0 {
|
||||
return empty_image((), bounds, Color::TRANSPARENT);
|
||||
}
|
||||
|
||||
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 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);
|
||||
let new_end = bounds_in_image_space.end.ceil().max(orig_image_scale);
|
||||
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 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];
|
||||
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 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;
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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]),
|
||||
|
|
|
|||
Loading…
Reference in New Issue