Compositor: Full frame matte nodes

Adds full frame implementation to Channel Key, Chroma Key, Color Key,
Color Spill, Cryptomatte, Difference Key, Distance Key, Keying,
Keying Screen and Luminance Key nodes. The other nodes
in "Matte" sub-menu are submitted separately.

No functional changes.

Part of T88150.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D12220
This commit is contained in:
Manuel Castilla 2021-08-23 15:30:46 +02:00
parent daa7c59e38
commit 153b45037f
28 changed files with 701 additions and 44 deletions

View File

@ -27,6 +27,7 @@ ChannelMatteOperation::ChannelMatteOperation()
addOutputSocket(DataType::Value);
this->m_inputImageProgram = nullptr;
flags.can_be_constant = true;
}
void ChannelMatteOperation::initExecution()
@ -121,4 +122,37 @@ void ChannelMatteOperation::executePixelSampled(float output[4],
output[0] = MIN2(alpha, inColor[3]);
}
void ChannelMatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *color = it.in(0);
/* Matte operation. */
float alpha = color[this->m_ids[0]] - MAX2(color[this->m_ids[1]], color[this->m_ids[2]]);
/* Flip because 0.0 is transparent, not 1.0. */
alpha = 1.0f - alpha;
/* Test range. */
if (alpha > m_limit_max) {
alpha = color[3]; /* Whatever it was prior. */
}
else if (alpha < m_limit_min) {
alpha = 0.0f;
}
else { /* Blend. */
alpha = (alpha - m_limit_min) / m_limit_range;
}
/* Store matte(alpha) value in [0] to go with
* COM_SetAlphaMultiplyOperation and the Value output.
*/
/* Don't make something that was more transparent less transparent. */
*it.out = MIN2(alpha, color[3]);
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_MixOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,7 +26,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class ChannelMatteOperation : public NodeOperation {
class ChannelMatteOperation : public MultiThreadedOperation {
private:
SocketReader *m_inputImageProgram;
@ -71,6 +71,10 @@ class ChannelMatteOperation : public NodeOperation {
this->m_limit_channel = nodeChroma->channel;
this->m_matte_channel = custom2;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -29,6 +29,7 @@ ChromaMatteOperation::ChromaMatteOperation()
this->m_inputImageProgram = nullptr;
this->m_inputKeyProgram = nullptr;
flags.can_be_constant = true;
}
void ChromaMatteOperation::initExecution()
@ -110,4 +111,58 @@ void ChromaMatteOperation::executePixelSampled(float output[4],
}
}
void ChromaMatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const float acceptance = this->m_settings->t1; /* In radians. */
const float cutoff = this->m_settings->t2; /* In radians. */
const float gain = this->m_settings->fstrength;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *in_image = it.in(0);
const float *in_key = it.in(1);
/* Store matte(alpha) value in [0] to go with
* #COM_SetAlphaMultiplyOperation and the Value output. */
/* Algorithm from book "Video Demystified", does not include the spill reduction part. */
/* Find theta, the angle that the color space should be rotated based on key. */
/* Rescale to `-1.0..1.0`. */
// const float image_Y = (in_image[0] * 2.0f) - 1.0f; // UNUSED
const float image_cb = (in_image[1] * 2.0f) - 1.0f;
const float image_cr = (in_image[2] * 2.0f) - 1.0f;
// const float key_Y = (in_key[0] * 2.0f) - 1.0f; // UNUSED
const float key_cb = (in_key[1] * 2.0f) - 1.0f;
const float key_cr = (in_key[2] * 2.0f) - 1.0f;
const float theta = atan2(key_cr, key_cb);
/* Rotate the cb and cr into x/z space. */
const float x_angle = image_cb * cosf(theta) + image_cr * sinf(theta);
const float z_angle = image_cr * cosf(theta) - image_cb * sinf(theta);
/* If within the acceptance angle. */
/* If kfg is <0 then the pixel is outside of the key color. */
const float kfg = x_angle - (fabsf(z_angle) / tanf(acceptance / 2.0f));
if (kfg > 0.0f) { /* Found a pixel that is within key color. */
const float beta = atan2(z_angle, x_angle);
float alpha = 1.0f - (kfg / gain);
/* Ff beta is within the cutoff angle. */
if (fabsf(beta) < (cutoff / 2.0f)) {
alpha = 0.0f;
}
/* Don't make something that was more transparent less transparent. */
it.out[0] = alpha < in_image[3] ? alpha : in_image[3];
}
else { /* Pixel is outside key color. */
it.out[0] = in_image[3]; /* Make pixel just as transparent as it was before. */
}
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_MixOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,7 +26,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class ChromaMatteOperation : public NodeOperation {
class ChromaMatteOperation : public MultiThreadedOperation {
private:
NodeChroma *m_settings;
SocketReader *m_inputImageProgram;
@ -50,6 +50,10 @@ class ChromaMatteOperation : public NodeOperation {
{
this->m_settings = nodeChroma;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -29,6 +29,7 @@ ColorMatteOperation::ColorMatteOperation()
this->m_inputImageProgram = nullptr;
this->m_inputKeyProgram = nullptr;
flags.can_be_constant = true;
}
void ColorMatteOperation::initExecution()
@ -82,4 +83,40 @@ void ColorMatteOperation::executePixelSampled(float output[4],
}
}
void ColorMatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const float hue = m_settings->t1;
const float sat = m_settings->t2;
const float val = m_settings->t3;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *in_color = it.in(0);
const float *in_key = it.in(1);
/* Store matte(alpha) value in [0] to go with
* COM_SetAlphaMultiplyOperation and the Value output.
*/
float h_wrap;
if (
/* Do hue last because it needs to wrap, and does some more checks. */
/* #sat */ (fabsf(in_color[1] - in_key[1]) < sat) &&
/* #val */ (fabsf(in_color[2] - in_key[2]) < val) &&
/* Multiply by 2 because it wraps on both sides of the hue,
* otherwise 0.5 would key all hue's. */
/* #hue */
((h_wrap = 2.0f * fabsf(in_color[0] - in_key[0])) < hue || (2.0f - h_wrap) < hue)) {
it.out[0] = 0.0f; /* Make transparent. */
}
else { /* Pixel is outside key color. */
it.out[0] = in_color[3]; /* Make pixel just as transparent as it was before. */
}
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_MixOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,7 +26,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class ColorMatteOperation : public NodeOperation {
class ColorMatteOperation : public MultiThreadedOperation {
private:
NodeChroma *m_settings;
SocketReader *m_inputImageProgram;
@ -50,6 +50,10 @@ class ColorMatteOperation : public NodeOperation {
{
this->m_settings = nodeChroma;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -32,6 +32,7 @@ ColorSpillOperation::ColorSpillOperation()
this->m_inputFacReader = nullptr;
this->m_spillChannel = 1; // GREEN
this->m_spillMethod = 0;
flags.can_be_constant = true;
}
void ColorSpillOperation::initExecution()
@ -118,4 +119,36 @@ void ColorSpillOperation::executePixelSampled(float output[4],
}
}
void ColorSpillOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *color = it.in(0);
const float factor = MIN2(1.0f, *it.in(1));
float map;
switch (m_spillMethod) {
case 0: /* simple */
map = factor *
(color[m_spillChannel] - (m_settings->limscale * color[m_settings->limchan]));
break;
default: /* average */
map = factor * (color[m_spillChannel] -
(m_settings->limscale * AVG(color[m_channel2], color[m_channel3])));
break;
}
if (map > 0.0f) {
it.out[0] = color[0] + m_rmut * (m_settings->uspillr * map);
it.out[1] = color[1] + m_gmut * (m_settings->uspillg * map);
it.out[2] = color[2] + m_bmut * (m_settings->uspillb * map);
it.out[3] = color[3];
}
else {
copy_v4_v4(it.out, color);
}
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,7 +26,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class ColorSpillOperation : public NodeOperation {
class ColorSpillOperation : public MultiThreadedOperation {
protected:
NodeColorspill *m_settings;
SocketReader *m_inputImageReader;
@ -65,6 +65,10 @@ class ColorSpillOperation : public NodeOperation {
}
float calculateMapValue(float fac, float *input);
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -71,4 +71,34 @@ void CryptomatteOperation::executePixel(float output[4], int x, int y, void *dat
}
}
void CryptomatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
zero_v4(it.out);
for (int i = 0; i < it.get_num_inputs(); i++) {
const float *input = it.in(i);
if (i == 0) {
/* Write the front-most object as false color for picking. */
it.out[0] = input[0];
uint32_t m3hash;
::memcpy(&m3hash, &input[0], sizeof(uint32_t));
/* Since the red channel is likely to be out of display range,
* setting green and blue gives more meaningful images. */
it.out[1] = ((float)(m3hash << 8) / (float)UINT32_MAX);
it.out[2] = ((float)(m3hash << 16) / (float)UINT32_MAX);
}
for (const float hash : m_objectIndex) {
if (input[0] == hash) {
it.out[3] += input[1];
}
if (input[2] == hash) {
it.out[3] += input[3];
}
}
}
}
}
} // namespace blender::compositor

View File

@ -18,11 +18,11 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class CryptomatteOperation : public NodeOperation {
class CryptomatteOperation : public MultiThreadedOperation {
private:
Vector<float> m_objectIndex;
@ -35,6 +35,10 @@ class CryptomatteOperation : public NodeOperation {
void executePixel(float output[4], int x, int y, void *data) override;
void addObjectIndex(float objectIndex);
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -29,6 +29,7 @@ DifferenceMatteOperation::DifferenceMatteOperation()
this->m_inputImage1Program = nullptr;
this->m_inputImage2Program = nullptr;
flags.can_be_constant = true;
}
void DifferenceMatteOperation::initExecution()
@ -86,4 +87,44 @@ void DifferenceMatteOperation::executePixelSampled(float output[4],
}
}
void DifferenceMatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *color1 = it.in(0);
const float *color2 = it.in(1);
float difference = (fabsf(color2[0] - color1[0]) + fabsf(color2[1] - color1[1]) +
fabsf(color2[2] - color1[2]));
/* Average together the distances. */
difference = difference / 3.0f;
const float tolerance = m_settings->t1;
const float falloff = m_settings->t2;
/* Make 100% transparent. */
if (difference <= tolerance) {
it.out[0] = 0.0f;
}
/* In the falloff region, make partially transparent. */
else if (difference <= falloff + tolerance) {
difference = difference - tolerance;
const float alpha = difference / falloff;
/* Only change if more transparent than before. */
if (alpha < color1[3]) {
it.out[0] = alpha;
}
else { /* Leave as before. */
it.out[0] = color1[3];
}
}
else {
/* Foreground object. */
it.out[0] = color1[3];
}
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_MixOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,7 +26,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class DifferenceMatteOperation : public NodeOperation {
class DifferenceMatteOperation : public MultiThreadedOperation {
private:
NodeChroma *m_settings;
SocketReader *m_inputImage1Program;
@ -50,6 +50,10 @@ class DifferenceMatteOperation : public NodeOperation {
{
this->m_settings = nodeChroma;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -29,6 +29,7 @@ DistanceRGBMatteOperation::DistanceRGBMatteOperation()
this->m_inputImageProgram = nullptr;
this->m_inputKeyProgram = nullptr;
flags.can_be_constant = true;
}
void DistanceRGBMatteOperation::initExecution()
@ -43,7 +44,7 @@ void DistanceRGBMatteOperation::deinitExecution()
this->m_inputKeyProgram = nullptr;
}
float DistanceRGBMatteOperation::calculateDistance(float key[4], float image[4])
float DistanceRGBMatteOperation::calculateDistance(const float key[4], const float image[4])
{
return len_v3v3(key, image);
}
@ -93,4 +94,43 @@ void DistanceRGBMatteOperation::executePixelSampled(float output[4],
}
}
void DistanceRGBMatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *in_image = it.in(0);
const float *in_key = it.in(1);
float distance = this->calculateDistance(in_key, in_image);
const float tolerance = this->m_settings->t1;
const float falloff = this->m_settings->t2;
/* Store matte(alpha) value in [0] to go with
* COM_SetAlphaMultiplyOperation and the Value output.
*/
/* Make 100% transparent. */
if (distance < tolerance) {
it.out[0] = 0.0f;
}
/* In the falloff region, make partially transparent. */
else if (distance < falloff + tolerance) {
distance = distance - tolerance;
const float alpha = distance / falloff;
/* Only change if more transparent than before. */
if (alpha < in_image[3]) {
it.out[0] = alpha;
}
else { /* Leave as before. */
it.out[0] = in_image[3];
}
}
else {
/* Leave as before. */
it.out[0] = in_image[3];
}
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_MixOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,13 +26,13 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class DistanceRGBMatteOperation : public NodeOperation {
class DistanceRGBMatteOperation : public MultiThreadedOperation {
protected:
NodeChroma *m_settings;
SocketReader *m_inputImageProgram;
SocketReader *m_inputKeyProgram;
virtual float calculateDistance(float key[4], float image[4]);
virtual float calculateDistance(const float key[4], const float image[4]);
public:
/**
@ -52,6 +52,10 @@ class DistanceRGBMatteOperation : public NodeOperation {
{
this->m_settings = nodeChroma;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -21,7 +21,7 @@
namespace blender::compositor {
float DistanceYCCMatteOperation::calculateDistance(float key[4], float image[4])
float DistanceYCCMatteOperation::calculateDistance(const float key[4], const float image[4])
{
/* only measure the second 2 values */
return len_v2v2(key + 1, image + 1);

View File

@ -29,7 +29,7 @@ namespace blender::compositor {
*/
class DistanceYCCMatteOperation : public DistanceRGBMatteOperation {
protected:
float calculateDistance(float key[4], float image[4]) override;
float calculateDistance(const float key[4], const float image[4]) override;
};
} // namespace blender::compositor

View File

@ -96,4 +96,67 @@ bool KeyingBlurOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void KeyingBlurOperation::get_area_of_interest(const int UNUSED(input_idx),
const rcti &output_area,
rcti &r_input_area)
{
switch (m_axis) {
case BLUR_AXIS_X:
r_input_area.xmin = output_area.xmin - m_size;
r_input_area.ymin = output_area.ymin;
r_input_area.xmax = output_area.xmax + m_size;
r_input_area.ymax = output_area.ymax;
break;
case BLUR_AXIS_Y:
r_input_area.xmin = output_area.xmin;
r_input_area.ymin = output_area.ymin - m_size;
r_input_area.xmax = output_area.xmax;
r_input_area.ymax = output_area.ymax + m_size;
break;
default:
BLI_assert_msg(0, "Unknown axis");
break;
}
}
void KeyingBlurOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input = inputs[0];
BuffersIterator<float> it = output->iterate_with(inputs, area);
int coord_max;
int elem_stride;
std::function<int()> get_current_coord;
switch (m_axis) {
case BLUR_AXIS_X:
get_current_coord = [&] { return it.x; };
coord_max = this->getWidth();
elem_stride = input->elem_stride;
break;
case BLUR_AXIS_Y:
get_current_coord = [&] { return it.y; };
coord_max = this->getHeight();
elem_stride = input->row_stride;
break;
}
for (; !it.is_end(); ++it) {
const int coord = get_current_coord();
const int start_coord = MAX2(0, coord - m_size + 1);
const int end_coord = MIN2(coord_max, coord + m_size);
const int count = end_coord - start_coord;
float sum = 0.0f;
const float *start = it.in(0) + (start_coord - coord) * elem_stride;
const float *end = start + count * elem_stride;
for (const float *elem = start; elem < end; elem += elem_stride) {
sum += *elem;
}
*it.out = sum / count;
}
}
} // namespace blender::compositor

View File

@ -18,14 +18,14 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
/**
* Class with implementation of blurring for keying node
*/
class KeyingBlurOperation : public NodeOperation {
class KeyingBlurOperation : public MultiThreadedOperation {
protected:
int m_size;
int m_axis;
@ -54,6 +54,13 @@ class KeyingBlurOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
void get_area_of_interest(const 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

@ -130,4 +130,89 @@ bool KeyingClipOperation::determineDependingAreaOfInterest(rcti *input,
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void KeyingClipOperation::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_kernelRadius;
r_input_area.xmax = output_area.xmax + m_kernelRadius;
r_input_area.ymin = output_area.ymin - m_kernelRadius;
r_input_area.ymax = output_area.ymax + m_kernelRadius;
}
void KeyingClipOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
const MemoryBuffer *input = inputs[0];
BuffersIterator<float> it = output->iterate_with(inputs, area);
const int delta = m_kernelRadius;
const float tolerance = m_kernelTolerance;
const int width = this->getWidth();
const int height = this->getHeight();
const int row_stride = input->row_stride;
const int elem_stride = input->elem_stride;
for (; !it.is_end(); ++it) {
const int x = it.x;
const int y = it.y;
const int start_x = MAX2(0, x - delta + 1);
const int start_y = MAX2(0, y - delta + 1);
const int end_x = MIN2(x + delta, width);
const int end_y = MIN2(y + delta, height);
const int x_len = end_x - start_x;
const int y_len = end_y - start_y;
const int total_count = x_len * y_len - 1;
const int threshold_count = ceil((float)total_count * 0.9f);
bool ok = false;
if (delta == 0) {
ok = true;
}
const float *main_elem = it.in(0);
const float value = *main_elem;
const float *row = input->get_elem(start_x, start_y);
const float *end_row = row + y_len * row_stride;
int count = 0;
for (; ok == false && row < end_row; row += row_stride) {
const float *end_elem = row + x_len * elem_stride;
for (const float *elem = row; ok == false && elem < end_elem; elem += elem_stride) {
if (UNLIKELY(elem == main_elem)) {
continue;
}
const float current_value = *elem;
if (fabsf(current_value - value) < tolerance) {
count++;
if (count >= threshold_count) {
ok = true;
}
}
}
}
if (m_isEdgeMatte) {
*it.out = ok ? 0.0f : 1.0f;
}
else {
if (!ok) {
*it.out = value;
}
else if (value < m_clipBlack) {
*it.out = 0.0f;
}
else if (value >= m_clipWhite) {
*it.out = 1.0f;
}
else {
*it.out = (value - m_clipBlack) / (m_clipWhite - m_clipBlack);
}
}
}
}
} // namespace blender::compositor

View File

@ -18,14 +18,14 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
/**
* Class with implementation of black/white clipping for keying node
*/
class KeyingClipOperation : public NodeOperation {
class KeyingClipOperation : public MultiThreadedOperation {
protected:
float m_clipBlack;
float m_clipWhite;
@ -68,6 +68,13 @@ class KeyingClipOperation : public NodeOperation {
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
void get_area_of_interest(const 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

@ -36,6 +36,7 @@ KeyingDespillOperation::KeyingDespillOperation()
this->m_pixelReader = nullptr;
this->m_screenReader = nullptr;
flags.can_be_constant = true;
}
void KeyingDespillOperation::initExecution()
@ -82,4 +83,32 @@ void KeyingDespillOperation::executePixelSampled(float output[4],
}
}
void KeyingDespillOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *pixel_color = it.in(0);
const float *screen_color = it.in(1);
const int screen_primary_channel = max_axis_v3(screen_color);
const int other_1 = (screen_primary_channel + 1) % 3;
const int other_2 = (screen_primary_channel + 2) % 3;
const int min_channel = MIN2(other_1, other_2);
const int max_channel = MAX2(other_1, other_2);
const float average_value = m_colorBalance * pixel_color[min_channel] +
(1.0f - m_colorBalance) * pixel_color[max_channel];
const float amount = (pixel_color[screen_primary_channel] - average_value);
copy_v4_v4(it.out, pixel_color);
const float amount_despill = m_despillFactor * amount;
if (amount_despill > 0.0f) {
it.out[screen_primary_channel] = pixel_color[screen_primary_channel] - amount_despill;
}
}
}
} // namespace blender::compositor

View File

@ -18,14 +18,14 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
/**
* Class with implementation of keying despill node
*/
class KeyingDespillOperation : public NodeOperation {
class KeyingDespillOperation : public MultiThreadedOperation {
protected:
SocketReader *m_pixelReader;
SocketReader *m_screenReader;
@ -48,6 +48,10 @@ class KeyingDespillOperation : public NodeOperation {
}
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;
};
} // namespace blender::compositor

View File

@ -110,4 +110,49 @@ void KeyingOperation::executePixelSampled(float output[4], float x, float y, Pix
}
}
void KeyingOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *pixel_color = it.in(0);
const float *screen_color = it.in(1);
const int primary_channel = max_axis_v3(screen_color);
const float min_pixel_color = min_fff(pixel_color[0], pixel_color[1], pixel_color[2]);
if (min_pixel_color > 1.0f) {
/* Overexposure doesn't happen on screen itself and usually happens
* on light sources in the shot, this need to be checked separately
* because saturation and falloff calculation is based on the fact
* that pixels are not overexposed.
*/
it.out[0] = 1.0f;
}
else {
const float saturation = get_pixel_saturation(pixel_color, m_screenBalance, primary_channel);
const float screen_saturation = get_pixel_saturation(
screen_color, m_screenBalance, primary_channel);
if (saturation < 0) {
/* Means main channel of pixel is different from screen,
* assume this is completely a foreground.
*/
it.out[0] = 1.0f;
}
else if (saturation >= screen_saturation) {
/* Matched main channels and higher saturation on pixel
* is treated as completely background.
*/
it.out[0] = 0.0f;
}
else {
/* Nice alpha falloff on edges. */
const float distance = 1.0f - saturation / screen_saturation;
it.out[0] = distance;
}
}
}
}
} // namespace blender::compositor

View File

@ -20,7 +20,7 @@
#include <string.h>
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
#include "BLI_listbase.h"
@ -29,7 +29,7 @@ namespace blender::compositor {
/**
* Class with implementation of keying node
*/
class KeyingOperation : public NodeOperation {
class KeyingOperation : public MultiThreadedOperation {
protected:
SocketReader *m_pixelReader;
SocketReader *m_screenReader;
@ -48,6 +48,10 @@ class KeyingOperation : public NodeOperation {
}
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;
};
} // namespace blender::compositor

View File

@ -39,12 +39,21 @@ KeyingScreenOperation::KeyingScreenOperation()
this->m_framenumber = 0;
this->m_trackingObject[0] = 0;
flags.complex = true;
m_cachedTriangulation = nullptr;
}
void KeyingScreenOperation::initExecution()
{
initMutex();
this->m_cachedTriangulation = nullptr;
if (execution_model_ == eExecutionModel::FullFrame) {
BLI_assert(m_cachedTriangulation == nullptr);
if (m_movieClip) {
m_cachedTriangulation = buildVoronoiTriangulation();
}
}
else {
this->m_cachedTriangulation = nullptr;
}
}
void KeyingScreenOperation::deinitExecution()
@ -226,7 +235,7 @@ KeyingScreenOperation::TriangulationData *KeyingScreenOperation::buildVoronoiTri
return triangulation;
}
void *KeyingScreenOperation::initializeTileData(rcti *rect)
KeyingScreenOperation::TileData *KeyingScreenOperation::triangulate(const rcti *rect)
{
TileData *tile_data;
TriangulationData *triangulation;
@ -234,18 +243,6 @@ void *KeyingScreenOperation::initializeTileData(rcti *rect)
int chunk_size = 20;
int i;
if (this->m_movieClip == nullptr) {
return nullptr;
}
if (!this->m_cachedTriangulation) {
lockMutex();
if (this->m_cachedTriangulation == nullptr) {
this->m_cachedTriangulation = buildVoronoiTriangulation();
}
unlockMutex();
}
triangulation = this->m_cachedTriangulation;
if (!triangulation) {
@ -278,6 +275,23 @@ void *KeyingScreenOperation::initializeTileData(rcti *rect)
return tile_data;
}
void *KeyingScreenOperation::initializeTileData(rcti *rect)
{
if (this->m_movieClip == nullptr) {
return nullptr;
}
if (!this->m_cachedTriangulation) {
lockMutex();
if (this->m_cachedTriangulation == nullptr) {
this->m_cachedTriangulation = buildVoronoiTriangulation();
}
unlockMutex();
}
return triangulate(rect);
}
void KeyingScreenOperation::deinitializeTileData(rcti * /*rect*/, void *data)
{
TileData *tile_data = (TileData *)data;
@ -347,4 +361,57 @@ void KeyingScreenOperation::executePixel(float output[4], int x, int y, void *da
}
}
void KeyingScreenOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
if (m_movieClip == nullptr) {
output->fill(area, COM_COLOR_BLACK);
return;
}
TileData *tri_area = this->triangulate(&area);
BLI_assert(tri_area != nullptr);
const int *triangles = tri_area->triangles;
const int num_triangles = tri_area->triangles_total;
const TriangulationData *triangulation = m_cachedTriangulation;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
copy_v4_v4(it.out, COM_COLOR_BLACK);
const float co[2] = {(float)it.x, (float)it.y};
for (int i = 0; i < num_triangles; i++) {
const int triangle_idx = triangles[i];
const rcti *rect = &triangulation->triangles_AABB[triangle_idx];
if (!BLI_rcti_isect_pt(rect, it.x, it.y)) {
continue;
}
const int *triangle = triangulation->triangles[triangle_idx];
const VoronoiTriangulationPoint &a = triangulation->triangulated_points[triangle[0]];
const VoronoiTriangulationPoint &b = triangulation->triangulated_points[triangle[1]];
const VoronoiTriangulationPoint &c = triangulation->triangulated_points[triangle[2]];
float w[3];
if (!barycentric_coords_v2(a.co, b.co, c.co, co, w)) {
continue;
}
if (barycentric_inside_triangle_v2(w)) {
it.out[0] = a.color[0] * w[0] + b.color[0] * w[1] + c.color[0] * w[2];
it.out[1] = a.color[1] * w[0] + b.color[1] * w[1] + c.color[1] * w[2];
it.out[2] = a.color[2] * w[0] + b.color[2] * w[1] + c.color[2] * w[2];
break;
}
}
}
if (tri_area->triangles) {
MEM_freeN(tri_area->triangles);
}
MEM_freeN(tri_area);
}
} // 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"
@ -34,7 +34,7 @@ namespace blender::compositor {
/**
* Class with implementation of green screen gradient rasterization
*/
class KeyingScreenOperation : public NodeOperation {
class KeyingScreenOperation : public MultiThreadedOperation {
protected:
typedef struct TriangulationData {
VoronoiTriangulationPoint *triangulated_points;
@ -43,6 +43,7 @@ class KeyingScreenOperation : public NodeOperation {
rcti *triangles_AABB;
} TriangulationData;
/* TODO(manzanilla): rename to #TrianguledArea on removing tiled implementation. */
typedef struct TileData {
int *triangles;
int triangles_total;
@ -84,6 +85,13 @@ class KeyingScreenOperation : public NodeOperation {
}
void executePixel(float output[4], int x, int y, void *data) override;
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
private:
TileData *triangulate(const rcti *rect);
};
} // namespace blender::compositor

View File

@ -29,6 +29,7 @@ LuminanceMatteOperation::LuminanceMatteOperation()
addOutputSocket(DataType::Value);
this->m_inputImageProgram = nullptr;
flags.can_be_constant = true;
}
void LuminanceMatteOperation::initExecution()
@ -78,4 +79,39 @@ void LuminanceMatteOperation::executePixelSampled(float output[4],
output[0] = min_ff(alpha, inColor[3]);
}
void LuminanceMatteOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const float *color = it.in(0);
const float luminance = IMB_colormanagement_get_luminance(color);
/* One line thread-friend algorithm:
* `it.out[0] = MIN2(color[3], MIN2(1.0f, MAX2(0.0f, ((luminance - low) / (high - low))));`
*/
/* Test range. */
const float high = m_settings->t1;
const float low = m_settings->t2;
float alpha;
if (luminance > high) {
alpha = 1.0f;
}
else if (luminance < low) {
alpha = 0.0f;
}
else { /* Blend. */
alpha = (luminance - low) / (high - low);
}
/* Store matte(alpha) value in [0] to go with
* COM_SetAlphaMultiplyOperation and the Value output.
*/
/* Don't make something that was more transparent less transparent. */
it.out[0] = MIN2(alpha, color[3]);
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_MixOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
@ -26,7 +26,7 @@ namespace blender::compositor {
* this program converts an input color to an output value.
* it assumes we are in sRGB color space.
*/
class LuminanceMatteOperation : public NodeOperation {
class LuminanceMatteOperation : public MultiThreadedOperation {
private:
NodeChroma *m_settings;
SocketReader *m_inputImageProgram;
@ -49,6 +49,10 @@ class LuminanceMatteOperation : public NodeOperation {
{
this->m_settings = nodeChroma;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor