mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
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:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user