diff options
author | Omar Emara <mail@OmarEmara.dev> | 2022-08-10 11:30:27 +0300 |
---|---|---|
committer | Omar Emara <mail@OmarEmara.dev> | 2022-08-10 11:30:27 +0300 |
commit | 40c45985a924e2e2310d5c51cf399150d557792c (patch) | |
tree | fc38d5846230db8f2d62004600f323d50afce21d /source/blender/nodes | |
parent | c014021802c323c3ed55c3c283717975a3d93edf (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')
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); } |