Compositor: Full frame distort nodes

Adds full frame implementation to "Displace", "Crop", "Flip",
"Plane Track Deform", "Corner Pin", "Movie Distortion",
"Lens Distortion" and "Map UV" nodes.

The other nodes in "Distort" sub-menu are implemented
separately in other commits.

No functional changes.

Part of T88150.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D12166
This commit is contained in:
Manuel Castilla 2021-08-23 15:30:18 +02:00
parent 064167fce7
commit 344aca3b1b
22 changed files with 934 additions and 57 deletions

View File

@ -95,6 +95,22 @@ void CropOperation::executePixelSampled(float output[4], float x, float y, Pixel
}
}
void CropOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
rcti crop_area;
BLI_rcti_init(&crop_area, m_xmin, m_xmax, m_ymin, m_ymax);
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
if (BLI_rcti_isect_pt(&crop_area, it.x, it.y)) {
copy_v4_v4(it.out, it.in(0));
}
else {
zero_v4(it.out);
}
}
}
CropImageOperation::CropImageOperation() : CropBaseOperation()
{
/* pass */
@ -114,6 +130,18 @@ bool CropImageOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void CropImageOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
BLI_assert(input_idx == 0);
UNUSED_VARS_NDEBUG(input_idx);
r_input_area.xmax = output_area.xmax + this->m_xmin;
r_input_area.xmin = output_area.xmin + this->m_xmin;
r_input_area.ymax = output_area.ymax + this->m_ymin;
r_input_area.ymin = output_area.ymin + this->m_ymin;
}
void CropImageOperation::determineResolution(unsigned int resolution[2],
unsigned int preferredResolution[2])
{
@ -136,4 +164,21 @@ void CropImageOperation::executePixelSampled(float output[4],
}
}
void CropImageOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
rcti op_area;
BLI_rcti_init(&op_area, 0, getWidth(), 0, getHeight());
const MemoryBuffer *input = inputs[0];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
if (BLI_rcti_isect_pt(&op_area, it.x, it.y)) {
input->read_elem_checked(it.x + this->m_xmin, it.y + this->m_ymin, it.out);
}
else {
zero_v4(it.out);
}
}
}
} // namespace blender::compositor

View File

