mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Take the transform of the ImageFrame into account when blending (#1072)
* Take the transform of the ImageFrame into account when blending The implementation computes the axis-aligned bounding box after we transform the corners of the source image, and then iterates through that box and computes the inverse of the affine transform of the source image. The samples are taken based on the u/v coordinates, so that the differences in size/aspect ratio between the images don't matter. This makes for a much simpler implementation, and gives us the flexibility to add different filtering methods in the future, for example. Signed-off-by: Ică Alexandru-Gabriel <alexandru@seyhanlee.com> * Name the parameters for the blend node properly This avoids confusion between which one of the images is the `source` image and which one is the `destination`. Signed-off-by: Ică Alexandru-Gabriel <alexandru@seyhanlee.com> * Remove rendundant computation for u/v coordinates Signed-off-by: Ică Alexandru-Gabriel <alexandru@seyhanlee.com> * Rewrite the sampling/clamping logic * Add image frame transform node * Move transform node to transform module * Fix a few issues with our transformation logic * Fix math + do cleanup --------- Signed-off-by: Ică Alexandru-Gabriel <alexandru@seyhanlee.com> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
committed by
Keavon Chambers
parent
fb6ca73808
commit
0a775fe9be
@@ -1,6 +1,6 @@
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::{BVec2, DAffine2, DVec2};
|
||||
use graphene_core::raster::{Color, Image, ImageFrame};
|
||||
use graphene_core::Node;
|
||||
|
||||
@@ -124,23 +124,91 @@ where
|
||||
image_frame
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AxisAlignedBbox {
|
||||
start: DVec2,
|
||||
end: DVec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Bbox {
|
||||
top_left: DVec2,
|
||||
top_right: DVec2,
|
||||
bottom_left: DVec2,
|
||||
bottom_right: DVec2,
|
||||
}
|
||||
|
||||
impl Bbox {
|
||||
fn axis_aligned_bbox(&self) -> AxisAlignedBbox {
|
||||
let start_x = self.top_left.x.min(self.top_right.x).min(self.bottom_left.x).min(self.bottom_right.x);
|
||||
let start_y = self.top_left.y.min(self.top_right.y).min(self.bottom_left.y).min(self.bottom_right.y);
|
||||
let end_x = self.top_left.x.max(self.top_right.x).max(self.bottom_left.x).max(self.bottom_right.x);
|
||||
let end_y = self.top_left.y.max(self.top_right.y).max(self.bottom_left.y).max(self.bottom_right.y);
|
||||
|
||||
AxisAlignedBbox {
|
||||
start: DVec2::new(start_x, start_y),
|
||||
end: DVec2::new(end_x, end_y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_transformed_bounding_box(transform: DAffine2) -> Bbox {
|
||||
let top_left = DVec2::new(0., 1.);
|
||||
let top_right = DVec2::new(1., 1.);
|
||||
let bottom_left = DVec2::new(0., 0.);
|
||||
let bottom_right = DVec2::new(1., 0.);
|
||||
let transform = |p| transform.transform_point2(p);
|
||||
|
||||
Bbox {
|
||||
top_left: transform(top_left),
|
||||
top_right: transform(top_right),
|
||||
bottom_left: transform(bottom_left),
|
||||
bottom_right: transform(bottom_right),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlendImageNode<Second, MapFn> {
|
||||
second: Second,
|
||||
pub struct BlendImageNode<background, MapFn> {
|
||||
background: background,
|
||||
map_fn: MapFn,
|
||||
}
|
||||
|
||||
// TODO: Implement proper blending
|
||||
#[node_macro::node_fn(BlendImageNode)]
|
||||
fn blend_image<MapFn>(image: ImageFrame, second: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame
|
||||
fn blend_image<MapFn>(foreground: ImageFrame, mut background: ImageFrame, map_fn: &'any_input MapFn) -> ImageFrame
|
||||
where
|
||||
MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input,
|
||||
{
|
||||
let mut image = image;
|
||||
for (pixel, sec_pixel) in &mut image.image.data.iter_mut().zip(second.image.data.iter()) {
|
||||
*pixel = map_fn.eval((*pixel, *sec_pixel));
|
||||
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);
|
||||
|
||||
// Transforms a point from the background image to the forground image
|
||||
let bg_to_fg = DAffine2::from_scale(foreground_size) * foreground.transform.inverse() * background.transform * DAffine2::from_scale(1. / background_size);
|
||||
|
||||
// Footprint of the foreground image (0,0) (1, 1) in the background image space
|
||||
let bg_aabb = compute_transformed_bounding_box(background.transform.inverse() * foreground.transform).axis_aligned_bbox();
|
||||
|
||||
// Clamp the foreground image to the background image
|
||||
let start = (bg_aabb.start * background_size).max(DVec2::ZERO).as_uvec2();
|
||||
let end = (bg_aabb.end * background_size).min(background_size).as_uvec2();
|
||||
|
||||
for y in start.y..end.y {
|
||||
for x in start.x..end.x {
|
||||
let bg_point = DVec2::new(x as f64, y as f64);
|
||||
let fg_point = bg_to_fg.transform_point2(bg_point);
|
||||
if !((fg_point.cmpge(DVec2::ZERO) & fg_point.cmple(foreground_size)) == BVec2::new(true, true)) {
|
||||
//log::debug!("Skipping pixel at {:?}", dest_point);
|
||||
continue;
|
||||
}
|
||||
|
||||
let dst_pixel = background.get_mut(x as usize, y as usize);
|
||||
let src_pixel = foreground.sample(fg_point.x, fg_point.y);
|
||||
|
||||
*dst_pixel = map_fn.eval((src_pixel, *dst_pixel));
|
||||
}
|
||||
}
|
||||
image
|
||||
|
||||
background
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
||||
Reference in New Issue
Block a user