mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 23:38:06 +08:00
Instance tables refactor part 2: move the transform and alpha_blending fields up a level (#2249)
* Fix domain data structure field plural naming * Rename method one_item to one_instance Rename method one_item to one_instance * Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T> Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T> * Add transform and alpha_blending fields to Instances<T> * Finish the refactor (Brush tool is broken though) * Add test for brush node * Fix brush node * Fix default empty images being 1x1 instead of 0x0 as they should be * Fix tests * Fix path transform * Add correct upgrading to move the transform/blending up a level --------- Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
@@ -605,17 +605,15 @@ impl Blend<Color> for ImageFrameTable<Color> {
|
||||
let mut result = self.clone();
|
||||
|
||||
for (over, under) in result.instances_mut().zip(under.instances()) {
|
||||
let data = over.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
let data = over.instance.image.data.iter().zip(under.instance.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
|
||||
*over = ImageFrame {
|
||||
*over.instance = ImageFrame {
|
||||
image: super::Image {
|
||||
data,
|
||||
width: over.image.width,
|
||||
height: over.image.height,
|
||||
width: over.instance.image.width,
|
||||
height: over.instance.image.height,
|
||||
base64_string: None,
|
||||
},
|
||||
transform: over.transform,
|
||||
alpha_blending: over.alpha_blending,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -744,7 +742,7 @@ where
|
||||
{
|
||||
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
|
||||
for instance in self.instances_mut() {
|
||||
for c in instance.image.data.iter_mut() {
|
||||
for c in instance.instance.image.data.iter_mut() {
|
||||
*c = map_fn(c);
|
||||
}
|
||||
}
|
||||
@@ -1582,10 +1580,7 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn color_overlay_multiply() {
|
||||
let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4);
|
||||
let image = ImageFrame {
|
||||
image: Image::new(1, 1, image_color),
|
||||
..Default::default()
|
||||
};
|
||||
let image = ImageFrame { image: Image::new(1, 1, image_color) };
|
||||
|
||||
// Color { red: 0., green: 1., blue: 0., alpha: 1. }
|
||||
let overlay_color = Color::GREEN;
|
||||
@@ -1594,7 +1589,7 @@ mod test {
|
||||
let opacity = 100_f64;
|
||||
|
||||
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
|
||||
let result = result.one_item();
|
||||
let result = result.one_instance().instance;
|
||||
|
||||
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
|
||||
assert_eq!(result.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use core::hash::Hash;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use crate::raster::image::ImageFrame;
|
||||
use crate::graphene_core::raster::image::ImageFrameTable;
|
||||
use crate::raster::Image;
|
||||
use crate::vector::brush_stroke::BrushStroke;
|
||||
use crate::vector::brush_stroke::BrushStyle;
|
||||
use crate::Color;
|
||||
|
||||
use core::hash::Hash;
|
||||
use dyn_any::DynAny;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct BrushCacheImpl {
|
||||
@@ -18,9 +17,12 @@ struct BrushCacheImpl {
|
||||
prev_input: Vec<BrushStroke>,
|
||||
|
||||
// The strokes that have been fully processed and blended into the background.
|
||||
background: ImageFrame<Color>,
|
||||
blended_image: ImageFrame<Color>,
|
||||
last_stroke_texture: ImageFrame<Color>,
|
||||
#[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>,
|
||||
|
||||
// A cache for brush textures.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
@@ -28,9 +30,9 @@ struct BrushCacheImpl {
|
||||
}
|
||||
|
||||
impl BrushCacheImpl {
|
||||
fn compute_brush_plan(&mut self, mut background: ImageFrame<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
// Do background invalidation.
|
||||
if background.transform != self.background.transform || background.image != self.background.image {
|
||||
if background.one_instance().instance.image != self.background.one_instance().instance.image {
|
||||
self.background = background.clone();
|
||||
return BrushPlan {
|
||||
strokes: input.to_vec(),
|
||||
@@ -55,7 +57,7 @@ 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 = ImageFrame::default();
|
||||
let mut first_stroke_texture = ImageFrameTable::empty();
|
||||
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 {
|
||||
@@ -79,7 +81,7 @@ impl BrushCacheImpl {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrame<Color>, last_stroke_texture: ImageFrame<Color>) {
|
||||
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
|
||||
self.prev_input = input;
|
||||
self.blended_image = blended_image;
|
||||
self.last_stroke_texture = last_stroke_texture;
|
||||
@@ -94,8 +96,8 @@ impl Hash for BrushCacheImpl {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BrushPlan {
|
||||
pub strokes: Vec<BrushStroke>,
|
||||
pub background: ImageFrame<Color>,
|
||||
pub first_stroke_texture: ImageFrame<Color>,
|
||||
pub background: ImageFrameTable<Color>,
|
||||
pub first_stroke_texture: ImageFrameTable<Color>,
|
||||
pub first_stroke_point_skip: usize,
|
||||
}
|
||||
|
||||
@@ -159,12 +161,12 @@ impl BrushCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_brush_plan(&self, background: ImageFrame<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
pub fn compute_brush_plan(&self, background: ImageFrameTable<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: ImageFrame<Color>, last_stroke_texture: ImageFrame<Color>) {
|
||||
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.cache_results(input, blended_image, last_stroke_texture)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::discrete_srgb::float_to_srgb_u8;
|
||||
use super::Color;
|
||||
use crate::instances::Instances;
|
||||
use crate::{instances::Instances, transform::TransformMut};
|
||||
use crate::{AlphaBlending, GraphicElement};
|
||||
use alloc::vec::Vec;
|
||||
use core::hash::{Hash, Hasher};
|
||||
@@ -110,15 +110,6 @@ impl<P: Hash + Pixel> Hash for Image<P> {
|
||||
}
|
||||
|
||||
impl<P: Pixel> Image<P> {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: Vec::new(),
|
||||
base64_string: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(width: u32, height: u32, color: P) -> Self {
|
||||
Self {
|
||||
width,
|
||||
@@ -221,47 +212,50 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
||||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ImageFrameTable<Color>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[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 EitherFormat {
|
||||
ImageFrame(ImageFrame<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
ImageFrameTable(ImageFrameTable<Color>),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::ImageFrame(image_frame) => ImageFrameTable::<Color>::new(image_frame),
|
||||
EitherFormat::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(ImageFrame { image });
|
||||
*image_frame_table.one_instance_mut().transform = transform;
|
||||
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
|
||||
image_frame_table
|
||||
}
|
||||
EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table,
|
||||
})
|
||||
}
|
||||
|
||||
pub type ImageFrameTable<P> = Instances<ImageFrame<P>>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, specta::Type)]
|
||||
/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
|
||||
impl ImageFrameTable<Color> {
|
||||
pub fn empty() -> Self {
|
||||
let mut result = Self::new(ImageFrame::default());
|
||||
*result.transform_mut() = DAffine2::ZERO;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
// The transform that maps image space to layer space.
|
||||
//
|
||||
// Image space is unitless [0, 1] for both axes, with x axis positive
|
||||
// going right and y axis positive going down, with the origin lying at
|
||||
// the topleft of the image and (1, 1) lying at the bottom right of the image.
|
||||
//
|
||||
// Layer space has pixels as its units for both axes, with the x axis
|
||||
// positive going right and y axis positive going down, with the origin
|
||||
// being an unspecified quantity.
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
impl<P: Pixel> Default for ImageFrame<P> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
image: Image::empty(),
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
// Different from DAffine2::default() which is IDENTITY
|
||||
transform: DAffine2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
||||
@@ -271,7 +265,6 @@ impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
||||
#[inline(always)]
|
||||
fn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> {
|
||||
let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64);
|
||||
let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos);
|
||||
if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y {
|
||||
return None;
|
||||
}
|
||||
@@ -289,7 +282,11 @@ where
|
||||
// TODO: Improve sampling logic
|
||||
#[inline(always)]
|
||||
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
|
||||
let image = self.one_item();
|
||||
let image_transform = self.one_instance().transform;
|
||||
let image = self.one_instance().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)
|
||||
}
|
||||
@@ -319,19 +316,19 @@ where
|
||||
type Pixel = P;
|
||||
|
||||
fn width(&self) -> u32 {
|
||||
let image = self.one_item();
|
||||
let image = self.one_instance().instance;
|
||||
|
||||
image.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
let image = self.one_item();
|
||||
let image = self.one_instance().instance;
|
||||
|
||||
image.height()
|
||||
}
|
||||
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
|
||||
let image = self.one_item();
|
||||
let image = self.one_instance().instance;
|
||||
|
||||
image.get_pixel(x, y)
|
||||
}
|
||||
@@ -349,7 +346,7 @@ where
|
||||
P::Static: Pixel,
|
||||
{
|
||||
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
|
||||
let image = self.one_item_mut();
|
||||
let image = self.one_instance_mut().instance;
|
||||
|
||||
BitmapMut::get_pixel_mut(image, x, y)
|
||||
}
|
||||
@@ -384,19 +381,11 @@ impl<P: Pixel> AsRef<ImageFrame<P>> for ImageFrame<P> {
|
||||
|
||||
impl<P: Hash + Pixel> Hash for ImageFrame<P> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
0.hash(state);
|
||||
self.image.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> ImageFrame<P> {
|
||||
/// Compute the pivot in local space with the current transform applied
|
||||
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(normalized_pivot)
|
||||
}
|
||||
}
|
||||
|
||||
/* This does not work because of missing specialization
|
||||
* so we have to manually implement this for now
|
||||
impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> {
|
||||
@@ -420,8 +409,6 @@ impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
|
||||
height: image.image.height,
|
||||
base64_string: None,
|
||||
},
|
||||
transform: image.transform,
|
||||
alpha_blending: image.alpha_blending,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,8 +423,6 @@ impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
|
||||
height: image.image.height,
|
||||
base64_string: None,
|
||||
},
|
||||
transform: image.transform,
|
||||
alpha_blending: image.alpha_blending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user