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:
Keavon Chambers
2025-03-02 01:26:36 -08:00
parent 4ff2bdb04f
commit f1160e1ca6
33 changed files with 1099 additions and 984 deletions

View File

@@ -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,
}
}
}