From 064167fce70e3d7c382c374334a1bd0b520fe9fe Mon Sep 17 00:00:00 2001 From: Manuel Castilla Date: Mon, 23 Aug 2021 15:30:01 +0200 Subject: Compositor: Full frame transform nodes Adds full frame implementation to "Rotate", "Transform" and "Stabilize2D" nodes. To avoid sampling twice when concatenating scale and rotate operations, a `TransformOperation` is implemented with all the functionality. The nodes have no functional changes. Part of T88150. Reviewed By: jbakker Differential Revision: https://developer.blender.org/D12165 --- source/blender/compositor/CMakeLists.txt | 2 + source/blender/compositor/COM_defines.h | 2 + source/blender/compositor/nodes/COM_RotateNode.cc | 25 +++- .../compositor/nodes/COM_Stabilize2dNode.cc | 100 ++++++++----- .../blender/compositor/nodes/COM_TransformNode.cc | 61 +++++--- .../compositor/operations/COM_RotateOperation.cc | 86 +++++++++++- .../compositor/operations/COM_RotateOperation.h | 37 ++++- .../compositor/operations/COM_ScaleOperation.cc | 13 +- .../compositor/operations/COM_ScaleOperation.h | 9 ++ .../operations/COM_TransformOperation.cc | 156 +++++++++++++++++++++ .../compositor/operations/COM_TransformOperation.h | 87 ++++++++++++ 11 files changed, 501 insertions(+), 77 deletions(-) create mode 100644 source/blender/compositor/operations/COM_TransformOperation.cc create mode 100644 source/blender/compositor/operations/COM_TransformOperation.h (limited to 'source') diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index ee287c65fe9..dba2d1e1e67 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -515,6 +515,8 @@ set(SRC operations/COM_ScaleOperation.h operations/COM_ScreenLensDistortionOperation.cc operations/COM_ScreenLensDistortionOperation.h + operations/COM_TransformOperation.cc + operations/COM_TransformOperation.h operations/COM_TranslateOperation.cc operations/COM_TranslateOperation.h operations/COM_WrapOperation.cc diff --git a/source/blender/compositor/COM_defines.h b/source/blender/compositor/COM_defines.h index 40a1e0da2a8..ee9bea7b2c6 100644 --- a/source/blender/compositor/COM_defines.h +++ b/source/blender/compositor/COM_defines.h @@ -119,6 +119,8 @@ constexpr float COM_PREVIEW_SIZE = 140.f; constexpr float COM_RULE_OF_THIRDS_DIVIDER = 100.0f; constexpr float COM_BLUR_BOKEH_PIXELS = 512; +constexpr rcti COM_SINGLE_ELEM_AREA = {0, 1, 0, 1}; + constexpr IndexRange XRange(const rcti &area) { return IndexRange(area.xmin, area.xmax - area.xmin); diff --git a/source/blender/compositor/nodes/COM_RotateNode.cc b/source/blender/compositor/nodes/COM_RotateNode.cc index af5baa733dc..c2fd8ed5594 100644 --- a/source/blender/compositor/nodes/COM_RotateNode.cc +++ b/source/blender/compositor/nodes/COM_RotateNode.cc @@ -30,20 +30,31 @@ RotateNode::RotateNode(bNode *editorNode) : Node(editorNode) } void RotateNode::convertToOperations(NodeConverter &converter, - const CompositorContext & /*context*/) const + const CompositorContext &context) const { NodeInput *inputSocket = this->getInputSocket(0); NodeInput *inputDegreeSocket = this->getInputSocket(1); NodeOutput *outputSocket = this->getOutputSocket(0); RotateOperation *operation = new RotateOperation(); - SetSamplerOperation *sampler = new SetSamplerOperation(); - sampler->setSampler((PixelSampler)this->getbNode()->custom1); - - converter.addOperation(sampler); converter.addOperation(operation); - converter.addLink(sampler->getOutputSocket(), operation->getInputSocket(0)); - converter.mapInputSocket(inputSocket, sampler->getInputSocket(0)); + PixelSampler sampler = (PixelSampler)this->getbNode()->custom1; + switch (context.get_execution_model()) { + case eExecutionModel::Tiled: { + SetSamplerOperation *sampler_op = new SetSamplerOperation(); + sampler_op->setSampler(sampler); + converter.addOperation(sampler_op); + converter.addLink(sampler_op->getOutputSocket(), operation->getInputSocket(0)); + converter.mapInputSocket(inputSocket, sampler_op->getInputSocket(0)); + break; + } + case eExecutionModel::FullFrame: { + operation->set_sampler(sampler); + converter.mapInputSocket(inputSocket, operation->getInputSocket(0)); + break; + } + } + converter.mapInputSocket(inputDegreeSocket, operation->getInputSocket(1)); converter.mapOutputSocket(outputSocket, operation->getOutputSocket(0)); } diff --git a/source/blender/compositor/nodes/COM_Stabilize2dNode.cc b/source/blender/compositor/nodes/COM_Stabilize2dNode.cc index 0262f653d1a..7b2388bebca 100644 --- a/source/blender/compositor/nodes/COM_Stabilize2dNode.cc +++ b/source/blender/compositor/nodes/COM_Stabilize2dNode.cc @@ -22,6 +22,7 @@ #include "COM_RotateOperation.h" #include "COM_ScaleOperation.h" #include "COM_SetSamplerOperation.h" +#include "COM_TransformOperation.h" #include "COM_TranslateOperation.h" #include "BKE_tracking.h" @@ -42,18 +43,12 @@ void Stabilize2dNode::convertToOperations(NodeConverter &converter, NodeInput *imageInput = this->getInputSocket(0); MovieClip *clip = (MovieClip *)editorNode->id; bool invert = (editorNode->custom2 & CMP_NODEFLAG_STABILIZE_INVERSE) != 0; + const PixelSampler sampler = (PixelSampler)editorNode->custom1; - ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); - scaleOperation->setSampler((PixelSampler)editorNode->custom1); - RotateOperation *rotateOperation = new RotateOperation(); - rotateOperation->setDoDegree2RadConversion(false); - TranslateOperation *translateOperation = new TranslateOperation(); MovieClipAttributeOperation *scaleAttribute = new MovieClipAttributeOperation(); MovieClipAttributeOperation *angleAttribute = new MovieClipAttributeOperation(); MovieClipAttributeOperation *xAttribute = new MovieClipAttributeOperation(); MovieClipAttributeOperation *yAttribute = new MovieClipAttributeOperation(); - SetSamplerOperation *psoperation = new SetSamplerOperation(); - psoperation->setSampler((PixelSampler)editorNode->custom1); scaleAttribute->setAttribute(MCA_SCALE); scaleAttribute->setFramenumber(context.getFramenumber()); @@ -79,38 +74,67 @@ void Stabilize2dNode::convertToOperations(NodeConverter &converter, converter.addOperation(angleAttribute); converter.addOperation(xAttribute); converter.addOperation(yAttribute); - converter.addOperation(scaleOperation); - converter.addOperation(translateOperation); - converter.addOperation(rotateOperation); - converter.addOperation(psoperation); - converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(1)); - converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(2)); - - converter.addLink(angleAttribute->getOutputSocket(), rotateOperation->getInputSocket(1)); - - converter.addLink(xAttribute->getOutputSocket(), translateOperation->getInputSocket(1)); - converter.addLink(yAttribute->getOutputSocket(), translateOperation->getInputSocket(2)); - - converter.mapOutputSocket(getOutputSocket(), psoperation->getOutputSocket()); - - if (invert) { - // Translate -> Rotate -> Scale. - converter.mapInputSocket(imageInput, translateOperation->getInputSocket(0)); - - converter.addLink(translateOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); - converter.addLink(rotateOperation->getOutputSocket(), scaleOperation->getInputSocket(0)); - - converter.addLink(scaleOperation->getOutputSocket(), psoperation->getInputSocket(0)); - } - else { - // Scale -> Rotate -> Translate. - converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0)); - - converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); - converter.addLink(rotateOperation->getOutputSocket(), translateOperation->getInputSocket(0)); - - converter.addLink(translateOperation->getOutputSocket(), psoperation->getInputSocket(0)); + switch (context.get_execution_model()) { + case eExecutionModel::Tiled: { + ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); + scaleOperation->setSampler(sampler); + RotateOperation *rotateOperation = new RotateOperation(); + rotateOperation->setDoDegree2RadConversion(false); + TranslateOperation *translateOperation = new TranslateOperation(); + SetSamplerOperation *psoperation = new SetSamplerOperation(); + psoperation->setSampler(sampler); + + converter.addOperation(scaleOperation); + converter.addOperation(translateOperation); + converter.addOperation(rotateOperation); + converter.addOperation(psoperation); + + converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(1)); + converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(2)); + + converter.addLink(angleAttribute->getOutputSocket(), rotateOperation->getInputSocket(1)); + + converter.addLink(xAttribute->getOutputSocket(), translateOperation->getInputSocket(1)); + converter.addLink(yAttribute->getOutputSocket(), translateOperation->getInputSocket(2)); + + converter.mapOutputSocket(getOutputSocket(), psoperation->getOutputSocket()); + + if (invert) { + // Translate -> Rotate -> Scale. + converter.mapInputSocket(imageInput, translateOperation->getInputSocket(0)); + + converter.addLink(translateOperation->getOutputSocket(), + rotateOperation->getInputSocket(0)); + converter.addLink(rotateOperation->getOutputSocket(), scaleOperation->getInputSocket(0)); + + converter.addLink(scaleOperation->getOutputSocket(), psoperation->getInputSocket(0)); + } + else { + // Scale -> Rotate -> Translate. + converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0)); + + converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); + converter.addLink(rotateOperation->getOutputSocket(), + translateOperation->getInputSocket(0)); + + converter.addLink(translateOperation->getOutputSocket(), psoperation->getInputSocket(0)); + } + break; + } + case eExecutionModel::FullFrame: { + TransformOperation *transform_op = new TransformOperation(); + transform_op->set_sampler(sampler); + transform_op->set_convert_rotate_degree_to_rad(false); + transform_op->set_invert(invert); + converter.addOperation(transform_op); + converter.mapInputSocket(imageInput, transform_op->getInputSocket(0)); + converter.addLink(xAttribute->getOutputSocket(), transform_op->getInputSocket(1)); + converter.addLink(yAttribute->getOutputSocket(), transform_op->getInputSocket(2)); + converter.addLink(angleAttribute->getOutputSocket(), transform_op->getInputSocket(3)); + converter.addLink(scaleAttribute->getOutputSocket(), transform_op->getInputSocket(4)); + converter.mapOutputSocket(getOutputSocket(), transform_op->getOutputSocket()); + } } } diff --git a/source/blender/compositor/nodes/COM_TransformNode.cc b/source/blender/compositor/nodes/COM_TransformNode.cc index e1deaf616a4..d2fb7b54633 100644 --- a/source/blender/compositor/nodes/COM_TransformNode.cc +++ b/source/blender/compositor/nodes/COM_TransformNode.cc @@ -22,6 +22,7 @@ #include "COM_ScaleOperation.h" #include "COM_SetSamplerOperation.h" #include "COM_SetValueOperation.h" +#include "COM_TransformOperation.h" #include "COM_TranslateOperation.h" namespace blender::compositor { @@ -32,7 +33,7 @@ TransformNode::TransformNode(bNode *editorNode) : Node(editorNode) } void TransformNode::convertToOperations(NodeConverter &converter, - const CompositorContext & /*context*/) const + const CompositorContext &context) const { NodeInput *imageInput = this->getInputSocket(0); NodeInput *xInput = this->getInputSocket(1); @@ -40,33 +41,51 @@ void TransformNode::convertToOperations(NodeConverter &converter, NodeInput *angleInput = this->getInputSocket(3); NodeInput *scaleInput = this->getInputSocket(4); - ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); - converter.addOperation(scaleOperation); + switch (context.get_execution_model()) { + case eExecutionModel::Tiled: { + ScaleRelativeOperation *scaleOperation = new ScaleRelativeOperation(); + converter.addOperation(scaleOperation); - RotateOperation *rotateOperation = new RotateOperation(); - rotateOperation->setDoDegree2RadConversion(false); - converter.addOperation(rotateOperation); + RotateOperation *rotateOperation = new RotateOperation(); + rotateOperation->setDoDegree2RadConversion(false); + converter.addOperation(rotateOperation); - TranslateOperation *translateOperation = new TranslateOperation(); - converter.addOperation(translateOperation); + TranslateOperation *translateOperation = new TranslateOperation(); + converter.addOperation(translateOperation); - SetSamplerOperation *sampler = new SetSamplerOperation(); - sampler->setSampler((PixelSampler)this->getbNode()->custom1); - converter.addOperation(sampler); + SetSamplerOperation *sampler = new SetSamplerOperation(); + sampler->setSampler((PixelSampler)this->getbNode()->custom1); + converter.addOperation(sampler); - converter.mapInputSocket(imageInput, sampler->getInputSocket(0)); - converter.addLink(sampler->getOutputSocket(), scaleOperation->getInputSocket(0)); - converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(1)); - converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(2)); // xscale = yscale + converter.mapInputSocket(imageInput, sampler->getInputSocket(0)); + converter.addLink(sampler->getOutputSocket(), scaleOperation->getInputSocket(0)); + converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(1)); + converter.mapInputSocket(scaleInput, scaleOperation->getInputSocket(2)); // xscale = yscale - converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); - converter.mapInputSocket(angleInput, rotateOperation->getInputSocket(1)); + converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0)); + converter.mapInputSocket(angleInput, rotateOperation->getInputSocket(1)); - converter.addLink(rotateOperation->getOutputSocket(), translateOperation->getInputSocket(0)); - converter.mapInputSocket(xInput, translateOperation->getInputSocket(1)); - converter.mapInputSocket(yInput, translateOperation->getInputSocket(2)); + converter.addLink(rotateOperation->getOutputSocket(), translateOperation->getInputSocket(0)); + converter.mapInputSocket(xInput, translateOperation->getInputSocket(1)); + converter.mapInputSocket(yInput, translateOperation->getInputSocket(2)); - converter.mapOutputSocket(getOutputSocket(), translateOperation->getOutputSocket()); + converter.mapOutputSocket(getOutputSocket(), translateOperation->getOutputSocket()); + break; + } + case eExecutionModel::FullFrame: { + TransformOperation *op = new TransformOperation(); + op->set_sampler((PixelSampler)this->getbNode()->custom1); + converter.addOperation(op); + + converter.mapInputSocket(imageInput, op->getInputSocket(0)); + converter.mapInputSocket(xInput, op->getInputSocket(1)); + converter.mapInputSocket(yInput, op->getInputSocket(2)); + converter.mapInputSocket(angleInput, op->getInputSocket(3)); + converter.mapInputSocket(scaleInput, op->getInputSocket(4)); + converter.mapOutputSocket(getOutputSocket(), op->getOutputSocket()); + break; + } + } } } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_RotateOperation.cc b/source/blender/compositor/operations/COM_RotateOperation.cc index 4fb3d324992..e3c482c15cb 100644 --- a/source/blender/compositor/operations/COM_RotateOperation.cc +++ b/source/blender/compositor/operations/COM_RotateOperation.cc @@ -17,6 +17,8 @@ */ #include "COM_RotateOperation.h" +#include "COM_ConstantOperation.h" + #include "BLI_math.h" namespace blender::compositor { @@ -31,13 +33,50 @@ RotateOperation::RotateOperation() this->m_degreeSocket = nullptr; this->m_doDegree2RadConversion = false; this->m_isDegreeSet = false; + sampler_ = PixelSampler::Bilinear; +} + +void RotateOperation::get_area_rotation_bounds(const rcti &area, + const float center_x, + const float center_y, + const float sine, + const float cosine, + rcti &r_bounds) +{ + const float dxmin = area.xmin - center_x; + const float dymin = area.ymin - center_y; + const float dxmax = area.xmax - center_x; + const float dymax = area.ymax - center_y; + + const float x1 = center_x + (cosine * dxmin + sine * dymin); + const float x2 = center_x + (cosine * dxmax + sine * dymin); + const float x3 = center_x + (cosine * dxmin + sine * dymax); + const float x4 = center_x + (cosine * dxmax + sine * dymax); + const float y1 = center_y + (-sine * dxmin + cosine * dymin); + const float y2 = center_y + (-sine * dxmax + cosine * dymin); + const float y3 = center_y + (-sine * dxmin + cosine * dymax); + const float y4 = center_y + (-sine * dxmax + cosine * dymax); + const float minx = MIN2(x1, MIN2(x2, MIN2(x3, x4))); + const float maxx = MAX2(x1, MAX2(x2, MAX2(x3, x4))); + const float miny = MIN2(y1, MIN2(y2, MIN2(y3, y4))); + const float maxy = MAX2(y1, MAX2(y2, MAX2(y3, y4))); + + r_bounds.xmin = floor(minx); + r_bounds.xmax = ceil(maxx); + r_bounds.ymin = floor(miny); + r_bounds.ymax = ceil(maxy); +} + +void RotateOperation::init_data() +{ + this->m_centerX = (getWidth() - 1) / 2.0; + this->m_centerY = (getHeight() - 1) / 2.0; } + void RotateOperation::initExecution() { this->m_imageSocket = this->getInputSocketReader(0); this->m_degreeSocket = this->getInputSocketReader(1); - this->m_centerX = (getWidth() - 1) / 2.0; - this->m_centerY = (getHeight() - 1) / 2.0; } void RotateOperation::deinitExecution() @@ -50,7 +89,19 @@ inline void RotateOperation::ensureDegree() { if (!this->m_isDegreeSet) { float degree[4]; - this->m_degreeSocket->readSampled(degree, 0, 0, PixelSampler::Nearest); + switch (execution_model_) { + case eExecutionModel::Tiled: + this->m_degreeSocket->readSampled(degree, 0, 0, PixelSampler::Nearest); + break; + case eExecutionModel::FullFrame: + NodeOperation *degree_op = getInputOperation(DEGREE_INPUT_INDEX); + const bool is_constant_degree = degree_op->get_flags().is_constant_operation; + degree[0] = is_constant_degree ? + static_cast(degree_op)->get_constant_elem()[0] : + 0.0f; + break; + } + double rad; if (this->m_doDegree2RadConversion) { rad = DEG2RAD((double)degree[0]); @@ -108,4 +159,33 @@ bool RotateOperation::determineDependingAreaOfInterest(rcti *input, return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); } +void RotateOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + if (input_idx == DEGREE_INPUT_INDEX) { + /* Degrees input is always used as constant. */ + r_input_area = COM_SINGLE_ELEM_AREA; + return; + } + + ensureDegree(); + get_area_rotation_bounds(output_area, m_centerX, m_centerY, m_sine, m_cosine, r_input_area); + expand_area_for_sampler(r_input_area, sampler_); +} + +void RotateOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) +{ + ensureDegree(); + const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX]; + for (BuffersIterator it = output->iterate_with({}, area); !it.is_end(); ++it) { + float x = it.x; + float y = it.y; + rotate_coords(x, y, m_centerX, m_centerY, m_sine, m_cosine); + input_img->read_elem_sampled(x, y, sampler_, it.out); + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_RotateOperation.h b/source/blender/compositor/operations/COM_RotateOperation.h index d76507f9816..f0de699f9ce 100644 --- a/source/blender/compositor/operations/COM_RotateOperation.h +++ b/source/blender/compositor/operations/COM_RotateOperation.h @@ -18,12 +18,15 @@ #pragma once -#include "COM_NodeOperation.h" +#include "COM_MultiThreadedOperation.h" namespace blender::compositor { -class RotateOperation : public NodeOperation { +class RotateOperation : public MultiThreadedOperation { private: + constexpr static int IMAGE_INPUT_INDEX = 0; + constexpr static int DEGREE_INPUT_INDEX = 1; + SocketReader *m_imageSocket; SocketReader *m_degreeSocket; float m_centerX; @@ -32,21 +35,51 @@ class RotateOperation : public NodeOperation { float m_sine; bool m_doDegree2RadConversion; bool m_isDegreeSet; + PixelSampler sampler_; public: RotateOperation(); + + static void rotate_coords( + float &x, float &y, float center_x, float center_y, float sine, float cosine) + { + const float dx = x - center_x; + const float dy = y - center_y; + x = center_x + (cosine * dx + sine * dy); + y = center_y + (-sine * dx + cosine * dy); + } + + static void get_area_rotation_bounds(const rcti &area, + const float center_x, + const float center_y, + const float sine, + const float cosine, + rcti &r_bounds); + bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) override; void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override; + void init_data() override; void initExecution() override; void deinitExecution() override; + void setDoDegree2RadConversion(bool abool) { this->m_doDegree2RadConversion = abool; } + void set_sampler(PixelSampler sampler) + { + sampler_ = sampler; + } + void ensureDegree(); + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) override; }; } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_ScaleOperation.cc b/source/blender/compositor/operations/COM_ScaleOperation.cc index 5410b2c832a..ef34bc7bee6 100644 --- a/source/blender/compositor/operations/COM_ScaleOperation.cc +++ b/source/blender/compositor/operations/COM_ScaleOperation.cc @@ -74,17 +74,18 @@ float ScaleOperation::get_constant_scale_y() return get_constant_scale(2, get_relative_scale_y_factor()); } -BLI_INLINE float scale_coord(const int coord, const float center, const float relative_scale) +void ScaleOperation::scale_area( + rcti &rect, float center_x, float center_y, float scale_x, float scale_y) { - return center + (coord - center) / relative_scale; + rect.xmin = scale_coord(rect.xmin, center_x, scale_x); + rect.xmax = scale_coord(rect.xmax, center_x, scale_x); + rect.ymin = scale_coord(rect.ymin, center_y, scale_y); + rect.ymax = scale_coord(rect.ymax, center_y, scale_y); } void ScaleOperation::scale_area(rcti &rect, float scale_x, float scale_y) { - rect.xmin = scale_coord(rect.xmin, m_centerX, scale_x); - rect.xmax = scale_coord(rect.xmax, m_centerX, scale_x); - rect.ymin = scale_coord(rect.ymin, m_centerY, scale_y); - rect.ymax = scale_coord(rect.ymax, m_centerY, scale_y); + scale_area(rect, m_centerX, m_centerY, scale_x, scale_y); } void ScaleOperation::init_data() diff --git a/source/blender/compositor/operations/COM_ScaleOperation.h b/source/blender/compositor/operations/COM_ScaleOperation.h index 62a2cabc8e6..65762d1ce62 100644 --- a/source/blender/compositor/operations/COM_ScaleOperation.h +++ b/source/blender/compositor/operations/COM_ScaleOperation.h @@ -46,6 +46,9 @@ class BaseScaleOperation : public MultiThreadedOperation { }; class ScaleOperation : public BaseScaleOperation { + public: + static constexpr float MIN_SCALE = 0.0001f; + protected: SocketReader *m_inputOperation; SocketReader *m_inputXOperation; @@ -57,6 +60,12 @@ class ScaleOperation : public BaseScaleOperation { ScaleOperation(); ScaleOperation(DataType data_type); + static float scale_coord(const float coord, const float center, const float relative_scale) + { + return center + (coord - center) / MAX2(relative_scale, MIN_SCALE); + } + static void scale_area(rcti &rect, float center_x, float center_y, float scale_x, float scale_y); + void init_data() override; void initExecution() override; void deinitExecution() override; diff --git a/source/blender/compositor/operations/COM_TransformOperation.cc b/source/blender/compositor/operations/COM_TransformOperation.cc new file mode 100644 index 00000000000..2feaa0ae16d --- /dev/null +++ b/source/blender/compositor/operations/COM_TransformOperation.cc @@ -0,0 +1,156 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#include "COM_TransformOperation.h" +#include "COM_ConstantOperation.h" +#include "COM_RotateOperation.h" +#include "COM_ScaleOperation.h" + +#include "BLI_math.h" + +namespace blender::compositor { + +TransformOperation::TransformOperation() +{ + addInputSocket(DataType::Color); + addInputSocket(DataType::Value); + addInputSocket(DataType::Value); + addInputSocket(DataType::Value); + addInputSocket(DataType::Value); + addOutputSocket(DataType::Color); + translate_factor_x_ = 1.0f; + translate_factor_y_ = 1.0f; + convert_degree_to_rad_ = false; + sampler_ = PixelSampler::Bilinear; + invert_ = false; +} + +void TransformOperation::init_data() +{ + /* Translation. */ + translate_x_ = 0; + NodeOperation *x_op = getInputOperation(X_INPUT_INDEX); + if (x_op->get_flags().is_constant_operation) { + translate_x_ = static_cast(x_op)->get_constant_elem()[0] * + translate_factor_x_; + } + translate_y_ = 0; + NodeOperation *y_op = getInputOperation(Y_INPUT_INDEX); + if (y_op->get_flags().is_constant_operation) { + translate_y_ = static_cast(y_op)->get_constant_elem()[0] * + translate_factor_y_; + } + + /* Scaling. */ + scale_center_x_ = getWidth() / 2.0; + scale_center_y_ = getHeight() / 2.0; + constant_scale_ = 1.0f; + NodeOperation *scale_op = getInputOperation(SCALE_INPUT_INDEX); + if (scale_op->get_flags().is_constant_operation) { + constant_scale_ = static_cast(scale_op)->get_constant_elem()[0]; + } + + /* Rotation. */ + rotate_center_x_ = (getWidth() - 1.0) / 2.0; + rotate_center_y_ = (getHeight() - 1.0) / 2.0; + NodeOperation *degree_op = getInputOperation(DEGREE_INPUT_INDEX); + const bool is_constant_degree = degree_op->get_flags().is_constant_operation; + const float degree = is_constant_degree ? + static_cast(degree_op)->get_constant_elem()[0] : + 0.0f; + const double rad = convert_degree_to_rad_ ? DEG2RAD((double)degree) : degree; + rotate_cosine_ = cos(rad); + rotate_sine_ = sin(rad); +} + +void TransformOperation::get_area_of_interest(const int input_idx, + const rcti &output_area, + rcti &r_input_area) +{ + switch (input_idx) { + case IMAGE_INPUT_INDEX: { + BLI_rcti_translate(&r_input_area, translate_x_, translate_y_); + ScaleOperation::scale_area( + r_input_area, scale_center_x_, scale_center_y_, constant_scale_, constant_scale_); + RotateOperation::get_area_rotation_bounds(r_input_area, + rotate_center_x_, + rotate_center_y_, + rotate_sine_, + rotate_cosine_, + r_input_area); + expand_area_for_sampler(r_input_area, sampler_); + break; + } + case X_INPUT_INDEX: + case Y_INPUT_INDEX: + case DEGREE_INPUT_INDEX: { + r_input_area = COM_SINGLE_ELEM_AREA; + break; + } + case SCALE_INPUT_INDEX: { + r_input_area = output_area; + break; + } + } +} + +void TransformOperation::update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) +{ + const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX]; + MemoryBuffer *input_scale = inputs[SCALE_INPUT_INDEX]; + BuffersIterator it = output->iterate_with({input_scale}, area); + if (invert_) { + transform_inverted(it, input_img); + } + else { + transform(it, input_img); + } +} + +void TransformOperation::transform(BuffersIterator &it, const MemoryBuffer *input_img) +{ + for (; !it.is_end(); ++it) { + const float scale = *it.in(0); + float x = it.x - translate_x_; + float y = it.y - translate_y_; + RotateOperation::rotate_coords( + x, y, rotate_center_x_, rotate_center_y_, rotate_sine_, rotate_cosine_); + x = ScaleOperation::scale_coord(x, scale_center_x_, scale); + y = ScaleOperation::scale_coord(y, scale_center_y_, scale); + input_img->read_elem_sampled(x, y, sampler_, it.out); + } +} + +void TransformOperation::transform_inverted(BuffersIterator &it, + const MemoryBuffer *input_img) +{ + for (; !it.is_end(); ++it) { + const float scale = *it.in(0); + float x = ScaleOperation::scale_coord(it.x, scale_center_x_, scale); + float y = ScaleOperation::scale_coord(it.y, scale_center_y_, scale); + RotateOperation::rotate_coords( + x, y, rotate_center_x_, rotate_center_y_, rotate_sine_, rotate_cosine_); + x -= translate_x_; + y -= translate_y_; + input_img->read_elem_sampled(x, y, sampler_, it.out); + } +} + +} // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_TransformOperation.h b/source/blender/compositor/operations/COM_TransformOperation.h new file mode 100644 index 00000000000..480998a0207 --- /dev/null +++ b/source/blender/compositor/operations/COM_TransformOperation.h @@ -0,0 +1,87 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright 2021, Blender Foundation. + */ + +#pragma once + +#include "COM_MultiThreadedOperation.h" + +namespace blender::compositor { + +class TransformOperation : public MultiThreadedOperation { + private: + constexpr static int IMAGE_INPUT_INDEX = 0; + constexpr static int X_INPUT_INDEX = 1; + constexpr static int Y_INPUT_INDEX = 2; + constexpr static int DEGREE_INPUT_INDEX = 3; + constexpr static int SCALE_INPUT_INDEX = 4; + + float scale_center_x_; + float scale_center_y_; + float rotate_center_x_; + float rotate_center_y_; + float rotate_cosine_; + float rotate_sine_; + float translate_x_; + float translate_y_; + float constant_scale_; + + /* Set variables. */ + PixelSampler sampler_; + bool convert_degree_to_rad_; + float translate_factor_x_; + float translate_factor_y_; + bool invert_; + + public: + TransformOperation(); + + void set_translate_factor_xy(float x, float y) + { + translate_factor_x_ = x; + translate_factor_y_ = y; + } + + void set_convert_rotate_degree_to_rad(bool value) + { + convert_degree_to_rad_ = value; + } + + void set_sampler(PixelSampler sampler) + { + sampler_ = sampler; + } + + void set_invert(bool value) + { + invert_ = value; + } + + void init_data() override; + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; + void update_memory_buffer_partial(MemoryBuffer *output, + const rcti &area, + Span inputs) override; + + private: + /** Translate -> Rotate -> Scale. */ + void transform(BuffersIterator &it, const MemoryBuffer *input_img); + /** Scale -> Rotate -> Translate. */ + void transform_inverted(BuffersIterator &it, const MemoryBuffer *input_img); +}; + +} // namespace blender::compositor -- cgit v1.2.3