@ -18,11 +18,11 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class CropBaseOperation : public NodeOperation {
class CropBaseOperation : public MultiThreadedOperation {
protected:
SocketReader *m_inputOperation;
NodeTwoXYs *m_settings;
@ -53,6 +53,10 @@ class CropOperation : public CropBaseOperation {
public:
CropOperation();
void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override;
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
class CropImageOperation : public CropBaseOperation {
@ -65,6 +69,11 @@ class CropImageOperation : public CropBaseOperation {
void determineResolution(unsigned int resolution[2],
unsigned int preferredResolution[2]) override;
void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) 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;
};
} // namespace blender::compositor

View File

@ -32,20 +32,30 @@ DisplaceOperation::DisplaceOperation()
this->flags.complex = true;
this->m_inputColorProgram = nullptr;
this->m_inputVectorProgram = nullptr;
this->m_inputScaleXProgram = nullptr;
this->m_inputScaleYProgram = nullptr;
}
void DisplaceOperation::initExecution()
{
this->m_inputColorProgram = this->getInputSocketReader(0);
this->m_inputVectorProgram = this->getInputSocketReader(1);
this->m_inputScaleXProgram = this->getInputSocketReader(2);
this->m_inputScaleYProgram = this->getInputSocketReader(3);
NodeOperation *vector = this->getInputSocketReader(1);
NodeOperation *scale_x = this->getInputSocketReader(2);
NodeOperation *scale_y = this->getInputSocketReader(3);
if (execution_model_ == eExecutionModel::Tiled) {
vector_read_fn_ = [=](float x, float y, float *out) {
vector->readSampled(out, x, y, PixelSampler::Bilinear);
};
scale_x_read_fn_ = [=](float x, float y, float *out) {
scale_x->readSampled(out, x, y, PixelSampler::Nearest);
};
scale_y_read_fn_ = [=](float x, float y, float *out) {
scale_y->readSampled(out, x, y, PixelSampler::Nearest);
};
}
this->m_width_x4 = this->getWidth() * 4;
this->m_height_x4 = this->getHeight() * 4;
input_vector_width_ = vector->getWidth();
input_vector_height_ = vector->getHeight();
}
void DisplaceOperation::executePixelSampled(float output[4],
@ -69,8 +79,8 @@ void DisplaceOperation::executePixelSampled(float output[4],
bool DisplaceOperation::read_displacement(
float x, float y, float xscale, float yscale, const float origin[2], float &r_u, float &r_v)
{
float width = m_inputVectorProgram->getWidth();
float height = m_inputVectorProgram->getHeight();
float width = input_vector_width_;
float height = input_vector_height_;
if (x < 0.0f || x >= width || y < 0.0f || y >= height) {
r_u = 0.0f;
r_v = 0.0f;
@ -78,7 +88,7 @@ bool DisplaceOperation::read_displacement(
}
float col[4];
m_inputVectorProgram->readSampled(col, x, y, PixelSampler::Bilinear);
vector_read_fn_(x, y, col);
r_u = origin[0] - col[0] * xscale;
r_v = origin[1] - col[1] * yscale;
return true;
@ -90,9 +100,9 @@ void DisplaceOperation::pixelTransform(const float xy[2], float r_uv[2], float r
float uv[2]; /* temporary variables for derivative estimation */
int num;
m_inputScaleXProgram->readSampled(col, xy[0], xy[1], PixelSampler::Nearest);
scale_x_read_fn_(xy[0], xy[1], col);
float xs = col[0];
m_inputScaleYProgram->readSampled(col, xy[0], xy[1], PixelSampler::Nearest);
scale_y_read_fn_(xy[0], xy[1], col);
float ys = col[0];
/* clamp x and y displacement to triple image resolution -
* to prevent hangs from huge values mistakenly plugged in eg. z buffers */
@ -146,9 +156,9 @@ void DisplaceOperation::pixelTransform(const float xy[2], float r_uv[2], float r
void DisplaceOperation::deinitExecution()
{
this->m_inputColorProgram = nullptr;
this->m_inputVectorProgram = nullptr;
this->m_inputScaleXProgram = nullptr;
this->m_inputScaleYProgram = nullptr;
vector_read_fn_ = nullptr;
scale_x_read_fn_ = nullptr;
scale_y_read_fn_ = nullptr;
}
bool DisplaceOperation::determineDependingAreaOfInterest(rcti *input,
@ -195,4 +205,61 @@ bool DisplaceOperation::determineDependingAreaOfInterest(rcti *input,
return false;
}
void DisplaceOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
switch (input_idx) {
case 0: {
r_input_area.xmin = 0;
r_input_area.ymin = 0;
r_input_area.xmax = getInputOperation(input_idx)->getWidth();
r_input_area.ymax = getInputOperation(input_idx)->getHeight();
break;
}
case 1: {
r_input_area = output_area;
expand_area_for_sampler(r_input_area, PixelSampler::Bilinear);
break;
}
default: {
r_input_area = output_area;
break;
}
}
}
void DisplaceOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output),
const rcti &UNUSED(area),
Span<MemoryBuffer *> inputs)
{
MemoryBuffer *vector = inputs[1];
MemoryBuffer *scale_x = inputs[2];
MemoryBuffer *scale_y = inputs[3];
vector_read_fn_ = [=](float x, float y, float *out) { vector->read_elem_bilinear(x, y, out); };
scale_x_read_fn_ = [=](float x, float y, float *out) { scale_x->read_elem_checked(x, y, out); };
scale_y_read_fn_ = [=](float x, float y, float *out) { scale_y->read_elem_checked(x, y, out); };
}
void DisplaceOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_color = inputs[0];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
const float xy[2] = {(float)it.x, (float)it.y};
float uv[2];
float deriv[2][2];
pixelTransform(xy, uv, deriv);
if (is_zero_v2(deriv[0]) && is_zero_v2(deriv[1])) {
input_color->read_elem_bilinear(uv[0], uv[1], it.out);
}
else {
/* EWA filtering (without nearest it gets blurry with NO distortion). */
input_color->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out);
}
}
}
} // namespace blender::compositor

View File

@ -18,23 +18,27 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class DisplaceOperation : public NodeOperation {
class DisplaceOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
*/
SocketReader *m_inputColorProgram;
SocketReader *m_inputVectorProgram;
SocketReader *m_inputScaleXProgram;
SocketReader *m_inputScaleYProgram;
float m_width_x4;
float m_height_x4;
int input_vector_width_;
int input_vector_height_;
std::function<void(float x, float y, float *out)> vector_read_fn_;
std::function<void(float x, float y, float *out)> scale_x_read_fn_;
std::function<void(float x, float y, float *out)> scale_y_read_fn_;
public:
DisplaceOperation();
@ -62,6 +66,14 @@ class DisplaceOperation : public NodeOperation {
*/
void deinitExecution() override;
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
void update_memory_buffer_started(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
private:
bool read_displacement(
float x, float y, float xscale, float yscale, const float origin[2], float &r_u, float &r_v);

View File

@ -132,4 +132,56 @@ bool DisplaceSimpleOperation::determineDependingAreaOfInterest(rcti *input,
return false;
}
void DisplaceSimpleOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
switch (input_idx) {
case 0: {
r_input_area.xmin = 0;
r_input_area.ymin = 0;
r_input_area.xmax = getInputOperation(input_idx)->getWidth();
r_input_area.ymax = getInputOperation(input_idx)->getHeight();
break;
}
default: {
r_input_area = output_area;
break;
}
}
}
void DisplaceSimpleOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const float width = this->getWidth();
const float height = this->getHeight();
const MemoryBuffer *input_color = inputs[0];
for (BuffersIterator<float> it = output->iterate_with(inputs.drop_front(1), area); !it.is_end();
++it) {
float scale_x = *it.in(1);
float scale_y = *it.in(2);
/* Clamp x and y displacement to triple image resolution -
* to prevent hangs from huge values mistakenly plugged in eg. z buffers. */
CLAMP(scale_x, -m_width_x4, m_width_x4);
CLAMP(scale_y, -m_height_x4, m_height_x4);
/* Main displacement in pixel space. */
const float *vector = it.in(0);
const float p_dx = vector[0] * scale_x;
const float p_dy = vector[1] * scale_y;
/* Displaced pixel in uv coords, for image sampling. */
/* Clamp nodes to avoid glitches. */
float u = it.x - p_dx + 0.5f;
float v = it.y - p_dy + 0.5f;
CLAMP(u, 0.0f, width - 1.0f);
CLAMP(v, 0.0f, height - 1.0f);
input_color->read_elem_checked(u, v, it.out);
}
}
} // namespace blender::compositor

View File

@ -18,11 +18,11 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class DisplaceSimpleOperation : public NodeOperation {
class DisplaceSimpleOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@ -59,6 +59,11 @@ class DisplaceSimpleOperation : public NodeOperation {
* Deinitialize the execution
*/
void deinitExecution() 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;
};
} // namespace blender::compositor

View File

@ -75,4 +75,42 @@ bool FlipOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void FlipOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
BLI_assert(input_idx == 0);
UNUSED_VARS_NDEBUG(input_idx);
if (this->m_flipX) {
const int w = (int)this->getWidth() - 1;
r_input_area.xmax = (w - output_area.xmin) + 1;
r_input_area.xmin = (w - output_area.xmax) - 1;
}
else {
r_input_area.xmin = output_area.xmin;
r_input_area.xmax = output_area.xmax;
}
if (this->m_flipY) {
const int h = (int)this->getHeight() - 1;
r_input_area.ymax = (h - output_area.ymin) + 1;
r_input_area.ymin = (h - output_area.ymax) - 1;
}
else {
r_input_area.ymin = output_area.ymin;
r_input_area.ymax = output_area.ymax;
}
}
void FlipOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_img = inputs[0];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
const int nx = this->m_flipX ? ((int)this->getWidth() - 1) - it.x : it.x;
const int ny = this->m_flipY ? ((int)this->getHeight() - 1) - it.y : it.y;
input_img->read_elem(nx, ny, it.out);
}
}
} // namespace blender::compositor

