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
This commit is contained in:
Manuel Castilla 2021-08-23 15:30:01 +02:00
parent a95e56b741
commit 064167fce7
11 changed files with 493 additions and 69 deletions

View File

@ -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

View File

@ -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);

View File

@ -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));
}

View File

@ -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));
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.addLink(angleAttribute->getOutputSocket(), rotateOperation->getInputSocket(1));
converter.addOperation(scaleOperation);
converter.addOperation(translateOperation);
converter.addOperation(rotateOperation);
converter.addOperation(psoperation);
converter.addLink(xAttribute->getOutputSocket(), translateOperation->getInputSocket(1));
converter.addLink(yAttribute->getOutputSocket(), translateOperation->getInputSocket(2));
converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(1));
converter.addLink(scaleAttribute->getOutputSocket(), scaleOperation->getInputSocket(2));
converter.mapOutputSocket(getOutputSocket(), psoperation->getOutputSocket());
converter.addLink(angleAttribute->getOutputSocket(), rotateOperation->getInputSocket(1));
if (invert) {
// Translate -> Rotate -> Scale.
converter.mapInputSocket(imageInput, translateOperation->getInputSocket(0));
converter.addLink(xAttribute->getOutputSocket(), translateOperation->getInputSocket(1));
converter.addLink(yAttribute->getOutputSocket(), translateOperation->getInputSocket(2));
converter.addLink(translateOperation->getOutputSocket(), rotateOperation->getInputSocket(0));
converter.addLink(rotateOperation->getOutputSocket(), scaleOperation->getInputSocket(0));
converter.mapOutputSocket(getOutputSocket(), psoperation->getOutputSocket());
converter.addLink(scaleOperation->getOutputSocket(), psoperation->getInputSocket(0));
}
else {
// Scale -> Rotate -> Translate.
converter.mapInputSocket(imageInput, scaleOperation->getInputSocket(0));
if (invert) {
// Translate -> Rotate -> Scale.
converter.mapInputSocket(imageInput, translateOperation->getInputSocket(0));
converter.addLink(scaleOperation->getOutputSocket(), rotateOperation->getInputSocket(0));
converter.addLink(rotateOperation->getOutputSocket(), translateOperation->getInputSocket(0));
converter.addLink(translateOperation->getOutputSocket(),
rotateOperation->getInputSocket(0));
converter.addLink(rotateOperation->getOutputSocket(), scaleOperation->getInputSocket(0));
converter.addLink(translateOperation->getOutputSocket(), psoperation->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());
}
}
}

View File

@ -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

View File

@ -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<ConstantOperation *>(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<MemoryBuffer *> inputs)
{
ensureDegree();
const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX];
for (BuffersIterator<float> 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

View File

@ -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<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -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()

View File

@ -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;

View File

@ -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<ConstantOperation *>(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<ConstantOperation *>(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<ConstantOperation *>(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<ConstantOperation *>(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<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_img = inputs[IMAGE_INPUT_INDEX];
MemoryBuffer *input_scale = inputs[SCALE_INPUT_INDEX];
BuffersIterator<float> it = output->iterate_with({input_scale}, area);
if (invert_) {
transform_inverted(it, input_img);
}
else {
transform(it, input_img);
}
}
void TransformOperation::transform(BuffersIterator<float> &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<float> &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

View File

@ -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<MemoryBuffer *> inputs) override;
private:
/** Translate -> Rotate -> Scale. */
void transform(BuffersIterator<float> &it, const MemoryBuffer *input_img);
/** Scale -> Rotate -> Translate. */
void transform_inverted(BuffersIterator<float> &it, const MemoryBuffer *input_img);
};
} // namespace blender::compositor