Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmar Emara <mail@OmarEmara.dev>2022-08-10 11:30:27 +0300
committerOmar Emara <mail@OmarEmara.dev>2022-08-10 11:30:27 +0300
commit40c45985a924e2e2310d5c51cf399150d557792c (patch)
treefc38d5846230db8f2d62004600f323d50afce21d /source/blender/nodes
parentc014021802c323c3ed55c3c283717975a3d93edf (diff)
Realtime Compositor: Add basic distort nodes
This patch implements the following nodes for the realtime compositor: - Crop node. - Flip node. - Lens distort node. - Rotate node. - Transform node. - Translate node. Differential Revision: https://developer.blender.org/D15231 Reviewed By: Clement Foucault
Diffstat (limited to 'source/blender/nodes')
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_crop.cc171
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_flip.cc64
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_lensdist.cc207
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_rotate.cc54
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_transform.cc77
-rw-r--r--source/blender/nodes/composite/nodes/node_composite_translate.cc75
6 files changed, 633 insertions, 15 deletions
diff --git a/source/blender/nodes/composite/nodes/node_composite_crop.cc b/source/blender/nodes/composite/nodes/node_composite_crop.cc
index 823e1052dd0..d7331732fc7 100644
--- a/source/blender/nodes/composite/nodes/node_composite_crop.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_crop.cc
@@ -5,11 +5,22 @@
* \ingroup cmpnodes
*/
+#include "BLI_math_base.h"
+#include "BLI_math_vec_types.hh"
+
+#include "DNA_node_types.h"
+
#include "RNA_access.h"
#include "UI_interface.h"
#include "UI_resources.h"
+#include "GPU_shader.h"
+#include "GPU_texture.h"
+
+#include "COM_node_operation.hh"
+#include "COM_utilities.hh"
+
#include "node_composite_util.hh"
/* **************** Crop ******************** */
@@ -18,7 +29,9 @@ namespace blender::nodes::node_composite_crop_cc {
static void cmp_node_crop_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
+ b.add_input<decl::Color>(N_("Image"))
+ .default_value({1.0f, 1.0f, 1.0f, 1.0f})
+ .compositor_domain_priority(0);
b.add_output<decl::Color>(N_("Image"));
}
@@ -54,6 +67,161 @@ static void node_composit_buts_crop(uiLayout *layout, bContext *UNUSED(C), Point
}
}
+using namespace blender::realtime_compositor;
+
+class CropOperation : public NodeOperation {
+ public:
+ using NodeOperation::NodeOperation;
+
+ void execute() override
+ {
+ /* The operation does nothing, so just pass the input through. */
+ if (is_identity()) {
+ get_input("Image").pass_through(get_result("Image"));
+ return;
+ }
+
+ if (get_is_image_crop()) {
+ execute_image_crop();
+ }
+ else {
+ execute_alpha_crop();
+ }
+ }
+
+ /* Crop by replacing areas outside of the cropping bounds with zero alpha. The output have the
+ * same domain as the input image. */
+ void execute_alpha_crop()
+ {
+ GPUShader *shader = shader_manager().get("compositor_alpha_crop");
+ GPU_shader_bind(shader);
+
+ int2 lower_bound, upper_bound;
+ compute_cropping_bounds(lower_bound, upper_bound);
+ GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound);
+ GPU_shader_uniform_2iv(shader, "upper_bound", upper_bound);
+
+ const Result &input_image = get_input("Image");
+ input_image.bind_as_texture(shader, "input_tx");
+
+ const Domain domain = compute_domain();
+
+ Result &output_image = get_result("Image");
+ output_image.allocate_texture(domain);
+ output_image.bind_as_image(shader, "output_img");
+
+ compute_dispatch_threads_at_least(shader, domain.size);
+
+ input_image.unbind_as_texture();
+ output_image.unbind_as_image();
+ GPU_shader_unbind();
+ }
+
+ /* Crop the image into a new size that matches the cropping bounds. */
+ void execute_image_crop()
+ {
+ int2 lower_bound, upper_bound;
+ compute_cropping_bounds(lower_bound, upper_bound);
+
+ /* The image is cropped into nothing, so just return a single zero value. */
+ if (lower_bound.x == upper_bound.x || lower_bound.y == upper_bound.y) {
+ Result &result = get_result("Image");
+ result.allocate_invalid();
+ return;
+ }
+
+ GPUShader *shader = shader_manager().get("compositor_image_crop");
+ GPU_shader_bind(shader);
+
+ GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound);
+
+ const Result &input_image = get_input("Image");
+ input_image.bind_as_texture(shader, "input_tx");
+
+ const int2 size = upper_bound - lower_bound;
+
+ Result &output_image = get_result("Image");
+ output_image.allocate_texture(Domain(size, compute_domain().transformation));
+ output_image.bind_as_image(shader, "output_img");
+
+ compute_dispatch_threads_at_least(shader, size);
+
+ input_image.unbind_as_texture();
+ output_image.unbind_as_image();
+ GPU_shader_unbind();
+ }
+
+ /* If true, the image should actually be cropped into a new size. Otherwise, if false, the region
+ * outside of the cropping bounds will be set to a zero alpha value. */
+ bool get_is_image_crop()
+ {
+ return bnode().custom1;
+ }
+
+ bool get_is_relative()
+ {
+ return bnode().custom2;
+ }
+
+ NodeTwoXYs &get_node_two_xys()
+ {
+ return *static_cast<NodeTwoXYs *>(bnode().storage);
+ }
+
+ /* Returns true if the operation does nothing and the input can be passed through. */
+ bool is_identity()
+ {
+ const Result &input = get_input("Image");
+ /* Single value inputs can't be cropped and are returned as is. */
+ if (input.is_single_value()) {
+ return true;
+ }
+
+ int2 lower_bound, upper_bound;
+ compute_cropping_bounds(lower_bound, upper_bound);
+ const int2 input_size = input.domain().size;
+ /* The cropping bounds cover the whole image, so no cropping happens. */
+ if (lower_bound == int2(0) && upper_bound == input_size) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void compute_cropping_bounds(int2 &lower_bound, int2 &upper_bound)
+ {
+ const NodeTwoXYs &node_two_xys = get_node_two_xys();
+ const int2 input_size = get_input("Image").domain().size;
+
+ if (get_is_relative()) {
+ /* The cropping bounds are relative to the image size. The factors are in the [0, 1] range,
+ * so it is guaranteed that they won't go over the input image size. */
+ lower_bound.x = input_size.x * node_two_xys.fac_x1;
+ lower_bound.y = input_size.y * node_two_xys.fac_y2;
+ upper_bound.x = input_size.x * node_two_xys.fac_x2;
+ upper_bound.y = input_size.y * node_two_xys.fac_y1;
+ }
+ else {
+ /* Make sure the bounds don't go over the input image size. */
+ lower_bound.x = min_ii(node_two_xys.x1, input_size.x);
+ lower_bound.y = min_ii(node_two_xys.y2, input_size.y);
+ upper_bound.x = min_ii(node_two_xys.x2, input_size.x);
+ upper_bound.y = min_ii(node_two_xys.y1, input_size.y);
+ }
+
+ /* Make sure upper bound is actually higher than the lower bound. */
+ lower_bound.x = min_ii(lower_bound.x, upper_bound.x);
+ lower_bound.y = min_ii(lower_bound.y, upper_bound.y);
+ upper_bound.x = max_ii(lower_bound.x, upper_bound.x);
+ upper_bound.y = max_ii(lower_bound.y, upper_bound.y);
+ }
+};
+
+static NodeOperation *get_compositor_operation(Context &context, DNode node)
+{
+ return new CropOperation(context, node);
+}
+
} // namespace blender::nodes::node_composite_crop_cc
void register_node_type_cmp_crop()
@@ -67,6 +235,7 @@ void register_node_type_cmp_crop()
ntype.draw_buttons = file_ns::node_composit_buts_crop;
node_type_init(&ntype, file_ns::node_composit_init_crop);
node_type_storage(&ntype, "NodeTwoXYs", node_free_standard_storage, node_copy_standard_storage);
+ ntype.get_compositor_operation = file_ns::get_compositor_operation;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/composite/nodes/node_composite_flip.cc b/source/blender/nodes/composite/nodes/node_composite_flip.cc
index 37b9a2d020d..aaa2b565ed2 100644
--- a/source/blender/nodes/composite/nodes/node_composite_flip.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_flip.cc
@@ -5,9 +5,18 @@
* \ingroup cmpnodes
*/
+#include "BLI_assert.h"
+#include "BLI_utildefines.h"
+
#include "UI_interface.h"
#include "UI_resources.h"
+#include "GPU_shader.h"
+#include "GPU_texture.h"
+
+#include "COM_node_operation.hh"
+#include "COM_utilities.hh"
+
#include "node_composite_util.hh"
/* **************** Flip ******************** */
@@ -16,7 +25,9 @@ namespace blender::nodes::node_composite_flip_cc {
static void cmp_node_flip_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
+ b.add_input<decl::Color>(N_("Image"))
+ .default_value({1.0f, 1.0f, 1.0f, 1.0f})
+ .compositor_domain_priority(0);
b.add_output<decl::Color>(N_("Image"));
}
@@ -25,6 +36,56 @@ static void node_composit_buts_flip(uiLayout *layout, bContext *UNUSED(C), Point
uiItemR(layout, ptr, "axis", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
}
+using namespace blender::realtime_compositor;
+
+class FlipOperation : public NodeOperation {
+ public:
+ using NodeOperation::NodeOperation;
+
+ void execute() override
+ {
+ Result &input = get_input("Image");
+ Result &result = get_result("Image");
+
+ /* Can't flip a single value, pass it through to the output. */
+ if (input.is_single_value()) {
+ input.pass_through(result);
+ return;
+ }
+
+ GPUShader *shader = shader_manager().get("compositor_flip");
+ GPU_shader_bind(shader);
+
+ GPU_shader_uniform_1b(
+ shader, "flip_x", ELEM(get_flip_mode(), CMP_NODE_FLIP_X, CMP_NODE_FLIP_X_Y));
+ GPU_shader_uniform_1b(
+ shader, "flip_y", ELEM(get_flip_mode(), CMP_NODE_FLIP_Y, CMP_NODE_FLIP_X_Y));
+
+ input.bind_as_texture(shader, "input_tx");
+
+ const Domain domain = compute_domain();
+
+ result.allocate_texture(domain);
+ result.bind_as_image(shader, "output_img");
+
+ compute_dispatch_threads_at_least(shader, domain.size);
+
+ input.unbind_as_texture();
+ result.unbind_as_image();
+ GPU_shader_unbind();
+ }
+
+ CMPNodeFlipMode get_flip_mode()
+ {
+ return (CMPNodeFlipMode)bnode().custom1;
+ }
+};
+
+static NodeOperation *get_compositor_operation(Context &context, DNode node)
+{
+ return new FlipOperation(context, node);
+}
+
} // namespace blender::nodes::node_composite_flip_cc
void register_node_type_cmp_flip()
@@ -36,6 +97,7 @@ void register_node_type_cmp_flip()
cmp_node_type_base(&ntype, CMP_NODE_FLIP, "Flip", NODE_CLASS_DISTORT);
ntype.declare = file_ns::cmp_node_flip_declare;
ntype.draw_buttons = file_ns::node_composit_buts_flip;
+ ntype.get_compositor_operation = file_ns::get_compositor_operation;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/composite/nodes/node_composite_lensdist.cc b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc
index 593b7cc9b71..26477b02496 100644
--- a/source/blender/nodes/composite/nodes/node_composite_lensdist.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc
@@ -5,20 +5,48 @@
* \ingroup cmpnodes
*/
+#include "BLI_math_base.h"
+#include "BLI_math_vec_types.hh"
+
#include "RNA_access.h"
#include "UI_interface.h"
#include "UI_resources.h"
+#include "GPU_shader.h"
+#include "GPU_texture.h"
+
+#include "COM_node_operation.hh"
+#include "COM_utilities.hh"
+
#include "node_composite_util.hh"
+/* Distortion can't be exactly -1.0 as it will cause infinite pincushion distortion. */
+#define MINIMUM_DISTORTION -0.999f
+/* Arbitrary scaling factor for the dispersion input in projector distortion mode. */
+#define PROJECTOR_DISPERSION_SCALE 5.0f
+/* Arbitrary scaling factor for the dispersion input in screen distortion mode. */
+#define SCREEN_DISPERSION_SCALE 4.0f
+/* Arbitrary scaling factor for the distortion input. */
+#define DISTORTION_SCALE 4.0f
+
namespace blender::nodes::node_composite_lensdist_cc {
static void cmp_node_lensdist_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
- b.add_input<decl::Float>(N_("Distort")).default_value(0.0f).min(-0.999f).max(1.0f);
- b.add_input<decl::Float>(N_("Dispersion")).default_value(0.0f).min(0.0f).max(1.0f);
+ b.add_input<decl::Color>(N_("Image"))
+ .default_value({1.0f, 1.0f, 1.0f, 1.0f})
+ .compositor_domain_priority(0);
+ b.add_input<decl::Float>(N_("Distort"))
+ .default_value(0.0f)
+ .min(MINIMUM_DISTORTION)
+ .max(1.0f)
+ .compositor_expects_single_value();
+ b.add_input<decl::Float>(N_("Dispersion"))
+ .default_value(0.0f)
+ .min(0.0f)
+ .max(1.0f)
+ .compositor_expects_single_value();
b.add_output<decl::Color>(N_("Image"));
}
@@ -42,6 +70,178 @@ static void node_composit_buts_lensdist(uiLayout *layout, bContext *UNUSED(C), P
uiItemR(col, ptr, "use_fit", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
}
+using namespace blender::realtime_compositor;
+
+class LensDistortionOperation : public NodeOperation {
+ public:
+ using NodeOperation::NodeOperation;
+
+ void execute() override
+ {
+ if (is_identity()) {
+ get_input("Image").pass_through(get_result("Image"));
+ return;
+ }
+
+ if (get_is_projector()) {
+ execute_projector_distortion();
+ }
+ else {
+ execute_screen_distortion();
+ }
+ }
+
+ void execute_projector_distortion()
+ {
+ GPUShader *shader = shader_manager().get("compositor_projector_lens_distortion");
+ GPU_shader_bind(shader);
+
+ const Result &input_image = get_input("Image");
+ input_image.bind_as_texture(shader, "input_tx");
+
+ GPU_texture_filter_mode(input_image.texture(), true);
+ GPU_texture_wrap_mode(input_image.texture(), false, false);
+
+ const Domain domain = compute_domain();
+
+ const float dispersion = (get_dispersion() * PROJECTOR_DISPERSION_SCALE) / domain.size.x;
+ GPU_shader_uniform_1f(shader, "dispersion", dispersion);
+
+ Result &output_image = get_result("Image");
+ output_image.allocate_texture(domain);
+ output_image.bind_as_image(shader, "output_img");
+
+ compute_dispatch_threads_at_least(shader, domain.size);
+
+ input_image.unbind_as_texture();
+ output_image.unbind_as_image();
+ GPU_shader_unbind();
+ }
+
+ void execute_screen_distortion()
+ {
+ GPUShader *shader = shader_manager().get(get_screen_distortion_shader());
+ GPU_shader_bind(shader);
+
+ const Result &input_image = get_input("Image");
+ input_image.bind_as_texture(shader, "input_tx");
+
+ GPU_texture_filter_mode(input_image.texture(), true);
+ GPU_texture_wrap_mode(input_image.texture(), false, false);
+
+ const Domain domain = compute_domain();
+
+ const float3 chromatic_distortion = compute_chromatic_distortion();
+ GPU_shader_uniform_3fv(shader, "chromatic_distortion", chromatic_distortion);
+
+ GPU_shader_uniform_1f(shader, "scale", compute_scale());
+
+ Result &output_image = get_result("Image");
+ output_image.allocate_texture(domain);
+ output_image.bind_as_image(shader, "output_img");
+
+ compute_dispatch_threads_at_least(shader, domain.size);
+
+ input_image.unbind_as_texture();
+ output_image.unbind_as_image();
+ GPU_shader_unbind();
+ }
+
+ const char *get_screen_distortion_shader()
+ {
+ if (get_is_jitter()) {
+ return "compositor_screen_lens_distortion_jitter";
+ }
+ return "compositor_screen_lens_distortion";
+ }
+
+ float get_distortion()
+ {
+ const Result &input = get_input("Distort");
+ return clamp_f(input.get_float_value_default(0.0f), MINIMUM_DISTORTION, 1.0f);
+ }
+
+ float get_dispersion()
+ {
+ const Result &input = get_input("Dispersion");
+ return clamp_f(input.get_float_value_default(0.0f), 0.0f, 1.0f);
+ }
+
+ /* Get the distortion amount for each channel. The green channel has a distortion amount that
+ * matches that specified in the node inputs, while the red and blue channels have higher and
+ * lower distortion amounts respectively based on the dispersion value. */
+ float3 compute_chromatic_distortion()
+ {
+ const float green_distortion = get_distortion();
+ const float dispersion = get_dispersion() / SCREEN_DISPERSION_SCALE;
+ const float red_distortion = clamp_f(green_distortion + dispersion, MINIMUM_DISTORTION, 1.0f);
+ const float blue_distortion = clamp_f(green_distortion - dispersion, MINIMUM_DISTORTION, 1.0f);
+ return float3(red_distortion, green_distortion, blue_distortion) * DISTORTION_SCALE;
+ }
+
+ /* The distortion model model will distort the image in such a way that the result will no longer
+ * fit the domain of the original image, so we scale the image to account for that. If get_is_fit
+ * is false, then the scaling factor will be such that the furthest pixels horizontally and
+ * vertically are at the boundary of the image. Otherwise, if get_is_fit is true, the scaling
+ * factor will be such that the furthest pixels diagonally are at the corner of the image. */
+ float compute_scale()
+ {
+ const float3 distortion = compute_chromatic_distortion() / DISTORTION_SCALE;
+ const float maximum_distortion = max_fff(distortion[0], distortion[1], distortion[2]);
+
+ if (get_is_fit() && (maximum_distortion > 0.0f)) {
+ return 1.0f / (1.0f + 2.0f * maximum_distortion);
+ }
+ return 1.0f / (1.0f + maximum_distortion);
+ }
+
+ bool get_is_projector()
+ {
+ return get_node_lens_distortion().proj;
+ }
+
+ bool get_is_jitter()
+ {
+ return get_node_lens_distortion().jit;
+ }
+
+ bool get_is_fit()
+ {
+ return get_node_lens_distortion().fit;
+ }
+
+ NodeLensDist &get_node_lens_distortion()
+ {
+ return *static_cast<NodeLensDist *>(bnode().storage);
+ }
+
+ /* Returns true if the operation does nothing and the input can be passed through. */
+ bool is_identity()
+ {
+ /* The input is a single value and the operation does nothing. */
+ if (get_input("Image").is_single_value()) {
+ return true;
+ }
+
+ /* Projector have zero dispersion and does nothing. */
+ if (get_is_projector() && get_dispersion() == 0.0f) {
+ return true;
+ }
+
+ /* Both distortion and dispersion are zero and the operation does nothing. */
+ if (get_distortion() == 0.0f && get_dispersion() == 0.0f) {
+ return true;
+ }
+
+ return false;
+ }
+};
+
+static NodeOperation *get_compositor_operation(Context &context, DNode node)
+{
+ return new LensDistortionOperation(context, node);
+}
+
} // namespace blender::nodes::node_composite_lensdist_cc
void register_node_type_cmp_lensdist()
@@ -56,6 +256,7 @@ void register_node_type_cmp_lensdist()
node_type_init(&ntype, file_ns::node_composit_init_lensdist);
node_type_storage(
&ntype, "NodeLensDist", node_free_standard_storage, node_copy_standard_storage);
+ ntype.get_compositor_operation = file_ns::get_compositor_operation;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/composite/nodes/node_composite_rotate.cc b/source/blender/nodes/composite/nodes/node_composite_rotate.cc
index a083bc1837b..35caa3cd242 100644
--- a/source/blender/nodes/composite/nodes/node_composite_rotate.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_rotate.cc
@@ -5,9 +5,14 @@
* \ingroup cmpnodes
*/
+#include "BLI_assert.h"
+#include "BLI_float3x3.hh"
+
#include "UI_interface.h"
#include "UI_resources.h"
+#include "COM_node_operation.hh"
+
#include "node_composite_util.hh"
/* **************** Rotate ******************** */
@@ -16,12 +21,15 @@ namespace blender::nodes::node_composite_rotate_cc {
static void cmp_node_rotate_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
+ b.add_input<decl::Color>(N_("Image"))
+ .default_value({1.0f, 1.0f, 1.0f, 1.0f})
+ .compositor_domain_priority(0);
b.add_input<decl::Float>(N_("Degr"))
.default_value(0.0f)
.min(-10000.0f)
.max(10000.0f)
- .subtype(PROP_ANGLE);
+ .subtype(PROP_ANGLE)
+ .compositor_expects_single_value();
b.add_output<decl::Color>(N_("Image"));
}
@@ -35,6 +43,47 @@ static void node_composit_buts_rotate(uiLayout *layout, bContext *UNUSED(C), Poi
uiItemR(layout, ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
}
+using namespace blender::realtime_compositor;
+
+class RotateOperation : public NodeOperation {
+ public:
+ using NodeOperation::NodeOperation;
+
+ void execute() override
+ {
+ Result &input = get_input("Image");
+ Result &result = get_result("Image");
+ input.pass_through(result);
+
+ const float rotation = get_input("Degr").get_float_value_default(0.0f);
+
+ const float3x3 transformation = float3x3::from_rotation(rotation);
+
+ result.transform(transformation);
+ result.get_realization_options().interpolation = get_interpolation();
+ }
+
+ Interpolation get_interpolation()
+ {
+ switch (bnode().custom1) {
+ case 0:
+ return Interpolation::Nearest;
+ case 1:
+ return Interpolation::Bilinear;
+ case 2:
+ return Interpolation::Bicubic;
+ }
+
+ BLI_assert_unreachable();
+ return Interpolation::Nearest;
+ }
+};
+
+static NodeOperation *get_compositor_operation(Context &context, DNode node)
+{
+ return new RotateOperation(context, node);
+}
+
} // namespace blender::nodes::node_composite_rotate_cc
void register_node_type_cmp_rotate()
@@ -47,6 +96,7 @@ void register_node_type_cmp_rotate()
ntype.declare = file_ns::cmp_node_rotate_declare;
ntype.draw_buttons = file_ns::node_composit_buts_rotate;
node_type_init(&ntype, file_ns::node_composit_init_rotate);
+ ntype.get_compositor_operation = file_ns::get_compositor_operation;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/composite/nodes/node_composite_transform.cc b/source/blender/nodes/composite/nodes/node_composite_transform.cc
index fe72f5e33ca..7c5866d2d06 100644
--- a/source/blender/nodes/composite/nodes/node_composite_transform.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_transform.cc
@@ -5,9 +5,15 @@
* \ingroup cmpnodes
*/
+#include "BLI_assert.h"
+#include "BLI_float3x3.hh"
+#include "BLI_math_vector.h"
+
#include "UI_interface.h"
#include "UI_resources.h"
+#include "COM_node_operation.hh"
+
#include "node_composite_util.hh"
/* **************** Transform ******************** */
@@ -16,15 +22,30 @@ namespace blender::nodes::node_composite_transform_cc {
static void cmp_node_transform_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Color>(N_("Image")).default_value({0.8f, 0.8f, 0.8f, 1.0f});
- b.add_input<decl::Float>(N_("X")).default_value(0.0f).min(-10000.0f).max(10000.0f);
- b.add_input<decl::Float>(N_("Y")).default_value(0.0f).min(-10000.0f).max(10000.0f);
+ b.add_input<decl::Color>(N_("Image"))
+ .default_value({0.8f, 0.8f, 0.8f, 1.0f})
+ .compositor_domain_priority(0);
+ b.add_input<decl::Float>(N_("X"))
+ .default_value(0.0f)
+ .min(-10000.0f)
+ .max(10000.0f)
+ .compositor_expects_single_value();
+ b.add_input<decl::Float>(N_("Y"))
+ .default_value(0.0f)
+ .min(-10000.0f)
+ .max(10000.0f)
+ .compositor_expects_single_value();
b.add_input<decl::Float>(N_("Angle"))
.default_value(0.0f)
.min(-10000.0f)
.max(10000.0f)
- .subtype(PROP_ANGLE);
- b.add_input<decl::Float>(N_("Scale")).default_value(1.0f).min(0.0001f).max(CMP_SCALE_MAX);
+ .subtype(PROP_ANGLE)
+ .compositor_expects_single_value();
+ b.add_input<decl::Float>(N_("Scale"))
+ .default_value(1.0f)
+ .min(0.0001f)
+ .max(CMP_SCALE_MAX)
+ .compositor_expects_single_value();
b.add_output<decl::Color>(N_("Image"));
}
@@ -33,6 +54,51 @@ static void node_composit_buts_transform(uiLayout *layout, bContext *UNUSED(C),
uiItemR(layout, ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
}
+using namespace blender::realtime_compositor;
+
+class TransformOperation : public NodeOperation {
+ public:
+ using NodeOperation::NodeOperation;
+
+ void execute() override
+ {
+ Result &input = get_input("Image");
+ Result &result = get_result("Image");
+ input.pass_through(result);
+
+ const float2 translation = float2(get_input("X").get_float_value_default(0.0f),
+ get_input("Y").get_float_value_default(0.0f));
+ const float rotation = get_input("Angle").get_float_value_default(0.0f);
+ const float2 scale = float2(get_input("Scale").get_float_value_default(1.0f));
+
+ const float3x3 transformation = float3x3::from_translation_rotation_scale(
+ translation, rotation, scale);
+
+ result.transform(transformation);
+ result.get_realization_options().interpolation = get_interpolation();
+ }
+
+ Interpolation get_interpolation()
+ {
+ switch (bnode().custom1) {
+ case 0:
+ return Interpolation::Nearest;
+ case 1:
+ return Interpolation::Bilinear;
+ case 2:
+ return Interpolation::Bicubic;
+ }
+
+ BLI_assert_unreachable();
+ return Interpolation::Nearest;
+ }
+};
+
+static NodeOperation *get_compositor_operation(Context &context, DNode node)
+{
+ return new TransformOperation(context, node);
+}
+
} // namespace blender::nodes::node_composite_transform_cc
void register_node_type_cmp_transform()
@@ -44,6 +110,7 @@ void register_node_type_cmp_transform()
cmp_node_type_base(&ntype, CMP_NODE_TRANSFORM, "Transform", NODE_CLASS_DISTORT);
ntype.declare = file_ns::cmp_node_transform_declare;
ntype.draw_buttons = file_ns::node_composit_buts_transform;
+ ntype.get_compositor_operation = file_ns::get_compositor_operation;
nodeRegisterType(&ntype);
}
diff --git a/source/blender/nodes/composite/nodes/node_composite_translate.cc b/source/blender/nodes/composite/nodes/node_composite_translate.cc
index bbdc8ca4d31..fbd53b8310f 100644
--- a/source/blender/nodes/composite/nodes/node_composite_translate.cc
+++ b/source/blender/nodes/composite/nodes/node_composite_translate.cc
@@ -5,9 +5,14 @@
* \ingroup cmpnodes
*/
+#include "BLI_float3x3.hh"
+#include "BLI_math_vec_types.hh"
+
#include "UI_interface.h"
#include "UI_resources.h"
+#include "COM_node_operation.hh"
+
#include "node_composite_util.hh"
/* **************** Translate ******************** */
@@ -16,9 +21,19 @@ namespace blender::nodes::node_composite_translate_cc {
static void cmp_node_translate_declare(NodeDeclarationBuilder &b)
{
- b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
- b.add_input<decl::Float>(N_("X")).default_value(0.0f).min(-10000.0f).max(10000.0f);
- b.add_input<decl::Float>(N_("Y")).default_value(0.0f).min(-10000.0f).max(10000.0f);
+ b.add_input<decl::Color>(N_("Image"))
+ .default_value({1.0f, 1.0f, 1.0f, 1.0f})
+ .compositor_domain_priority(0);
+ b.add_input<decl::Float>(N_("X"))
+ .default_value(0.0f)
+ .min(-10000.0f)
+ .max(10000.0f)
+ .compositor_expects_single_value();
+ b.add_input<decl::Float>(N_("Y"))
+ .default_value(0.0f)
+ .min(-10000.0f)
+ .max(10000.0f)
+ .compositor_expects_single_value();
b.add_output<decl::Color>(N_("Image"));
}
@@ -34,6 +49,59 @@ static void node_composit_buts_translate(uiLayout *layout, bContext *UNUSED(C),
uiItemR(layout, ptr, "wrap_axis", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
}
+using namespace blender::realtime_compositor;
+
+class TranslateOperation : public NodeOperation {
+ public:
+ using NodeOperation::NodeOperation;
+
+ void execute() override
+ {
+ Result &input = get_input("Image");
+ Result &result = get_result("Image");
+ input.pass_through(result);
+
+ float x = get_input("X").get_float_value_default(0.0f);
+ float y = get_input("Y").get_float_value_default(0.0f);
+ if (get_use_relative()) {
+ x *= input.domain().size.x;
+ y *= input.domain().size.y;
+ }
+
+ const float2 translation = float2(x, y);
+ const float3x3 transformation = float3x3::from_translation(translation);
+
+ result.transform(transformation);
+ result.get_realization_options().repeat_x = get_repeat_x();
+ result.get_realization_options().repeat_y = get_repeat_y();
+ }
+
+ NodeTranslateData &get_node_translate()
+ {
+ return *static_cast<NodeTranslateData *>(bnode().storage);
+ }
+
+ bool get_use_relative()
+ {
+ return get_node_translate().relative;
+ }
+
+ bool get_repeat_x()
+ {
+ return ELEM(get_node_translate().wrap_axis, CMP_NODE_WRAP_X, CMP_NODE_WRAP_XY);
+ }
+
+ bool get_repeat_y()
+ {
+ return ELEM(get_node_translate().wrap_axis, CMP_NODE_WRAP_Y, CMP_NODE_WRAP_XY);
+ }
+};
+
+static NodeOperation *get_compositor_operation(Context &context, DNode node)
+{
+ return new TranslateOperation(context, node);
+}
+
} // namespace blender::nodes::node_composite_translate_cc
void register_node_type_cmp_translate()
@@ -48,6 +116,7 @@ void register_node_type_cmp_translate()
node_type_init(&ntype, file_ns::node_composit_init_translate);
node_type_storage(
&ntype, "NodeTranslateData", node_free_standard_storage, node_copy_standard_storage);
+ ntype.get_compositor_operation = file_ns::get_compositor_operation;
nodeRegisterType(&ntype);
}