View File

@ -18,11 +18,11 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class FlipOperation : public NodeOperation {
class FlipOperation : public MultiThreadedOperation {
private:
SocketReader *m_inputOperation;
bool m_flipX;
@ -45,6 +45,11 @@ class FlipOperation : public NodeOperation {
{
this->m_flipY = flipY;
}
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

@ -34,10 +34,26 @@ MapUVOperation::MapUVOperation()
this->m_inputColorProgram = nullptr;
}
void MapUVOperation::init_data()
{
NodeOperation *image_input = get_input_operation(0);
image_width_ = image_input->getWidth();
image_height_ = image_input->getHeight();
NodeOperation *uv_input = get_input_operation(1);
uv_width_ = uv_input->getWidth();
uv_height_ = uv_input->getHeight();
}
void MapUVOperation::initExecution()
{
this->m_inputColorProgram = this->getInputSocketReader(0);
this->m_inputUVProgram = this->getInputSocketReader(1);
if (execution_model_ == eExecutionModel::Tiled) {
uv_input_read_fn_ = [=](float x, float y, float *out) {
this->m_inputUVProgram->readSampled(out, x, y, PixelSampler::Bilinear);
};
}
}
void MapUVOperation::executePixelSampled(float output[4],
@ -81,9 +97,7 @@ void MapUVOperation::executePixelSampled(float output[4],
bool MapUVOperation::read_uv(float x, float y, float &r_u, float &r_v, float &r_alpha)
{
float width = m_inputUVProgram->getWidth();
float height = m_inputUVProgram->getHeight();
if (x < 0.0f || x >= width || y < 0.0f || y >= height) {
if (x < 0.0f || x >= uv_width_ || y < 0.0f || y >= uv_height_) {
r_u = 0.0f;
r_v = 0.0f;
r_alpha = 0.0f;
@ -91,9 +105,9 @@ bool MapUVOperation::read_uv(float x, float y, float &r_u, float &r_v, float &r_
}
float vector[3];
m_inputUVProgram->readSampled(vector, x, y, PixelSampler::Bilinear);
r_u = vector[0] * m_inputColorProgram->getWidth();
r_v = vector[1] * m_inputColorProgram->getHeight();
uv_input_read_fn_(x, y, vector);
r_u = vector[0] * image_width_;
r_v = vector[1] * image_height_;
r_alpha = vector[2];
return true;
}
@ -186,4 +200,75 @@ bool MapUVOperation::determineDependingAreaOfInterest(rcti *input,
return false;
}
void MapUVOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
switch (input_idx) {
case 0: {
r_input_area.xmin = 0;
r_input_area.xmax = image_width_;
r_input_area.ymin = 0;
r_input_area.ymax = image_height_;
break;
}
case 1: {
r_input_area = output_area;
expand_area_for_sampler(r_input_area, PixelSampler::Bilinear);
break;
}
}
}
void MapUVOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output),
const rcti &UNUSED(area),
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *uv_input = inputs[1];
uv_input_read_fn_ = [=](float x, float y, float *out) {
uv_input->read_elem_bilinear(x, y, out);
};
}
void MapUVOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_image = inputs[0];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
float xy[2] = {(float)it.x, (float)it.y};
float uv[2];
float deriv[2][2];
float alpha;
pixelTransform(xy, uv, deriv, alpha);
if (alpha == 0.0f) {
zero_v4(it.out);
continue;
}
/* EWA filtering. */
input_image->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out);
/* UV to alpha threshold. */
const float threshold = this->m_alpha * 0.05f;
/* XXX alpha threshold is used to fade out pixels on boundaries with invalid derivatives.
* this calculation is not very well defined, should be looked into if it becomes a problem ...
*/
const float du = len_v2(deriv[0]);
const float dv = len_v2(deriv[1]);
const float factor = 1.0f - threshold * (du / image_width_ + dv / image_height_);
if (factor < 0.0f) {
alpha = 0.0f;
}
else {
alpha *= factor;
}
/* "premul" */
if (alpha < 1.0f) {
mul_v4_fl(it.out, alpha);
}
}
}
} // namespace blender::compositor

View File

