Instance tables refactor part 3: flatten ImageFrame<P> in lieu of Image<P> (#2256)

* Remove ImageFrame<T> by flattening it into Image<T>

* Rename TextureFrame to ImageTexture

* Fix tests
This commit is contained in:
Keavon Chambers
2025-03-02 02:09:28 -08:00
parent f1160e1ca6
commit 2f6c6e28f0
19 changed files with 235 additions and 269 deletions

View File

@@ -5,8 +5,8 @@ use graph_craft::proto::FutureWrapperNode;
use graphene_core::raster::adjustments::blend_colors;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Image, Pixel, Sample};
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::raster::{Alpha, Bitmap, BlendMode, Color, Pixel, Sample};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
@@ -93,14 +93,14 @@ where
P: Pixel + Alpha + std::fmt::Debug + dyn_any::StaticType,
P::Static: Pixel,
BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>,
GraphicElement: From<ImageFrame<P>>,
GraphicElement: From<Image<P>>,
{
if positions.is_empty() {
return target;
}
let target_width = target.one_instance().instance.image.width;
let target_height = target.one_instance().instance.image.height;
let target_width = target.one_instance().instance.width;
let target_height = target.one_instance().instance.height;
let target_size = DVec2::new(target_width as f64, target_height as f64);
let texture_size = DVec2::new(texture.width as f64, texture.height as f64);
@@ -125,12 +125,12 @@ where
let max_y = (blit_area_offset.y + blit_area_dimensions.y).saturating_sub(1);
let max_x = (blit_area_offset.x + blit_area_dimensions.x).saturating_sub(1);
assert!(texture_index(max_x, max_y) < texture.data.len());
assert!(target_index(max_x, max_y) < target.one_instance().instance.image.data.len());
assert!(target_index(max_x, max_y) < target.one_instance().instance.data.len());
for y in blit_area_offset.y..blit_area_offset.y + blit_area_dimensions.y {
for x in blit_area_offset.x..blit_area_offset.x + blit_area_dimensions.x {
let src_pixel = texture.data[texture_index(x, y)];
let dst_pixel = &mut target.one_instance_mut().instance.image.data[target_index(x + clamp_start.x, y + clamp_start.y)];
let dst_pixel = &mut target.one_instance_mut().instance.data[target_index(x + clamp_start.x, y + clamp_start.y)];
*dst_pixel = blend_mode.eval((src_pixel, *dst_pixel));
}
}
@@ -146,7 +146,7 @@ pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image<Color> {
let blank_texture = empty_image((), transform, Color::TRANSPARENT);
let image = crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.one_instance().instance.image.clone()
image.one_instance().instance.clone()
}
macro_rules! inline_blend_funcs {
@@ -286,9 +286,7 @@ async fn brush(_: impl Ctx, image_frame_table: ImageFrameTable<Color>, bounds: I
let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
if has_erase_strokes {
let opaque_image = ImageFrame {
image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE),
};
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
let mut erase_restore_mask = ImageFrameTable::new(opaque_image);
*erase_restore_mask.transform_mut() = background_bounds;
*erase_restore_mask.one_instance_mut().alpha_blending = Default::default();

View File

@@ -1,6 +1,5 @@
use graph_craft::proto::types::Percentage;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{Color, Ctx};
@@ -13,10 +12,9 @@ async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Perc
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame = image_frame.one_instance().instance;
let image = image_frame.one_instance().instance;
// Prepare the image data for processing
let image = &image_frame.image;
let image_data = bytemuck::cast_vec(image.data.clone());
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
let dynamic_image: image::DynamicImage = image_buffer.into();
@@ -34,7 +32,7 @@ async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable<Color>, strength: Perc
base64_string: None,
};
let mut result = ImageFrameTable::new(ImageFrame { image: dehazed_image });
let mut result = ImageFrameTable::new(dehazed_image);
*result.transform_mut() = image_frame_transform;
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;

View File

@@ -4,8 +4,8 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::*;
use graph_craft::proto::*;
use graphene_core::application_io::ApplicationIo;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::{BlendMode, Image, Pixel};
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::raster::{BlendMode, Pixel};
use graphene_core::transform::Transform;
use graphene_core::transform::TransformMut;
use graphene_core::*;
@@ -72,9 +72,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap();
#[cfg(feature = "image-compare")]
let img: image::DynamicImage = image::Rgba32FImage::from_raw(image.image.width, image.image.height, bytemuck::cast_vec(image.image.data.clone()))
.unwrap()
.into();
let img: image::DynamicImage = image::Rgba32FImage::from_raw(image.width, image.height, bytemuck::cast_vec(image.data.clone())).unwrap().into();
// TODO: The cache should be based on the network topology not the node name
let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key("placeholder") {
@@ -94,7 +92,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
.create_compute_pass(
&compute_pass_descriptor.pipeline_layout,
compute_pass_descriptor.readback_buffer.clone(),
ComputePassDimensions::XY(image.image.width / 12 + 1, image.image.height / 8 + 1),
ComputePassDimensions::XY(image.width / 12 + 1, image.height / 8 + 1),
)
.unwrap();
executor.execute_compute_pipeline(compute_pass).unwrap();
@@ -105,7 +103,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
log::debug!("first color: {:?}", colors[0]);
#[cfg(feature = "image-compare")]
let img2: image::DynamicImage = image::Rgba32FImage::from_raw(image.image.width, image.image.height, bytemuck::cast_vec(colors.clone())).unwrap().into();
let img2: image::DynamicImage = image::Rgba32FImage::from_raw(image.width, image.height, bytemuck::cast_vec(colors.clone())).unwrap().into();
#[cfg(feature = "image-compare")]
let score = image_compare::rgb_hybrid_compare(&img.into_rgb8(), &img2.into_rgb8()).unwrap();
#[cfg(feature = "image-compare")]
@@ -113,11 +111,11 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
let new_image = Image {
data: colors,
width: image.image.width,
height: image.image.height,
width: image.width,
height: image.height,
..Default::default()
};
let mut result = ImageFrameTable::new(ImageFrame { image: new_image });
let mut result = ImageFrameTable::new(new_image);
*result.transform_mut() = image_frame_table.transform();
*result.one_instance_mut().alpha_blending = *image_frame_table.one_instance().alpha_blending;
@@ -136,7 +134,7 @@ impl<Node, EditorApi> MapGpuNode<Node, EditorApi> {
async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(node: DocumentNode, image: &ImageFrameTable<T>, executor: &&WgpuExecutor) -> Result<ComputePass, String>
where
GraphicElement: From<ImageFrame<T>>,
GraphicElement: From<Image<T>>,
T::Static: Pixel,
{
let image = image.one_instance().instance;
@@ -215,11 +213,11 @@ where
.await
.unwrap();
let len: usize = image.image.data.len();
let len: usize = image.data.len();
let storage_buffer = executor
.create_storage_buffer(
image.image.data.clone(),
image.data.clone(),
StorageBufferOptions {
cpu_writable: false,
gpu_writable: true,
@@ -234,7 +232,7 @@ where
// let surface = unsafe { executor.create_surface(canvas) }.unwrap();
// let surface_id = surface.surface_id;
// let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap();
// let texture = executor.create_texture_buffer(image.clone(), TextureBufferOptions::Texture).unwrap();
// // executor.create_render_pass(texture, surface).unwrap();
@@ -245,7 +243,7 @@ where
// return frame;
log::debug!("creating buffer");
let width_uniform = executor.create_uniform_buffer(image.image.width).unwrap();
let width_uniform = executor.create_uniform_buffer(image.width).unwrap();
let storage_buffer = Arc::new(storage_buffer);
let output_buffer = executor.create_output_buffer(len, concrete!(Color), false).unwrap();
@@ -289,8 +287,8 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let foreground = foreground.one_instance().instance;
let background = background.one_instance().instance;
let foreground_size = DVec2::new(foreground.image.width as f64, foreground.image.height as f64);
let background_size = DVec2::new(background.image.width as f64, background.image.height as f64);
let foreground_size = DVec2::new(foreground.width as f64, foreground.height as f64);
let background_size = DVec2::new(background.width as f64, background.height as f64);
// Transforms a point from the background image to the foreground image
let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size);
@@ -371,16 +369,16 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
)
.await
.unwrap();
let len = background.image.data.len();
let len = background.data.len();
let executor = WgpuExecutor::new()
.await
.expect("Failed to create wgpu executor. Please make sure that webgpu is enabled for your browser.");
log::debug!("creating buffer");
let width_uniform = executor.create_uniform_buffer(background.image.width).unwrap();
let width_uniform = executor.create_uniform_buffer(background.width).unwrap();
let bg_storage_buffer = executor
.create_storage_buffer(
background.image.data.clone(),
background.data.clone(),
StorageBufferOptions {
cpu_writable: false,
gpu_writable: true,
@@ -391,7 +389,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
.unwrap();
let fg_storage_buffer = executor
.create_storage_buffer(
foreground.image.data.clone(),
foreground.data.clone(),
StorageBufferOptions {
cpu_writable: false,
gpu_writable: true,
@@ -400,7 +398,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
},
)
.unwrap();
let fg_width_uniform = executor.create_uniform_buffer(foreground.image.width).unwrap();
let fg_width_uniform = executor.create_uniform_buffer(foreground.width).unwrap();
let transform_uniform = executor.create_uniform_buffer(transform_matrix).unwrap();
let translation_uniform = executor.create_uniform_buffer(translation).unwrap();
let width_uniform = Arc::new(width_uniform);
@@ -442,7 +440,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
};
log::debug!("created pipeline");
let compute_pass = executor
.create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(background.image.width, background.image.height))
.create_compute_pass(&pipeline, Some(readback_buffer.clone()), ComputePassDimensions::XY(background.width, background.height))
.unwrap();
executor.execute_compute_pipeline(compute_pass).unwrap();
log::debug!("executed pipeline");
@@ -452,12 +450,12 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let created_image = Image {
data: colors,
width: background.image.width,
height: background.image.height,
width: background.width,
height: background.height,
..Default::default()
};
let mut result = ImageFrameTable::new(ImageFrame { image: created_image });
let mut result = ImageFrameTable::new(created_image);
*result.transform_mut() = background_transform;
*result.one_instance_mut().alpha_blending = *background_alpha_blending;

View File

@@ -18,7 +18,7 @@ async fn image_color_palette(
let image = image.one_instance().instance;
for pixel in image.image.data.iter() {
for pixel in image.data.iter() {
let r = pixel.r() * GRID;
let g = pixel.g() * GRID;
let b = pixel.b() * GRID;
@@ -65,20 +65,17 @@ async fn image_color_palette(
mod test {
use super::*;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image;
use graphene_core::raster::image::{Image, ImageFrameTable};
#[test]
fn test_image_color_palette() {
let result = image_color_palette(
(),
ImageFrameTable::new(ImageFrame {
image: Image {
width: 100,
height: 100,
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
base64_string: None,
},
ImageFrameTable::new(Image {
width: 100,
height: 100,
data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000],
base64_string: None,
}),
1,
);

View File

@@ -1,9 +1,8 @@
use dyn_any::DynAny;
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::raster::{
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue,
Sample,
Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample,
};
use graphene_core::transform::{Transform, TransformMut};
use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, GraphicElement, Node};
@@ -33,10 +32,9 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
let image_frame_transform = image_frame.transform();
let image_frame_alpha_blending = image_frame.one_instance().alpha_blending;
let image_frame = image_frame.one_instance().instance;
let image = image_frame.one_instance().instance;
// Resize the image using the image crate
let image = &image_frame.image;
let data = bytemuck::cast_vec(image.data.clone());
let footprint = ctx.footprint();
@@ -86,7 +84,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
let mut result = ImageFrameTable::new(ImageFrame { image });
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = new_transform;
*result.one_instance_mut().alpha_blending = *image_frame_alpha_blending;
@@ -263,7 +261,7 @@ where
_P::Static: Pixel,
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
GraphicElement: From<ImageFrame<_P>>,
GraphicElement: From<Image<_P>>,
{
let (background, foreground) = images;
@@ -330,11 +328,11 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
}
let image_instance = image.one_instance().instance;
if image_instance.image.width == 0 || image_instance.image.height == 0 {
if image_instance.width == 0 || image_instance.height == 0 {
return empty_image((), bounds, Color::TRANSPARENT);
}
let orig_image_scale = DVec2::new(image_instance.image.width as f64, image_instance.image.height as f64);
let orig_image_scale = DVec2::new(image_instance.width as f64, image_instance.height as f64);
let layer_to_image_space = DAffine2::from_scale(orig_image_scale) * image.transform().inverse();
let bounds_in_image_space = Bbox::unit().affine_transform(layer_to_image_space * bounds).to_axis_aligned_bbox();
@@ -345,11 +343,11 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
// Copy over original image into enlarged image.
let mut new_img = Image::new(new_scale.x as u32, new_scale.y as u32, Color::TRANSPARENT);
let offset_in_new_image = (-new_start).as_uvec2();
for y in 0..image_instance.image.height {
let old_start = y * image_instance.image.width;
for y in 0..image_instance.height {
let old_start = y * image_instance.width;
let new_start = (y + offset_in_new_image.y) * new_img.width + offset_in_new_image.x;
let old_row = &image_instance.image.data[old_start as usize..(old_start + image_instance.image.width) as usize];
let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.image.width) as usize];
let old_row = &image_instance.data[old_start as usize..(old_start + image_instance.width) as usize];
let new_row = &mut new_img.data[new_start as usize..(new_start + image_instance.width) as usize];
new_row.copy_from_slice(old_row);
}
@@ -357,7 +355,7 @@ fn extend_image_to_bounds(image: ImageFrameTable<Color>, bounds: DAffine2) -> Im
// let layer_to_new_texture_space = (DAffine2::from_scale(1. / new_scale) * DAffine2::from_translation(new_start) * layer_to_image_space).inverse();
let new_texture_to_layer_space = image.transform() * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
let mut result = ImageFrameTable::new(ImageFrame { image: new_img });
let mut result = ImageFrameTable::new(new_img);
*result.transform_mut() = new_texture_to_layer_space;
*result.one_instance_mut().alpha_blending = *image.one_instance().alpha_blending;
@@ -371,7 +369,7 @@ fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> ImageFrameTabl
let image = Image::new(width, height, color);
let mut result = ImageFrameTable::new(ImageFrame { image });
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = transform;
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
@@ -559,7 +557,7 @@ fn noise_pattern(
}
}
let mut result = ImageFrameTable::new(ImageFrame { image });
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
@@ -621,7 +619,7 @@ fn noise_pattern(
}
}
let mut result = ImageFrameTable::new(ImageFrame { image });
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = AlphaBlending::default();
@@ -669,7 +667,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
data,
..Default::default()
};
let mut result = ImageFrameTable::new(ImageFrame { image });
let mut result = ImageFrameTable::new(image);
*result.transform_mut() = DAffine2::from_translation(offset) * DAffine2::from_scale(size);
*result.one_instance_mut().alpha_blending = Default::default();

View File

@@ -6,8 +6,7 @@ use graphene_core::application_io::SurfaceHandle;
use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig};
#[cfg(target_arch = "wasm32")]
use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::image::{ImageFrame, ImageFrameTable};
use graphene_core::raster::Image;
use graphene_core::raster::image::{Image, ImageFrameTable};
use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint;
@@ -81,13 +80,11 @@ fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
return ImageFrameTable::empty();
};
let image = image.to_rgba32f();
let image = ImageFrame {
image: Image {
data: image.chunks(4).map(|pixel| Color::from_unassociated_alpha(pixel[0], pixel[1], pixel[2], pixel[3])).collect(),
width: image.width(),
height: image.height(),
..Default::default()
},
let image = Image {
data: image.chunks(4).map(|pixel| Color::from_unassociated_alpha(pixel[0], pixel[1], pixel[2], pixel[3])).collect(),
width: image.width(),
height: image.height(),
..Default::default()
};
ImageFrameTable::new(image)
@@ -204,9 +201,7 @@ async fn rasterize<T: GraphicElementRendered + graphene_core::transform::Transfo
let rasterized = context.get_image_data(0., 0., resolution.x as f64, resolution.y as f64).unwrap();
let mut result = ImageFrameTable::new(ImageFrame {
image: Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32),
});
let mut result = ImageFrameTable::new(Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32));
*result.transform_mut() = footprint.transform;
result