Instance tables refactor part 6: unwrap VectorData and ImageFrame from single-row to multi-row tables (#2684)

* Start refactoring the boolean operations code

* Switch to iterators in the boolean operations code

* Make boolean operations work on rows of a table, not Vecs of single-row tables

* Remove more .transform()

* Simplify brush code

* Attempt to remove .transform() by using Instance<Image<Color>> in brush code, but a regression is introduced

* Improve blend_image_closure

* Simplify

* Remove leading underscore from type arguments

* Remove .transform() from ImageFrameTable<P> and fix Mask node behavior on stencils not fully overlapping its target image

* Remove more .one_instance_ref()

* Fully remove .one_instance_ref() and improve the 'Combine Channels' node robustness

* Fully remove .once_instance_mut()

* Fix tests

* Remove .one_empty_image()

* Make Instances<T>::default() return an empty table for images, but still not yet vector

---------

Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2025-06-04 20:40:15 -07:00
committed by GitHub
parent 76ecdc8f1b
commit cb4289169d
29 changed files with 882 additions and 811 deletions

View File

@@ -1,6 +1,6 @@
use crate::instances::Instances;
use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut};
use crate::transform::Footprint;
use crate::vector::style::ViewMode;
use alloc::sync::Arc;
use core::fmt::Debug;
@@ -37,17 +37,6 @@ impl Hash for SurfaceFrame {
}
}
impl Transform for SurfaceFrame {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl TransformMut for SurfaceFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
#[cfg(feature = "dyn-any")]
unsafe impl StaticType for SurfaceFrame {
type Static = SurfaceFrame;
@@ -152,18 +141,6 @@ unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> {
type Static = SurfaceHandleFrame<T>;
}
impl<T> Transform for SurfaceHandleFrame<T> {
fn transform(&self) -> DAffine2 {
self.transform
}
}
impl<T> TransformMut for SurfaceHandleFrame<T> {
fn transform_mut(&mut self) -> &mut DAffine2 {
&mut self.transform
}
}
// TODO: think about how to automatically clean up memory
/*
impl<'a, Surface> Drop for SurfaceHandle<'a, Surface> {

View File

@@ -277,13 +277,13 @@ pub trait GraphicElementRendered {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]>;
// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
// TODO: Store all click targets in a vec which contains the AABB, click target, and path
// fn add_click_targets(&self, click_targets: &mut Vec<([DVec2; 2], ClickTarget, Vec<NodeId>)>, current_path: Option<NodeId>) {}
// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify
/// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify.
fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}
fn contains_artboard(&self) -> bool {
@@ -639,9 +639,10 @@ impl GraphicElementRendered for VectorDataTable {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
let instance_transform = self.transform();
for instance in self.instance_ref_iter() {
let instance_transform = *instance.transform;
let instance = instance.instance;
for instance in self.instance_ref_iter().map(|instance| instance.instance) {
if let Some(element_id) = element_id {
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = instance.style.fill() != &Fill::None;
@@ -905,14 +906,15 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let instance_transform = self.transform();
let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, instance_transform);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@@ -933,8 +935,8 @@ impl GraphicElementRendered for RasterFrame {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;
let mut render_stuff = |image: vello::peniko::Image, blend_mode: crate::AlphaBlending| {
let image_transform = transform * self.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let mut render_stuff = |image: vello::peniko::Image, instance_transform: DAffine2, blend_mode: crate::AlphaBlending| {
let image_transform = transform * instance_transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
let layer = blend_mode != Default::default();
let Some(bounds) = self.bounding_box(transform, true) else { return };
@@ -960,7 +962,7 @@ impl GraphicElementRendered for RasterFrame {
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
render_stuff(image, *instance.alpha_blending);
render_stuff(image, *instance.transform, *instance.alpha_blending);
}
}
RasterFrame::TextureFrame(image_texture) => {
@@ -971,15 +973,22 @@ impl GraphicElementRendered for RasterFrame {
let id = image.data.id();
context.resource_overrides.insert(id, instance.instance.texture.clone());
render_stuff(image, *instance.alpha_blending);
render_stuff(image, *instance.transform, *instance.alpha_blending);
}
}
}
}
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
let transform = transform * self.transform();
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
let calculate_transform = |instance_transform| {
let transform: DAffine2 = transform * instance_transform;
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
};
match self {
RasterFrame::ImageFrame(instances) => instances.instance_ref_iter().flat_map(|instance| calculate_transform(*instance.transform)).reduce(Quad::combine_bounds),
RasterFrame::TextureFrame(instances) => instances.instance_ref_iter().flat_map(|instance| calculate_transform(*instance.transform)).reduce(Quad::combine_bounds),
}
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
@@ -988,7 +997,21 @@ impl GraphicElementRendered for RasterFrame {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, self.transform());
match self {
RasterFrame::ImageFrame(instances) => {
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
RasterFrame::TextureFrame(instances) => {
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image_texture) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image_texture.transform);
}
}
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@@ -1031,11 +1054,27 @@ impl GraphicElementRendered for GraphicElement {
}
GraphicElement::VectorData(vector_data) => {
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, vector_data.transform());
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *vector_data.transform);
}
}
GraphicElement::RasterFrame(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint);
metadata.local_transforms.insert(element_id, raster_frame.transform());
match raster_frame {
RasterFrame::ImageFrame(instances) => {
// TODO: Find a way to handle more than one row of images
if let Some(image) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
RasterFrame::TextureFrame(instances) => {
// TODO: Find a way to handle more than one row of image textures
if let Some(image_texture) = instances.instance_ref_iter().next() {
metadata.local_transforms.insert(element_id, *image_texture.transform);
}
}
}
}
}
}

View File

@@ -1,10 +1,5 @@
use crate::application_io::TextureFrameTable;
use crate::raster::Pixel;
use crate::raster::image::{Image, ImageFrameTable};
use crate::transform::{Transform, TransformMut};
use crate::AlphaBlending;
use crate::uuid::NodeId;
use crate::vector::VectorDataTable;
use crate::{AlphaBlending, GraphicElement, RasterFrame};
use dyn_any::StaticType;
use glam::DAffine2;
use std::hash::Hash;
@@ -47,26 +42,6 @@ impl<T> Instances<T> {
self.source_node_id.push(instance.source_node_id);
}
pub fn one_instance_ref(&self) -> InstanceRef<T> {
InstanceRef {
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
source_node_id: self.source_node_id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
}
}
pub fn one_instance_mut(&mut self) -> InstanceMut<T> {
let length = self.instance.len();
InstanceMut {
instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
source_node_id: self.source_node_id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
}
}
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
self.instance
.into_iter()
@@ -81,7 +56,7 @@ impl<T> Instances<T> {
})
}
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> {
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> + Clone {
self.instance
.iter()
.zip(self.transform.iter())
@@ -146,11 +121,8 @@ impl<T> Instances<T> {
impl<T: Default + Hash + 'static> Default for Instances<T> {
fn default() -> Self {
// TODO: Remove once all types have been converted to tables
let converted_to_tables = [TypeId::of::<crate::Artboard>(), TypeId::of::<crate::GraphicElement>()];
use core::any::TypeId;
if converted_to_tables.contains(&TypeId::of::<T>()) {
if TypeId::of::<T>() != TypeId::of::<crate::vector::VectorData>() {
// TODO: Remove the 'static trait bound when this special casing is removed by making all types return empty
Self::empty()
} else {
@@ -188,7 +160,7 @@ fn one_source_node_id_default() -> Vec<Option<NodeId>> {
vec![None]
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct InstanceRef<'a, T> {
pub instance: &'a T,
pub transform: &'a DAffine2,
@@ -196,6 +168,20 @@ pub struct InstanceRef<'a, T> {
pub source_node_id: &'a Option<NodeId>,
}
impl<T> InstanceRef<'_, T> {
pub fn to_instance_cloned(self) -> Instance<T>
where
T: Clone,
{
Instance {
instance: self.instance.clone(),
transform: *self.transform,
alpha_blending: *self.alpha_blending,
source_node_id: *self.source_node_id,
}
}
}
#[derive(Debug)]
pub struct InstanceMut<'a, T> {
pub instance: &'a mut T,
@@ -204,7 +190,7 @@ pub struct InstanceMut<'a, T> {
pub source_node_id: &'a mut Option<NodeId>,
}
#[derive(Copy, Clone, Default, Debug)]
#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Instance<T> {
pub instance: T,
pub transform: DAffine2,
@@ -224,64 +210,31 @@ impl<T> Instance<T> {
source_node_id: self.source_node_id,
}
}
}
// VECTOR DATA TABLE
impl Transform for VectorDataTable {
fn transform(&self) -> DAffine2 {
*self.one_instance_ref().transform
pub fn to_instance_ref(&self) -> InstanceRef<T> {
InstanceRef {
instance: &self.instance,
transform: &self.transform,
alpha_blending: &self.alpha_blending,
source_node_id: &self.source_node_id,
}
}
}
impl TransformMut for VectorDataTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.one_instance_mut().transform
}
}
// TEXTURE FRAME TABLE
impl Transform for TextureFrameTable {
fn transform(&self) -> DAffine2 {
*self.one_instance_ref().transform
pub fn to_instance_mut(&mut self) -> InstanceMut<T> {
InstanceMut {
instance: &mut self.instance,
transform: &mut self.transform,
alpha_blending: &mut self.alpha_blending,
source_node_id: &mut self.source_node_id,
}
}
}
impl TransformMut for TextureFrameTable {
fn transform_mut(&mut self) -> &mut DAffine2 {
self.one_instance_mut().transform
}
}
// IMAGE FRAME TABLE
impl<P: Pixel> Transform for ImageFrameTable<P>
where
GraphicElement: From<Image<P>>,
{
fn transform(&self) -> DAffine2 {
*self.one_instance_ref().transform
}
}
impl<P: Pixel> TransformMut for ImageFrameTable<P>
where
GraphicElement: From<Image<P>>,
{
fn transform_mut(&mut self) -> &mut DAffine2 {
self.one_instance_mut().transform
}
}
// RASTER FRAME
impl Transform for RasterFrame {
fn transform(&self) -> DAffine2 {
match self {
RasterFrame::ImageFrame(image_frame) => image_frame.transform(),
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform(),
}
}
}
impl TransformMut for RasterFrame {
fn transform_mut(&mut self) -> &mut DAffine2 {
match self {
RasterFrame::ImageFrame(image_frame) => image_frame.transform_mut(),
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform_mut(),
pub fn to_table(self) -> Instances<T> {
Instances {
instance: vec![self.instance],
transform: vec![self.transform],
alpha_blending: vec![self.alpha_blending],
source_node_id: vec![self.source_node_id],
}
}
}

View File

@@ -6,7 +6,7 @@ use glam::{DAffine2, DVec2};
fn log_to_console<T: core::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
#[cfg(not(target_arch = "spirv"))]
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
debug!("{:#?}", value);
log::debug!("{:#?}", value);
value
}

View File

@@ -586,22 +586,22 @@ impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode<N, I, <N as Nod
// Into
pub struct IntoNode<O>(PhantomData<O>);
impl<_O> IntoNode<_O> {
impl<O> IntoNode<O> {
#[cfg(feature = "alloc")]
pub const fn new() -> Self {
Self(core::marker::PhantomData)
}
}
impl<_O> Default for IntoNode<_O> {
impl<O> Default for IntoNode<O> {
fn default() -> Self {
Self::new()
}
}
impl<'input, I: 'input, _O: 'input> Node<'input, I> for IntoNode<_O>
impl<'input, I: 'input, O: 'input> Node<'input, I> for IntoNode<O>
where
I: Into<_O> + Sync + Send,
I: Into<O> + Sync + Send,
{
type Output = ::dyn_any::DynFuture<'input, _O>;
type Output = ::dyn_any::DynFuture<'input, O>;
#[inline]
fn eval(&'input self, input: I) -> Self::Output {

View File

@@ -665,9 +665,9 @@ impl Blend<Color> for Option<Color> {
}
impl Blend<Color> for ImageFrameTable<Color> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result = self.clone();
let mut result_table = self.clone();
for (over, under) in result.instance_mut_iter().zip(under.instance_ref_iter()) {
for (over, under) in result_table.instance_mut_iter().zip(under.instance_ref_iter()) {
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over.instance = Image {
@@ -678,7 +678,7 @@ impl Blend<Color> for ImageFrameTable<Color> {
};
}
result
result_table
}
}
impl Blend<Color> for GradientStops {

View File

@@ -1,5 +1,5 @@
use crate::Color;
use crate::graphene_core::raster::image::ImageFrameTable;
use crate::instances::Instance;
use crate::raster::Image;
use crate::vector::brush_stroke::BrushStroke;
use crate::vector::brush_stroke::BrushStyle;
@@ -16,12 +16,12 @@ struct BrushCacheImpl {
prev_input: Vec<BrushStroke>,
// The strokes that have been fully processed and blended into the background.
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
background: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
blended_image: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
last_stroke_texture: ImageFrameTable<Color>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
background: Instance<Image<Color>>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
blended_image: Instance<Image<Color>>,
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance"))]
last_stroke_texture: Instance<Image<Color>>,
// A cache for brush textures.
#[cfg_attr(feature = "serde", serde(skip))]
@@ -29,9 +29,9 @@ struct BrushCacheImpl {
}
impl BrushCacheImpl {
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
fn compute_brush_plan(&mut self, mut background: Instance<Image<Color>>, input: &[BrushStroke]) -> BrushPlan {
// Do background invalidation.
if background.one_instance_ref().instance != self.background.one_instance_ref().instance {
if background != self.background {
self.background = background.clone();
return BrushPlan {
strokes: input.to_vec(),
@@ -56,7 +56,11 @@ impl BrushCacheImpl {
background = core::mem::take(&mut self.blended_image);
// Check if the first non-blended stroke is an extension of the last one.
let mut first_stroke_texture = ImageFrameTable::one_empty_image();
let mut first_stroke_texture = Instance {
instance: Image::default(),
transform: glam::DAffine2::ZERO,
..Default::default()
};
let mut first_stroke_point_skip = 0;
let strokes = input[num_blended_strokes..].to_vec();
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
@@ -80,7 +84,7 @@ impl BrushCacheImpl {
}
}
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: Instance<Image<Color>>, last_stroke_texture: Instance<Image<Color>>) {
self.prev_input = input;
self.blended_image = blended_image;
self.last_stroke_texture = last_stroke_texture;
@@ -95,8 +99,8 @@ impl Hash for BrushCacheImpl {
#[derive(Clone, Debug, Default)]
pub struct BrushPlan {
pub strokes: Vec<BrushStroke>,
pub background: ImageFrameTable<Color>,
pub first_stroke_texture: ImageFrameTable<Color>,
pub background: Instance<Image<Color>>,
pub first_stroke_texture: Instance<Image<Color>>,
pub first_stroke_point_skip: usize,
}
@@ -160,12 +164,12 @@ impl BrushCache {
}
}
pub fn compute_brush_plan(&self, background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
pub fn compute_brush_plan(&self, background: Instance<Image<Color>>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.inner.lock().unwrap();
inner.compute_brush_plan(background, input)
}
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Image<Color>>, last_stroke_texture: Instance<Image<Color>>) {
let mut inner = self.inner.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture)
}

View File

@@ -3,7 +3,6 @@ use super::discrete_srgb::float_to_srgb_u8;
use crate::AlphaBlending;
use crate::GraphicElement;
use crate::instances::{Instance, Instances};
use crate::transform::TransformMut;
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
use dyn_any::StaticType;
@@ -232,7 +231,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
image: image.one_instance_ref().instance.clone(),
image: image.instance_ref_iter().next().unwrap().instance.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@@ -270,27 +269,90 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
let mut image_frame_table = ImageFrameTable::new(image);
*image_frame_table.one_instance_mut().transform = transform;
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
*image_frame_table.instance_mut_iter().next().unwrap().transform = transform;
*image_frame_table.instance_mut_iter().next().unwrap().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance_ref().instance.image.clone()),
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.instance_ref_iter().next().unwrap().instance.image.clone()),
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table,
})
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_image_frame_instance<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Instance<Image<Color>>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageFrame<P: Pixel> {
pub image: Image<P>,
}
impl From<ImageFrame<Color>> for GraphicElement {
fn from(image_frame: ImageFrame<Color>) -> Self {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(ImageFrameTable::new(image_frame.image)))
}
}
impl From<GraphicElement> for ImageFrame<Color> {
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
image: image.instance_ref_iter().next().unwrap().instance.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
}
}
#[cfg(feature = "dyn-any")]
unsafe impl<P> StaticType for ImageFrame<P>
where
P: dyn_any::StaticTypeSized + Pixel,
P::Static: Pixel,
{
type Static = ImageFrame<P::Static>;
}
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldImageFrame<P: Pixel> {
image: Image<P>,
transform: DAffine2,
alpha_blending: AlphaBlending,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Instances<ImageFrame<Color>>),
ImageFrameTable(ImageFrameTable<Color>),
ImageInstance(Instance<Image<Color>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Instance {
instance: image,
..Default::default()
},
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => Instance {
instance: image_frame_with_transform_and_blending.image,
transform: image_frame_with_transform_and_blending.transform,
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
source_node_id: None,
},
FormatVersions::ImageFrame(image_frame) => Instance {
instance: image_frame.instance_ref_iter().next().unwrap().instance.image.clone(),
..Default::default()
},
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table.instance_iter().next().unwrap_or_default(),
FormatVersions::ImageInstance(image_instance) => image_instance,
})
}
// TODO: Rename to ImageTable
pub type ImageFrameTable<P> = Instances<Image<P>>;
/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
impl ImageFrameTable<Color> {
pub fn one_empty_image() -> Self {
let mut result = Self::new(Image::default());
*result.transform_mut() = DAffine2::ZERO;
result
}
}
impl<P: Debug + Copy + Pixel> Sample for Image<P> {
type Pixel = P;
@@ -305,62 +367,6 @@ impl<P: Debug + Copy + Pixel> Sample for Image<P> {
}
}
impl<P> Sample for ImageFrameTable<P>
where
P: Debug + Copy + Pixel,
GraphicElement: From<Image<P>>,
{
type Pixel = P;
// TODO: Improve sampling logic
#[inline(always)]
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
let image_transform = self.one_instance_ref().transform;
let image = self.one_instance_ref().instance;
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos);
Sample::sample(image, pos, area)
}
}
impl<P> Bitmap for ImageFrameTable<P>
where
P: Copy + Pixel,
GraphicElement: From<Image<P>>,
{
type Pixel = P;
fn width(&self) -> u32 {
let image = self.one_instance_ref().instance;
image.width()
}
fn height(&self) -> u32 {
let image = self.one_instance_ref().instance;
image.height()
}
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
let image = self.one_instance_ref().instance;
image.get_pixel(x, y)
}
}
impl<P> BitmapMut for ImageFrameTable<P>
where
P: Copy + Pixel,
GraphicElement: From<Image<P>>,
{
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
self.one_instance_mut().instance.get_pixel_mut(x, y)
}
}
impl<P: Copy + Pixel> Image<P> {
pub fn get_mut(&mut self, x: usize, y: usize) -> &mut P {
&mut self.data[y * (self.width as usize) + x]

View File

@@ -266,11 +266,11 @@ pub struct DynAnyNode<I, O, Node> {
_o: PhantomData<O>,
}
impl<'input, _I, _O, N> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
impl<'input, I, O, N> Node<'input, Any<'input>> for DynAnyNode<I, O, N>
where
_I: 'input + dyn_any::StaticType + WasmNotSend,
_O: 'input + dyn_any::StaticType + WasmNotSend,
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
I: 'input + dyn_any::StaticType + WasmNotSend,
O: 'input + dyn_any::StaticType + WasmNotSend,
N: 'input + Node<'input, I, Output = DynFuture<'input, O>>,
{
type Output = FutureAny<'input>;
#[inline]
@@ -294,11 +294,11 @@ where
self.node.serialize()
}
}
impl<'input, _I, _O, N> DynAnyNode<_I, _O, N>
impl<'input, I, O, N> DynAnyNode<I, O, N>
where
_I: 'input + dyn_any::StaticType,
_O: 'input + dyn_any::StaticType,
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
I: 'input + dyn_any::StaticType,
O: 'input + dyn_any::StaticType,
N: 'input + Node<'input, I, Output = DynFuture<'input, O>>,
{
pub const fn new(node: N) -> Self {
Self {

View File

@@ -129,7 +129,9 @@ mod test {
.instance
.as_vector_data()
.unwrap()
.one_instance_ref()
.instance_ref_iter()
.next()
.unwrap()
.instance
.bounding_box_with_transform(*instanced.transform)
.unwrap();

View File

@@ -253,9 +253,9 @@ fn isometric_grid_test() {
// Works properly
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@@ -268,9 +268,9 @@ fn isometric_grid_test() {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;

View File

@@ -60,8 +60,8 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
region_domain: old.region_domain,
upstream_graphic_group: old.upstream_graphic_group,
});
*vector_data_table.one_instance_mut().transform = old.transform;
*vector_data_table.one_instance_mut().alpha_blending = old.alpha_blending;
*vector_data_table.instance_mut_iter().next().unwrap().transform = old.transform;
*vector_data_table.instance_mut_iter().next().unwrap().alpha_blending = old.alpha_blending;
vector_data_table
}
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,

View File

@@ -1193,11 +1193,16 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
}
}
let mut output_table = VectorDataTable::default();
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
// Create a table with one instance of an empty VectorData, then get a mutable reference to it which we append flattened subpaths to
let mut output_table = VectorDataTable::new(VectorData::default());
let Some(mut output) = output_table.instance_mut_iter().next() else {
return output_table;
};
// Flatten the graphic group input into the output VectorData instance
flatten_group(&graphic_group_input, &mut output);
// Return the single-row VectorDataTable containing the flattened VectorData subpaths
output_table
}
@@ -1217,6 +1222,8 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
style: std::mem::take(&mut vector_data_instance.instance.style),
upstream_graphic_group: std::mem::take(&mut vector_data_instance.instance.upstream_graphic_group),
};
// Transfer the stroke transform from the input vector data to the result.
result.style.set_stroke_transform(vector_data_instance.transform);
// Using `stroke_bezpath_iter` so that the `subpath_segment_lengths` is aligned to the segments of each bezpath.
// So we can index into `subpath_segment_lengths` to get the length of the segments.
@@ -1249,10 +1256,6 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
result.append_bezpath(sample_bezpath);
}
// Transfer the style from the input vector data to the result.
result.style = vector_data_instance.instance.style;
result.style.set_stroke_transform(vector_data_instance.transform);
vector_data_instance.instance = result;
result_table.push(vector_data_instance);
}
@@ -1459,11 +1462,6 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
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
}
@@ -1732,14 +1730,14 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
fn close_path(_: impl Ctx, source: VectorDataTable) -> VectorDataTable {
let mut new_table = VectorDataTable::empty();
let mut result_table = VectorDataTable::empty();
for mut source_instance in source.instance_iter() {
source_instance.instance.close_subpaths();
new_table.push(source_instance);
result_table.push(source_instance);
}
new_table
result_table
}
#[node_macro::node(category("Vector"), path(graphene_core::vector))]