@ -18,11 +18,11 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class MapUVOperation : public NodeOperation {
class MapUVOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@ -30,8 +30,15 @@ class MapUVOperation : public NodeOperation {
SocketReader *m_inputUVProgram;
SocketReader *m_inputColorProgram;
int uv_width_;
int uv_height_;
int image_width_;
int image_height_;
float m_alpha;
std::function<void(float x, float y, float *out)> uv_input_read_fn_;
public:
MapUVOperation();
@ -49,6 +56,8 @@ class MapUVOperation : public NodeOperation {
void pixelTransform(const float xy[2], float r_uv[2], float r_deriv[2][2], float &r_alpha);
void init_data() override;
/**
* Initialize the execution
*/
@ -64,6 +73,14 @@ class MapUVOperation : public NodeOperation {
this->m_alpha = alpha;
}
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
void update_memory_buffer_started(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
private:
bool read_uv(float x, float y, float &r_u, float &r_v, float &r_alpha);
};

View File

@ -128,4 +128,51 @@ bool MovieDistortionOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void MovieDistortionOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
BLI_assert(input_idx == 0);
UNUSED_VARS_NDEBUG(input_idx);
r_input_area.xmin = output_area.xmin - m_margin[0];
r_input_area.ymin = output_area.ymin - m_margin[1];
r_input_area.xmax = output_area.xmax + m_margin[0];
r_input_area.ymax = output_area.ymax + m_margin[1];
}
void MovieDistortionOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_img = inputs[0];
if (this->m_distortion == nullptr) {
output->copy_from(input_img, area);
return;
}
/* `float overscan = 0.0f;` */
const float pixel_aspect = this->m_pixel_aspect;
const float w = (float)this->m_width /* `/ (1 + overscan)` */;
const float h = (float)this->m_height /* `/ (1 + overscan)` */;
const float aspx = w / (float)this->m_calibration_width;
const float aspy = h / (float)this->m_calibration_height;
float xy[2];
float distorted_xy[2];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
xy[0] = (it.x /* `- 0.5 * overscan * w` */) / aspx;
xy[1] = (it.y /* `- 0.5 * overscan * h` */) / aspy / pixel_aspect;
if (this->m_apply) {
BKE_tracking_distortion_undistort_v2(this->m_distortion, xy, distorted_xy);
}
else {
BKE_tracking_distortion_distort_v2(this->m_distortion, xy, distorted_xy);
}
const float u = distorted_xy[0] * aspx /* `+ 0.5 * overscan * w` */;
const float v = (distorted_xy[1] * aspy /* `+ 0.5 * overscan * h` */) * pixel_aspect;
input_img->read_elem_bilinear(u, v, it.out);
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
#include "DNA_movieclip_types.h"
#include "MEM_guardedalloc.h"
@ -26,7 +26,7 @@
namespace blender::compositor {
class MovieDistortionOperation : public NodeOperation {
class MovieDistortionOperation : public MultiThreadedOperation {
private:
SocketReader *m_inputOperation;
MovieClip *m_movieClip;
@ -58,6 +58,11 @@ class MovieDistortionOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) 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;
};
} // namespace blender::compositor

View File

@ -16,6 +16,7 @@
*/
#include "COM_PlaneCornerPinOperation.h"
#include "COM_ConstantOperation.h"
#include "COM_ReadBufferOperation.h"
#include "MEM_guardedalloc.h"
@ -28,6 +29,11 @@
namespace blender::compositor {
constexpr int LOWER_LEFT_CORNER_INDEX = 0;
constexpr int LOWER_RIGHT_CORNER_INDEX = 1;
constexpr int UPPER_RIGHT_CORNER_INDEX = 2;
constexpr int UPPER_LEFT_CORNER_INDEX = 3;
static bool check_corners(float corners[4][2])
{
int i, next, prev;
@ -58,6 +64,7 @@ static bool check_corners(float corners[4][2])
return true;
}
/* TODO(manzanilla): to be removed with tiled implementation. */
static void readCornersFromSockets(rcti *rect, SocketReader *readers[4], float corners[4][2])
{
for (int i = 0; i < 4; i++) {
@ -87,6 +94,53 @@ static void readCornersFromSockets(rcti *rect, SocketReader *readers[4], float c
}
}
static void set_default_corner(const int corner_idx, float r_corner[2])
{
BLI_assert(corner_idx >= 0 && corner_idx < 4);
switch (corner_idx) {
case LOWER_LEFT_CORNER_INDEX:
r_corner[0] = 0.0f;
r_corner[1] = 0.0f;
break;
case LOWER_RIGHT_CORNER_INDEX:
r_corner[0] = 1.0f;
r_corner[1] = 0.0f;
break;
case UPPER_RIGHT_CORNER_INDEX:
r_corner[0] = 1.0f;
r_corner[1] = 1.0f;
break;
case UPPER_LEFT_CORNER_INDEX:
r_corner[0] = 0.0f;
r_corner[1] = 1.0f;
break;
}
}
static void read_input_corners(NodeOperation *op, const int first_input_idx, float r_corners[4][2])
{
for (const int i : IndexRange(4)) {
NodeOperation *input = op->get_input_operation(i + first_input_idx);
if (input->get_flags().is_constant_operation) {
ConstantOperation *corner_input = static_cast<ConstantOperation *>(input);
copy_v2_v2(r_corners[i], corner_input->get_constant_elem());
}
else {
set_default_corner(i, r_corners[i]);
}
}
/* Convexity check: concave corners need to be prevented, otherwise
* #BKE_tracking_homography_between_two_quads will freeze. */
if (!check_corners(r_corners)) {
/* Revert to default corners. There could be a more elegant solution,
* this prevents freezing at least. */
for (const int i : IndexRange(4)) {
set_default_corner(i, r_corners[i]);
}
}
}
/* ******** PlaneCornerPinMaskOperation ******** */
PlaneCornerPinMaskOperation::PlaneCornerPinMaskOperation() : m_corners_ready(false)
@ -103,6 +157,17 @@ PlaneCornerPinMaskOperation::PlaneCornerPinMaskOperation() : m_corners_ready(fal
flags.complex = true;
}
void PlaneCornerPinMaskOperation::init_data()
{
if (execution_model_ == eExecutionModel::FullFrame) {
float corners[4][2];
read_input_corners(this, 0, corners);
calculateCorners(corners, true, 0);
}
}
/* TODO(manzanilla): to be removed with tiled implementation. Same for #deinitExecution and do the
* same on #PlaneCornerPinWarpImageOperation. */
void PlaneCornerPinMaskOperation::initExecution()
{
PlaneDistortMaskOperation::initExecution();
@ -147,10 +212,22 @@ void *PlaneCornerPinMaskOperation::initializeTileData(rcti *rect)
void PlaneCornerPinMaskOperation::determineResolution(unsigned int resolution[2],
unsigned int preferredResolution[2])
{
if (execution_model_ == eExecutionModel::FullFrame) {
/* Determine inputs resolution. */
PlaneDistortMaskOperation::determineResolution(resolution, preferredResolution);
}
resolution[0] = preferredResolution[0];
resolution[1] = preferredResolution[1];
}
void PlaneCornerPinMaskOperation::get_area_of_interest(const int UNUSED(input_idx),
const rcti &UNUSED(output_area),
rcti &r_input_area)
{
/* All corner inputs are used as constants. */
r_input_area = COM_SINGLE_ELEM_AREA;
}
/* ******** PlaneCornerPinWarpImageOperation ******** */
PlaneCornerPinWarpImageOperation::PlaneCornerPinWarpImageOperation() : m_corners_ready(false)
@ -161,6 +238,15 @@ PlaneCornerPinWarpImageOperation::PlaneCornerPinWarpImageOperation() : m_corners
addInputSocket(DataType::Vector);
}
void PlaneCornerPinWarpImageOperation::init_data()
{
if (execution_model_ == eExecutionModel::FullFrame) {
float corners[4][2];
read_input_corners(this, 1, corners);
calculateCorners(corners, true, 0);
}
}
void PlaneCornerPinWarpImageOperation::initExecution()
{
PlaneDistortWarpImageOperation::initExecution();
@ -227,4 +313,17 @@ bool PlaneCornerPinWarpImageOperation::determineDependingAreaOfInterest(
#endif
}
void PlaneCornerPinWarpImageOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
if (input_idx == 0) {
PlaneDistortWarpImageOperation::get_area_of_interest(input_idx, output_area, r_input_area);
}
else {
/* Corner inputs are used as constants. */
r_input_area = COM_SINGLE_ELEM_AREA;
}
}
} // namespace blender::compositor

View File

@ -31,11 +31,13 @@ namespace blender::compositor {
class PlaneCornerPinMaskOperation : public PlaneDistortMaskOperation {
private:
/* TODO(manzanilla): to be removed with tiled implementation. */
bool m_corners_ready;
public:
PlaneCornerPinMaskOperation();
void init_data() override;
void initExecution() override;
void deinitExecution() override;
@ -43,6 +45,8 @@ class PlaneCornerPinMaskOperation : public PlaneDistortMaskOperation {
void determineResolution(unsigned int resolution[2],
unsigned int preferredResolution[2]) override;
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
};
class PlaneCornerPinWarpImageOperation : public PlaneDistortWarpImageOperation {
@ -52,6 +56,7 @@ class PlaneCornerPinWarpImageOperation : public PlaneDistortWarpImageOperation {
public:
PlaneCornerPinWarpImageOperation();
void init_data() override;
void initExecution() override;
void deinitExecution() override;
@ -60,6 +65,8 @@ class PlaneCornerPinWarpImageOperation : public PlaneDistortWarpImageOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
};
} // namespace blender::compositor

View File

@ -85,8 +85,9 @@ void PlaneDistortWarpImageOperation::calculateCorners(const float corners[4][2],
{
PlaneDistortBaseOperation::calculateCorners(corners, normalized, sample);
const int width = this->m_pixelReader->getWidth();
const int height = this->m_pixelReader->getHeight();
const NodeOperation *image = get_input_operation(0);
const int width = image->getWidth();
const int height = image->getHeight();
float frame_corners[4][2] = {
{0.0f, 0.0f}, {(float)width, 0.0f}, {(float)width, (float)height}, {0.0f, (float)height}};
MotionSample *sample_data = &this->m_samples[sample];
@ -127,6 +128,34 @@ void PlaneDistortWarpImageOperation::executePixelSampled(float output[4],
}
}
void PlaneDistortWarpImageOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_img = inputs[0];
float uv[2];
float deriv[2][2];
BuffersIterator<float> it = output->iterate_with({}, area);
if (this->m_motion_blur_samples == 1) {
for (; !it.is_end(); ++it) {
warpCoord(it.x, it.y, this->m_samples[0].perspectiveMatrix, uv, deriv);
input_img->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], it.out);
}
}
else {
for (; !it.is_end(); ++it) {
zero_v4(it.out);
for (const int sample : IndexRange(this->m_motion_blur_samples)) {
float color[4];
warpCoord(it.x, it.y, this->m_samples[sample].perspectiveMatrix, uv, deriv);
input_img->read_elem_filtered(uv[0], uv[1], deriv[0], deriv[1], color);
add_v4_v4(it.out, color);
}
mul_v4_fl(it.out, 1.0f / (float)this->m_motion_blur_samples);
}
}
}
bool PlaneDistortWarpImageOperation::determineDependingAreaOfInterest(
rcti *input, ReadBufferOperation *readOperation, rcti *output)
{
@ -157,6 +186,51 @@ bool PlaneDistortWarpImageOperation::determineDependingAreaOfInterest(
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void PlaneDistortWarpImageOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
if (input_idx != 0) {
r_input_area = output_area;
return;
}
/* TODO: figure out the area needed for warping and EWA filtering. */
r_input_area.xmin = 0;
r_input_area.ymin = 0;
r_input_area.xmax = get_input_operation(0)->getWidth();
r_input_area.ymax = get_input_operation(0)->getHeight();
/* Old implemention but resulting coordinates are way out of input operation bounds and in some
* cases the area result may incorrectly cause cropping. */
#if 0
float min[2], max[2];
INIT_MINMAX2(min, max);
for (int sample = 0; sample < this->m_motion_blur_samples; sample++) {
float UVs[4][2];
float deriv[2][2];
MotionSample *sample_data = &this->m_samples[sample];
/* TODO(sergey): figure out proper way to do this. */
warpCoord(
output_area.xmin - 2, output_area.ymin - 2, sample_data->perspectiveMatrix, UVs[0], deriv);
warpCoord(
output_area.xmax + 2, output_area.ymin - 2, sample_data->perspectiveMatrix, UVs[1], deriv);
warpCoord(
output_area.xmax + 2, output_area.ymax + 2, sample_data->perspectiveMatrix, UVs[2], deriv);
warpCoord(
output_area.xmin - 2, output_area.ymax + 2, sample_data->perspectiveMatrix, UVs[3], deriv);
for (int i = 0; i < 4; i++) {
minmax_v2v2_v2(min, max, UVs[i]);
}
}
r_input_area.xmin = min[0] - 1;
r_input_area.ymin = min[1] - 1;
r_input_area.xmax = max[0] + 1;
r_input_area.ymax = max[1] + 1;
#endif
}
/* ******** PlaneDistort Mask ******** */
PlaneDistortMaskOperation::PlaneDistortMaskOperation() : PlaneDistortBaseOperation()
@ -219,4 +293,41 @@ void PlaneDistortMaskOperation::executePixelSampled(float output[4],
}
}
void PlaneDistortMaskOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> UNUSED(inputs))
{
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
int inside_count = 0;
for (const int motion_sample : IndexRange(this->m_motion_blur_samples)) {
MotionSample &sample = this->m_samples[motion_sample];
inside_count += get_jitter_samples_inside_count(it.x, it.y, sample);
}
*it.out = (float)inside_count / (this->m_osa * this->m_motion_blur_samples);
}
}
int PlaneDistortMaskOperation::get_jitter_samples_inside_count(int x,
int y,
MotionSample &sample_data)
{
float point[2];
int inside_count = 0;
for (int sample = 0; sample < this->m_osa; sample++) {
point[0] = x + this->m_jitter[sample][0];
point[1] = y + this->m_jitter[sample][1];
if (isect_point_tri_v2(point,
sample_data.frameSpaceCorners[0],
sample_data.frameSpaceCorners[1],
sample_data.frameSpaceCorners[2]) ||
isect_point_tri_v2(point,
sample_data.frameSpaceCorners[0],
sample_data.frameSpaceCorners[2],
sample_data.frameSpaceCorners[3])) {
inside_count++;
}
}
return inside_count;
}
} // namespace blender::compositor

