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

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