diff options
28 files changed, 701 insertions, 44 deletions
diff --git a/source/blender/compositor/operations/COM_ChannelMatteOperation.cc b/source/blender/compositor/operations/COM_ChannelMatteOperation.cc index ec4331dc231..65742d0cfcc 100644 --- a/source/blender/compositor/operations/COM_ChannelMatteOperation.cc +++ b/source/blender/compositor/operations/COM_ChannelMatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_ChannelMatteOperation.h b/source/blender/compositor/operations/COM_ChannelMatteOperation.h index 6e9dcccd36e..ba50105dd3b 100644 --- a/source/blender/compositor/operations/COM_ChannelMatteOperation.h +++ b/source/blender/compositor/operations/COM_ChannelMatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_ChromaMatteOperation.cc b/source/blender/compositor/operations/COM_ChromaMatteOperation.cc index b7fec5f07e5..0784f266b19 100644 --- a/source/blender/compositor/operations/COM_ChromaMatteOperation.cc +++ b/source/blender/compositor/operations/COM_ChromaMatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_ChromaMatteOperation.h b/source/blender/compositor/operations/COM_ChromaMatteOperation.h index 48c3a785011..065349910a7 100644 --- a/source/blender/compositor/operations/COM_ChromaMatteOperation.h +++ b/source/blender/compositor/operations/COM_ChromaMatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_ColorMatteOperation.cc b/source/blender/compositor/operations/COM_ColorMatteOperation.cc index ddfbf415d9c..dec6571f217 100644 --- a/source/blender/compositor/operations/COM_ColorMatteOperation.cc +++ b/source/blender/compositor/operations/COM_ColorMatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_ColorMatteOperation.h b/source/blender/compositor/operations/COM_ColorMatteOperation.h index 439a3b0741d..49d06e62e65 100644 --- a/source/blender/compositor/operations/COM_ColorMatteOperation.h +++ b/source/blender/compositor/operations/COM_ColorMatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_ColorSpillOperation.cc b/source/blender/compositor/operations/COM_ColorSpillOperation.cc index 7dc7e2775fc..4b0e0520b75 100644 --- a/source/blender/compositor/operations/COM_ColorSpillOperation.cc +++ b/source/blender/compositor/operations/COM_ColorSpillOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_ColorSpillOperation.h b/source/blender/compositor/operations/COM_ColorSpillOperation.h index 9b82e720527..6a5e688c160 100644 --- a/source/blender/compositor/operations/COM_ColorSpillOperation.h +++ b/source/blender/compositor/operations/COM_ColorSpillOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_CryptomatteOperation.cc b/source/blender/compositor/operations/COM_CryptomatteOperation.cc index 1a86fadad76..02e7c5607d8 100644 --- a/source/blender/compositor/operations/COM_CryptomatteOperation.cc +++ b/source/blender/compositor/operations/COM_CryptomatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_CryptomatteOperation.h b/source/blender/compositor/operations/COM_CryptomatteOperation.h index 1b91358d228..f1bf4cdf624 100644 --- a/source/blender/compositor/operations/COM_CryptomatteOperation.h +++ b/source/blender/compositor/operations/COM_CryptomatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_DifferenceMatteOperation.cc b/source/blender/compositor/operations/COM_DifferenceMatteOperation.cc index 0acdfc1651f..31714b03b06 100644 --- a/source/blender/compositor/operations/COM_DifferenceMatteOperation.cc +++ b/source/blender/compositor/operations/COM_DifferenceMatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_DifferenceMatteOperation.h b/source/blender/compositor/operations/COM_DifferenceMatteOperation.h index d3963fee1c1..0a86535d946 100644 --- a/source/blender/compositor/operations/COM_DifferenceMatteOperation.h +++ b/source/blender/compositor/operations/COM_DifferenceMatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.cc b/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.cc index 1b3403cbb29..8155ff769a0 100644 --- a/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.cc +++ b/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.h b/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.h index 6fe603233b7..ba6682214ae 100644 --- a/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.h +++ b/source/blender/compositor/operations/COM_DistanceRGBMatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.cc b/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.cc index 597545dd706..50e473ea5b3 100644 --- a/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.cc +++ b/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.cc @@ -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); diff --git a/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.h b/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.h index a87e885e5d8..0e178fddc39 100644 --- a/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.h +++ b/source/blender/compositor/operations/COM_DistanceYCCMatteOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingBlurOperation.cc b/source/blender/compositor/operations/COM_KeyingBlurOperation.cc index 994b00cd3f4..d5ebd5e9df7 100644 --- a/source/blender/compositor/operations/COM_KeyingBlurOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingBlurOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingBlurOperation.h b/source/blender/compositor/operations/COM_KeyingBlurOperation.h index b055d7713f1..b290b905e63 100644 --- a/source/blender/compositor/operations/COM_KeyingBlurOperation.h +++ b/source/blender/compositor/operations/COM_KeyingBlurOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingClipOperation.cc b/source/blender/compositor/operations/COM_KeyingClipOperation.cc index 4029be4e077..817c920ed91 100644 --- a/source/blender/compositor/operations/COM_KeyingClipOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingClipOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingClipOperation.h b/source/blender/compositor/operations/COM_KeyingClipOperation.h index 0a21fb48c99..1a17d591781 100644 --- a/source/blender/compositor/operations/COM_KeyingClipOperation.h +++ b/source/blender/compositor/operations/COM_KeyingClipOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingDespillOperation.cc b/source/blender/compositor/operations/COM_KeyingDespillOperation.cc index d31a88cb91e..620b767e584 100644 --- a/source/blender/compositor/operations/COM_KeyingDespillOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingDespillOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingDespillOperation.h b/source/blender/compositor/operations/COM_KeyingDespillOperation.h index 279ac60e6e9..16bed651d3a 100644 --- a/source/blender/compositor/operations/COM_KeyingDespillOperation.h +++ b/source/blender/compositor/operations/COM_KeyingDespillOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingOperation.cc b/source/blender/compositor/operations/COM_KeyingOperation.cc index e786e4b8219..3edb5a5d34e 100644 --- a/source/blender/compositor/operations/COM_KeyingOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingOperation.h b/source/blender/compositor/operations/COM_KeyingOperation.h index 3d41ecaa0f6..e134ad54896 100644 --- a/source/blender/compositor/operations/COM_KeyingOperation.h +++ b/source/blender/compositor/operations/COM_KeyingOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc index 17b613246ad..c00aafc19a2 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.cc +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_KeyingScreenOperation.h b/source/blender/compositor/operations/COM_KeyingScreenOperation.h index 4118d229be9..0bc47dbea30 100644 --- a/source/blender/compositor/operations/COM_KeyingScreenOperation.h +++ b/source/blender/compositor/operations/COM_KeyingScreenOperation.h @@ -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 diff --git a/source/blender/compositor/operations/COM_LuminanceMatteOperation.cc b/source/blender/compositor/operations/COM_LuminanceMatteOperation.cc index 5ca16e40ce3..c642c60b912 100644 --- a/source/blender/compositor/operations/COM_LuminanceMatteOperation.cc +++ b/source/blender/compositor/operations/COM_LuminanceMatteOperation.cc @@ -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 diff --git a/source/blender/compositor/operations/COM_LuminanceMatteOperation.h b/source/blender/compositor/operations/COM_LuminanceMatteOperation.h index 035c68b9d59..aedfc715382 100644 --- a/source/blender/compositor/operations/COM_LuminanceMatteOperation.h +++ b/source/blender/compositor/operations/COM_LuminanceMatteOperation.h @@ -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 |