View File

@ -20,7 +20,7 @@
#include <string.h>
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
#include "DNA_movieclip_types.h"
#include "DNA_tracking_types.h"
@ -32,7 +32,7 @@ namespace blender::compositor {
#define PLANE_DISTORT_MAX_SAMPLES 64
class PlaneDistortBaseOperation : public NodeOperation {
class PlaneDistortBaseOperation : public MultiThreadedOperation {
protected:
struct MotionSample {
float frameSpaceCorners[4][2]; /* Corners coordinates in pixel space. */
@ -78,6 +78,11 @@ class PlaneDistortWarpImageOperation : public PlaneDistortBaseOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) 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;
};
class PlaneDistortMaskOperation : public PlaneDistortBaseOperation {
@ -91,6 +96,13 @@ class PlaneDistortMaskOperation : public PlaneDistortBaseOperation {
void initExecution() override;
void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override;
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
private:
int get_jitter_samples_inside_count(int x, int y, MotionSample &sample_data);
};
} // namespace blender::compositor

View File

@ -101,18 +101,40 @@ void PlaneTrackCommon::determineResolution(unsigned int resolution[2],
/* ******** PlaneTrackMaskOperation ******** */
void PlaneTrackMaskOperation::init_data()
{
PlaneDistortMaskOperation::init_data();
if (execution_model_ == eExecutionModel::FullFrame) {
PlaneTrackCommon::read_and_calculate_corners(this);
}
}
/* TODO(manzanilla): to be removed with tiled implementation. */
void PlaneTrackMaskOperation::initExecution()
{
PlaneDistortMaskOperation::initExecution();
PlaneTrackCommon::read_and_calculate_corners(this);
if (execution_model_ == eExecutionModel::Tiled) {
PlaneTrackCommon::read_and_calculate_corners(this);
}
}
/* ******** PlaneTrackWarpImageOperation ******** */
void PlaneTrackWarpImageOperation::init_data()
{
PlaneDistortWarpImageOperation::init_data();
if (execution_model_ == eExecutionModel::FullFrame) {
PlaneTrackCommon::read_and_calculate_corners(this);
}
}
/* TODO(manzanilla): to be removed with tiled implementation. */
void PlaneTrackWarpImageOperation::initExecution()
{
PlaneDistortWarpImageOperation::initExecution();
PlaneTrackCommon::read_and_calculate_corners(this);
if (execution_model_ == eExecutionModel::Tiled) {
PlaneTrackCommon::read_and_calculate_corners(this);
}
}
} // namespace blender::compositor

View File

@ -73,6 +73,8 @@ class PlaneTrackMaskOperation : public PlaneDistortMaskOperation, public PlaneTr
{
}
void init_data() override;
void initExecution() override;
void determineResolution(unsigned int resolution[2],
@ -92,6 +94,8 @@ class PlaneTrackWarpImageOperation : public PlaneDistortWarpImageOperation,
{
}
void init_data() override;
void initExecution() override;
void determineResolution(unsigned int resolution[2],

View File

@ -17,6 +17,8 @@
*/
#include "COM_ProjectorLensDistortionOperation.h"
#include "COM_ConstantOperation.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
@ -32,6 +34,20 @@ ProjectorLensDistortionOperation::ProjectorLensDistortionOperation()
this->m_dispersionAvailable = false;
this->m_dispersion = 0.0f;
}
void ProjectorLensDistortionOperation::init_data()
{
if (execution_model_ == eExecutionModel::FullFrame) {
NodeOperation *dispersion_input = get_input_operation(1);
if (dispersion_input->get_flags().is_constant_operation) {
this->m_dispersion =
static_cast<ConstantOperation *>(dispersion_input)->get_constant_elem()[0];
}
this->m_kr = 0.25f * max_ff(min_ff(this->m_dispersion, 1.0f), 0.0f);
this->m_kr2 = this->m_kr * 20;
}
}
void ProjectorLensDistortionOperation::initExecution()
{
this->initMutex();
@ -97,6 +113,7 @@ bool ProjectorLensDistortionOperation::determineDependingAreaOfInterest(
return false;
}
/* TODO(manzanilla): to be removed with tiled implementation. */
void ProjectorLensDistortionOperation::updateDispersion()
{
if (this->m_dispersionAvailable) {
@ -114,4 +131,41 @@ void ProjectorLensDistortionOperation::updateDispersion()
this->unlockMutex();
}
void ProjectorLensDistortionOperation::get_area_of_interest(const int input_idx,
const rcti &output_area,
rcti &r_input_area)
{
if (input_idx == 1) {
/* Dispersion input is used as constant only. */
r_input_area = COM_SINGLE_ELEM_AREA;
return;
}
r_input_area.ymax = output_area.ymax;
r_input_area.ymin = output_area.ymin;
r_input_area.xmin = output_area.xmin - this->m_kr2 - 2;
r_input_area.xmax = output_area.xmax + this->m_kr2 + 2;
}
void ProjectorLensDistortionOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_image = inputs[0];
const float height = this->getHeight();
const float width = this->getWidth();
float color[4];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
const float v = (it.y + 0.5f) / height;
const float u = (it.x + 0.5f) / width;
input_image->read_elem_bilinear((u * width + this->m_kr2) - 0.5f, v * height - 0.5f, color);
it.out[0] = color[0];
input_image->read_elem(it.x, it.y, color);
it.out[1] = color[1];
input_image->read_elem_bilinear((u * width - this->m_kr2) - 0.5f, v * height - 0.5f, color);
it.out[2] = color[2];
it.out[3] = 1.0f;
}
}
} // namespace blender::compositor

View File

@ -18,12 +18,12 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
#include "DNA_node_types.h"
namespace blender::compositor {
class ProjectorLensDistortionOperation : public NodeOperation {
class ProjectorLensDistortionOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@ -31,6 +31,7 @@ class ProjectorLensDistortionOperation : public NodeOperation {
SocketReader *m_inputProgram;
float m_dispersion;
/* TODO(manzanilla): to be removed with tiled implementation. */
bool m_dispersionAvailable;
float m_kr, m_kr2;
@ -43,6 +44,7 @@ class ProjectorLensDistortionOperation : public NodeOperation {
*/
void executePixel(float output[4], int x, int y, void *data) override;
void init_data() override;
/**
* Initialize the execution
*/
@ -59,6 +61,11 @@ class ProjectorLensDistortionOperation : public NodeOperation {
rcti *output) override;
void updateDispersion();
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

@ -17,6 +17,7 @@
*/
#include "COM_ScreenLensDistortionOperation.h"
#include "COM_ConstantOperation.h"
#include "BLI_math.h"
#include "BLI_rand.h"
@ -53,6 +54,35 @@ void ScreenLensDistortionOperation::setDispersion(float dispersion)
m_dispersion_const = true;
}
void ScreenLensDistortionOperation::init_data()
{
this->m_cx = 0.5f * (float)getWidth();
this->m_cy = 0.5f * (float)getHeight();
switch (execution_model_) {
case eExecutionModel::FullFrame: {
NodeOperation *distortion_op = get_input_operation(1);
NodeOperation *dispersion_op = get_input_operation(2);
if (!m_distortion_const && distortion_op->get_flags().is_constant_operation) {
m_distortion = static_cast<ConstantOperation *>(distortion_op)->get_constant_elem()[0];
}
if (!m_dispersion_const && distortion_op->get_flags().is_constant_operation) {
m_dispersion = static_cast<ConstantOperation *>(dispersion_op)->get_constant_elem()[0];
}
updateVariables(m_distortion, m_dispersion);
break;
}
case eExecutionModel::Tiled: {
/* If both are constant, init variables once. */
if (m_distortion_const && m_dispersion_const) {
updateVariables(m_distortion, m_dispersion);
m_variables_ready = true;
}
break;
}
}
}
void ScreenLensDistortionOperation::initExecution()
{
this->m_inputProgram = this->getInputSocketReader(0);
@ -61,15 +91,6 @@ void ScreenLensDistortionOperation::initExecution()
uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX);
rng_seed ^= (uint)POINTER_AS_INT(m_inputProgram);
this->m_rng = BLI_rng_new(rng_seed);
this->m_cx = 0.5f * (float)getWidth();
this->m_cy = 0.5f * (float)getHeight();
/* if both are constant, init variables once */
if (m_distortion_const && m_dispersion_const) {
updateVariables(m_distortion, m_dispersion);
m_variables_ready = true;
}
}
void *ScreenLensDistortionOperation::initializeTileData(rcti * /*rect*/)
@ -130,7 +151,7 @@ bool ScreenLensDistortionOperation::get_delta(float r_sq,
return false;
}
void ScreenLensDistortionOperation::accumulate(MemoryBuffer *buffer,
void ScreenLensDistortionOperation::accumulate(const MemoryBuffer *buffer,
int a,
int b,
float r_sq,
@ -154,7 +175,14 @@ void ScreenLensDistortionOperation::accumulate(MemoryBuffer *buffer,
float xy[2];
distort_uv(uv, t, xy);
buffer->readBilinear(color, xy[0], xy[1]);
switch (execution_model_) {
case eExecutionModel::Tiled:
buffer->readBilinear(color, xy[0], xy[1]);
break;
case eExecutionModel::FullFrame:
buffer->read_elem_bilinear(xy[0], xy[1], color);
break;
}
sum[a] += (1.0f - tz) * color[a];
sum[b] += (tz)*color[b];
@ -354,4 +382,143 @@ void ScreenLensDistortionOperation::updateVariables(float distortion, float disp
mul_v3_v3fl(m_k4, m_k, 4.0f);
}
void ScreenLensDistortionOperation::get_area_of_interest(const int input_idx,
const rcti &UNUSED(output_area),
rcti &r_input_area)
{
if (input_idx != 0) {
/* Dispersion and distorsion inputs are used as constants only. */
r_input_area = COM_SINGLE_ELEM_AREA;
}
/* XXX the original method of estimating the area-of-interest does not work
* it assumes a linear increase/decrease of mapped coordinates, which does not
* yield correct results for the area and leaves uninitialized buffer areas.
* So now just use the full image area, which may not be as efficient but works at least ...
*/
#if 1
NodeOperation *image = getInputOperation(0);
r_input_area.xmax = image->getWidth();
r_input_area.xmin = 0;
r_input_area.ymax = image->getHeight();
r_input_area.ymin = 0;
#else /* Original method in tiled implementation. */
rcti newInput;
const float margin = 2;
BLI_rcti_init_minmax(&newInput);
if (m_dispersion_const && m_distortion_const) {
/* update from fixed distortion/dispersion */
# define UPDATE_INPUT(x, y) \
{ \
float coords[6]; \
determineUV(coords, x, y); \
newInput.xmin = min_ffff(newInput.xmin, coords[0], coords[2], coords[4]); \
newInput.ymin = min_ffff(newInput.ymin, coords[1], coords[3], coords[5]); \
newInput.xmax = max_ffff(newInput.xmax, coords[0], coords[2], coords[4]); \
newInput.ymax = max_ffff(newInput.ymax, coords[1], coords[3], coords[5]); \
} \
(void)0
UPDATE_INPUT(input->xmin, input->xmax);
UPDATE_INPUT(input->xmin, input->ymax);
UPDATE_INPUT(input->xmax, input->ymax);
UPDATE_INPUT(input->xmax, input->ymin);
# undef UPDATE_INPUT
}
else {
/* use maximum dispersion 1.0 if not const */
float dispersion = m_dispersion_const ? m_dispersion : 1.0f;
# define UPDATE_INPUT(x, y, distortion) \
{ \
float coords[6]; \
updateVariables(distortion, dispersion); \
determineUV(coords, x, y); \
newInput.xmin = min_ffff(newInput.xmin, coords[0], coords[2], coords[4]); \
newInput.ymin = min_ffff(newInput.ymin, coords[1], coords[3], coords[5]); \
newInput.xmax = max_ffff(newInput.xmax, coords[0], coords[2], coords[4]); \
newInput.ymax = max_ffff(newInput.ymax, coords[1], coords[3], coords[5]); \
} \
(void)0
if (m_distortion_const) {
/* update from fixed distortion */
UPDATE_INPUT(input->xmin, input->xmax, m_distortion);
UPDATE_INPUT(input->xmin, input->ymax, m_distortion);
UPDATE_INPUT(input->xmax, input->ymax, m_distortion);
UPDATE_INPUT(input->xmax, input->ymin, m_distortion);
}
else {
/* update from min/max distortion (-1..1) */
UPDATE_INPUT(input->xmin, input->xmax, -1.0f);
UPDATE_INPUT(input->xmin, input->ymax, -1.0f);
UPDATE_INPUT(input->xmax, input->ymax, -1.0f);
UPDATE_INPUT(input->xmax, input->ymin, -1.0f);
UPDATE_INPUT(input->xmin, input->xmax, 1.0f);
UPDATE_INPUT(input->xmin, input->ymax, 1.0f);
UPDATE_INPUT(input->xmax, input->ymax, 1.0f);
UPDATE_INPUT(input->xmax, input->ymin, 1.0f);
# undef UPDATE_INPUT
}
}
newInput.xmin -= margin;
newInput.ymin -= margin;
newInput.xmax += margin;
newInput.ymax += margin;
operation = getInputOperation(0);
if (operation->determineDependingAreaOfInterest(&newInput, readOperation, output)) {
return true;
}
return false;
#endif
}
void ScreenLensDistortionOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input_image = inputs[0];
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
float xy[2] = {(float)it.x, (float)it.y};
float uv[2];
get_uv(xy, uv);
const float uv_dot = len_squared_v2(uv);
float delta[3][2];
const bool valid_r = get_delta(uv_dot, m_k4[0], uv, delta[0]);
const bool valid_g = get_delta(uv_dot, m_k4[1], uv, delta[1]);
const bool valid_b = get_delta(uv_dot, m_k4[2], uv, delta[2]);
if (!(valid_r && valid_g && valid_b)) {
zero_v4(it.out);
continue;
}
int count[3] = {0, 0, 0};
float sum[4] = {0, 0, 0, 0};
accumulate(input_image, 0, 1, uv_dot, uv, delta, sum, count);
accumulate(input_image, 1, 2, uv_dot, uv, delta, sum, count);
if (count[0]) {
it.out[0] = 2.0f * sum[0] / (float)count[0];
}
if (count[1]) {
it.out[1] = 2.0f * sum[1] / (float)count[1];
}
if (count[2]) {
it.out[2] = 2.0f * sum[2] / (float)count[2];
}
/* Set alpha. */
it.out[3] = 1.0f;
}
}
} // namespace blender::compositor

View File

@ -18,14 +18,14 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
#include "DNA_node_types.h"
struct RNG;
namespace blender::compositor {
class ScreenLensDistortionOperation : public NodeOperation {
class ScreenLensDistortionOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@ -50,6 +50,8 @@ class ScreenLensDistortionOperation : public NodeOperation {
public:
ScreenLensDistortionOperation();
void init_data() override;
/**
* The inner loop of this operation.
*/
@ -84,6 +86,11 @@ class ScreenLensDistortionOperation : public NodeOperation {
ReadBufferOperation *readOperation,
rcti *output) 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:
void determineUV(float result[6], float x, float y) const;
void updateVariables(float distortion, float dispersion);
@ -91,7 +98,7 @@ class ScreenLensDistortionOperation : public NodeOperation {
void get_uv(const float xy[2], float uv[2]) const;
void distort_uv(const float uv[2], float t, float xy[2]) const;
bool get_delta(float r_sq, float k4, const float uv[2], float delta[2]) const;
void accumulate(MemoryBuffer *buffer,
void accumulate(const MemoryBuffer *buffer,
int a,
int b,
float r_sq,