Compositor: Full frame color nodes

Adds full frame implementation to "Alpha Over",
"Hue Saturation Value", "Invert", "Tonemap" and "ZCombine" nodes.
The other nodes in "Color" submenu are implemented separately.
No functional changes.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D12092
This commit is contained in:
Manuel Castilla 2021-08-10 15:25:10 +02:00
parent 8f6cc16490
commit d481c6651d
14 changed files with 385 additions and 8 deletions

View File

@ -20,6 +20,11 @@
namespace blender::compositor {
AlphaOverKeyOperation::AlphaOverKeyOperation()
{
this->flags.can_be_constant = true;
}
void AlphaOverKeyOperation::executePixelSampled(float output[4],
float x,
float y,
@ -50,4 +55,29 @@ void AlphaOverKeyOperation::executePixelSampled(float output[4],
}
}
void AlphaOverKeyOperation::update_memory_buffer_row(PixelCursor &p)
{
for (; p.out < p.row_end; p.next()) {
const float *color1 = p.color1;
const float *over_color = p.color2;
const float value = *p.value;
if (over_color[3] <= 0.0f) {
copy_v4_v4(p.out, color1);
}
else if (value == 1.0f && over_color[3] >= 1.0f) {
copy_v4_v4(p.out, over_color);
}
else {
const float premul = value * over_color[3];
const float mul = 1.0f - premul;
p.out[0] = (mul * color1[0]) + premul * over_color[0];
p.out[1] = (mul * color1[1]) + premul * over_color[1];
p.out[2] = (mul * color1[2]) + premul * over_color[2];
p.out[3] = (mul * color1[3]) + value * over_color[3];
}
}
}
} // namespace blender::compositor

View File

@ -28,10 +28,14 @@ namespace blender::compositor {
*/
class AlphaOverKeyOperation : public MixBaseOperation {
public:
AlphaOverKeyOperation();
/**
* The inner loop of this operation.
*/
void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override;
void update_memory_buffer_row(PixelCursor &p) override;
};
} // namespace blender::compositor

View File

@ -23,6 +23,7 @@ namespace blender::compositor {
AlphaOverMixedOperation::AlphaOverMixedOperation()
{
this->m_x = 0.0f;
this->flags.can_be_constant = true;
}
void AlphaOverMixedOperation::executePixelSampled(float output[4],
@ -56,4 +57,30 @@ void AlphaOverMixedOperation::executePixelSampled(float output[4],
}
}
void AlphaOverMixedOperation::update_memory_buffer_row(PixelCursor &p)
{
for (; p.out < p.row_end; p.next()) {
const float *color1 = p.color1;
const float *over_color = p.color2;
const float value = *p.value;
if (over_color[3] <= 0.0f) {
copy_v4_v4(p.out, color1);
}
else if (value == 1.0f && over_color[3] >= 1.0f) {
copy_v4_v4(p.out, over_color);
}
else {
const float addfac = 1.0f - this->m_x + over_color[3] * this->m_x;
const float premul = value * addfac;
const float mul = 1.0f - value * over_color[3];
p.out[0] = (mul * color1[0]) + premul * over_color[0];
p.out[1] = (mul * color1[1]) + premul * over_color[1];
p.out[2] = (mul * color1[2]) + premul * over_color[2];
p.out[3] = (mul * color1[3]) + value * over_color[3];
}
}
}
} // namespace blender::compositor

View File

@ -45,6 +45,8 @@ class AlphaOverMixedOperation : public MixBaseOperation {
{
this->m_x = x;
}
void update_memory_buffer_row(PixelCursor &p) override;
};
} // namespace blender::compositor

View File

@ -20,6 +20,11 @@
namespace blender::compositor {
AlphaOverPremultiplyOperation::AlphaOverPremultiplyOperation()
{
this->flags.can_be_constant = true;
}
void AlphaOverPremultiplyOperation::executePixelSampled(float output[4],
float x,
float y,
@ -50,4 +55,28 @@ void AlphaOverPremultiplyOperation::executePixelSampled(float output[4],
}
}
void AlphaOverPremultiplyOperation::update_memory_buffer_row(PixelCursor &p)
{
for (; p.out < p.row_end; p.next()) {
const float *color1 = p.color1;
const float *over_color = p.color2;
const float value = *p.value;
if (over_color[3] <= 0.0f) {
copy_v4_v4(p.out, color1);
}
else if (value == 1.0f && over_color[3] >= 1.0f) {
copy_v4_v4(p.out, over_color);
}
else {
const float mul = 1.0f - value * over_color[3];
p.out[0] = (mul * color1[0]) + value * over_color[0];
p.out[1] = (mul * color1[1]) + value * over_color[1];
p.out[2] = (mul * color1[2]) + value * over_color[2];
p.out[3] = (mul * color1[3]) + value * over_color[3];
}
}
}
} // namespace blender::compositor

View File

@ -28,10 +28,14 @@ namespace blender::compositor {
*/
class AlphaOverPremultiplyOperation : public MixBaseOperation {
public:
AlphaOverPremultiplyOperation();
/**
* The inner loop of this operation.
*/
void executePixelSampled(float output[4], float x, float y, PixelSampler sampler) override;
void update_memory_buffer_row(PixelCursor &p) override;
};
} // namespace blender::compositor

View File

@ -28,6 +28,7 @@ ChangeHSVOperation::ChangeHSVOperation()
this->addInputSocket(DataType::Value);
this->addOutputSocket(DataType::Color);
this->m_inputOperation = nullptr;
this->flags.can_be_constant = true;
}
void ChangeHSVOperation::initExecution()
@ -71,4 +72,26 @@ void ChangeHSVOperation::executePixelSampled(float output[4],
output[3] = inputColor1[3];
}
void ChangeHSVOperation::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 hue = *it.in(1);
it.out[0] = color[0] + (hue - 0.5f);
if (it.out[0] > 1.0f) {
it.out[0] -= 1.0f;
}
else if (it.out[0] < 0.0f) {
it.out[0] += 1.0f;
}
const float saturation = *it.in(2);
const float value = *it.in(3);
it.out[1] = color[1] * saturation;
it.out[2] = color[2] * value;
it.out[3] = 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 ChangeHSVOperation : public NodeOperation {
class ChangeHSVOperation : public MultiThreadedOperation {
private:
SocketReader *m_inputOperation;
SocketReader *m_hueOperation;
@ -46,6 +46,10 @@ class ChangeHSVOperation : public NodeOperation {
* The inner loop of this operation.
*/
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

@ -30,6 +30,7 @@ InvertOperation::InvertOperation()
this->m_color = true;
this->m_alpha = false;
setResolutionInputSocketIndex(1);
this->flags.can_be_constant = true;
}
void InvertOperation::initExecution()
{
@ -70,4 +71,31 @@ void InvertOperation::deinitExecution()
this->m_inputColorProgram = nullptr;
}
void InvertOperation::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 value = *it.in(0);
const float inverted_value = 1.0f - value;
const float *color = it.in(1);
if (this->m_color) {
it.out[0] = (1.0f - color[0]) * value + color[0] * inverted_value;
it.out[1] = (1.0f - color[1]) * value + color[1] * inverted_value;
it.out[2] = (1.0f - color[2]) * value + color[2] * inverted_value;
}
else {
copy_v3_v3(it.out, color);
}
if (this->m_alpha) {
it.out[3] = (1.0f - color[3]) * value + color[3] * inverted_value;
}
else {
it.out[3] = color[3];
}
}
}
} // namespace blender::compositor

View File

@ -18,11 +18,11 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
namespace blender::compositor {
class InvertOperation : public NodeOperation {
class InvertOperation : public MultiThreadedOperation {
private:
/**
* Cached reference to the inputProgram
@ -59,6 +59,10 @@ class InvertOperation : public NodeOperation {
{
this->m_alpha = alpha;
}
void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
} // namespace blender::compositor

View File

@ -17,6 +17,8 @@
*/
#include "COM_TonemapOperation.h"
#include "COM_ExecutionSystem.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
@ -153,4 +155,126 @@ void TonemapOperation::deinitializeTileData(rcti * /*rect*/, void * /*data*/)
/* pass */
}
void TonemapOperation::get_area_of_interest(const int input_idx,
const rcti &UNUSED(output_area),
rcti &r_input_area)
{
BLI_assert(input_idx == 0);
NodeOperation *operation = getInputOperation(input_idx);
r_input_area.xmin = 0;
r_input_area.ymin = 0;
r_input_area.xmax = operation->getWidth();
r_input_area.ymax = operation->getHeight();
}
struct Luminance {
float sum;
float color_sum[3];
float log_sum;
float min;
float max;
int num_pixels;
};
static Luminance calc_area_luminance(const MemoryBuffer *input, const rcti &area)
{
Luminance lum = {0};
for (const float *elem : input->get_buffer_area(area)) {
const float lu = IMB_colormanagement_get_luminance(elem);
lum.sum += lu;
add_v3_v3(lum.color_sum, elem);
lum.log_sum += logf(MAX2(lu, 0.0f) + 1e-5f);
lum.max = MAX2(lu, lum.max);
lum.min = MIN2(lu, lum.min);
lum.num_pixels++;
}
return lum;
}
void TonemapOperation::update_memory_buffer_started(MemoryBuffer *UNUSED(output),
const rcti &UNUSED(area),
Span<MemoryBuffer *> inputs)
{
if (this->m_cachedInstance == nullptr) {
Luminance lum = {0};
const MemoryBuffer *input = inputs[0];
exec_system_->execute_work<Luminance>(
input->get_rect(),
[=](const rcti &split) { return calc_area_luminance(input, split); },
lum,
[](Luminance &join, const Luminance &chunk) {
join.sum += chunk.sum;
add_v3_v3(join.color_sum, chunk.color_sum);
join.log_sum += chunk.log_sum;
join.max = MAX2(join.max, chunk.max);
join.min = MIN2(join.min, chunk.min);
join.num_pixels += chunk.num_pixels;
});
AvgLogLum *avg = new AvgLogLum();
avg->lav = lum.sum / lum.num_pixels;
mul_v3_v3fl(avg->cav, lum.color_sum, 1.0f / lum.num_pixels);
const float max_log = log((double)lum.max + 1e-5);
const float min_log = log((double)lum.min + 1e-5);
const float avg_log = lum.log_sum / lum.num_pixels;
avg->auto_key = (max_log > min_log) ? ((max_log - avg_log) / (max_log - min_log)) : 1.0f;
const float al = exp((double)avg_log);
avg->al = (al == 0.0f) ? 0.0f : (this->m_data->key / al);
avg->igm = (this->m_data->gamma == 0.0f) ? 1 : (1.0f / this->m_data->gamma);
this->m_cachedInstance = avg;
}
}
void TonemapOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
AvgLogLum *avg = m_cachedInstance;
const float igm = avg->igm;
const float offset = this->m_data->offset;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
copy_v4_v4(it.out, it.in(0));
mul_v3_fl(it.out, avg->al);
float dr = it.out[0] + offset;
float dg = it.out[1] + offset;
float db = it.out[2] + offset;
it.out[0] /= ((dr == 0.0f) ? 1.0f : dr);
it.out[1] /= ((dg == 0.0f) ? 1.0f : dg);
it.out[2] /= ((db == 0.0f) ? 1.0f : db);
if (igm != 0.0f) {
it.out[0] = powf(MAX2(it.out[0], 0.0f), igm);
it.out[1] = powf(MAX2(it.out[1], 0.0f), igm);
it.out[2] = powf(MAX2(it.out[2], 0.0f), igm);
}
}
}
void PhotoreceptorTonemapOperation::update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs)
{
AvgLogLum *avg = m_cachedInstance;
NodeTonemap *ntm = this->m_data;
const float f = expf(-this->m_data->f);
const float m = (ntm->m > 0.0f) ? ntm->m : (0.3f + 0.7f * powf(avg->auto_key, 1.4f));
const float ic = 1.0f - ntm->c;
const float ia = 1.0f - ntm->a;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
copy_v4_v4(it.out, it.in(0));
const float L = IMB_colormanagement_get_luminance(it.out);
float I_l = it.out[0] + ic * (L - it.out[0]);
float I_g = avg->cav[0] + ic * (avg->lav - avg->cav[0]);
float I_a = I_l + ia * (I_g - I_l);
it.out[0] /= (it.out[0] + powf(f * I_a, m));
I_l = it.out[1] + ic * (L - it.out[1]);
I_g = avg->cav[1] + ic * (avg->lav - avg->cav[1]);
I_a = I_l + ia * (I_g - I_l);
it.out[1] /= (it.out[1] + powf(f * I_a, m));
I_l = it.out[2] + ic * (L - it.out[2]);
I_g = avg->cav[2] + ic * (avg->lav - avg->cav[2]);
I_a = I_l + ia * (I_g - I_l);
it.out[2] /= (it.out[2] + powf(f * I_a, m));
}
}
} // namespace blender::compositor

View File

@ -18,7 +18,7 @@
#pragma once
#include "COM_NodeOperation.h"
#include "COM_MultiThreadedOperation.h"
#include "DNA_node_types.h"
namespace blender::compositor {
@ -39,7 +39,7 @@ typedef struct AvgLogLum {
* \brief base class of tonemap, implementing the simple tonemap
* \ingroup operation
*/
class TonemapOperation : public NodeOperation {
class TonemapOperation : public MultiThreadedOperation {
protected:
/**
* \brief Cached reference to the reader
@ -85,6 +85,14 @@ class TonemapOperation : 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_started(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
virtual void update_memory_buffer_partial(MemoryBuffer *output,
const rcti &area,
Span<MemoryBuffer *> inputs) override;
};
/**
@ -99,6 +107,10 @@ class PhotoreceptorTonemapOperation : public TonemapOperation {
* The inner loop of this operation.
*/
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;
};
} // namespace blender::compositor

View File

@ -33,6 +33,7 @@ ZCombineOperation::ZCombineOperation()
this->m_depth1Reader = nullptr;
this->m_image2Reader = nullptr;
this->m_depth2Reader = nullptr;
this->flags.can_be_constant = true;
}
void ZCombineOperation::initExecution()
@ -60,6 +61,19 @@ void ZCombineOperation::executePixelSampled(float output[4],
this->m_image2Reader->readSampled(output, x, y, sampler);
}
}
void ZCombineOperation::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 depth1 = *it.in(1);
const float depth2 = *it.in(3);
const float *color = (depth1 < depth2) ? it.in(0) : it.in(2);
copy_v4_v4(it.out, color);
}
}
void ZCombineAlphaOperation::executePixelSampled(float output[4],
float x,
float y,
@ -88,6 +102,32 @@ void ZCombineAlphaOperation::executePixelSampled(float output[4],
output[3] = MAX2(color1[3], color2[3]);
}
void ZCombineAlphaOperation::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 depth1 = *it.in(1);
const float depth2 = *it.in(3);
const float *color1;
const float *color2;
if (depth1 <= depth2) {
color1 = it.in(0);
color2 = it.in(2);
}
else {
color1 = it.in(2);
color2 = it.in(0);
}
const float fac = color1[3];
const float ifac = 1.0f - fac;
it.out[0] = fac * color1[0] + ifac * color2[0];
it.out[1] = fac * color1[1] + ifac * color2[1];
it.out[2] = fac * color1[2] + ifac * color2[2];
it.out[3] = MAX2(color1[3], color2[3]);
}
}
void ZCombineOperation::deinitExecution()
{
this->m_image1Reader = nullptr;
@ -132,6 +172,18 @@ void ZCombineMaskOperation::executePixelSampled(float output[4],
interp_v4_v4v4(output, color1, color2, 1.0f - mask[0]);
}
void ZCombineMaskOperation::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 mask = *it.in(0);
const float *color1 = it.in(1);
const float *color2 = it.in(2);
interp_v4_v4v4(it.out, color1, color2, 1.0f - mask);
}
}
void ZCombineMaskAlphaOperation::executePixelSampled(float output[4],
float x,
float y,
@ -154,6 +206,24 @@ void ZCombineMaskAlphaOperation::executePixelSampled(float output[4],
output[3] = MAX2(color1[3], color2[3]);
}
void ZCombineMaskAlphaOperation::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 mask = *it.in(0);
const float *color1 = it.in(1);
const float *color2 = it.in(2);
const float fac = (1.0f - mask) * (1.0f - color1[3]) + mask * color2[3];
const float mfac = 1.0f - fac;
it.out[0] = color1[0] * mfac + color2[0] * fac;
it.out[1] = color1[1] * mfac + color2[1] * fac;
it.out[2] = color1[2] * mfac + color2[2] * fac;
it.out[3] = MAX2(color1[3], color2[3]);
}
}
void ZCombineMaskOperation::deinitExecution()
{
this->m_image1Reader = nullptr;

View File

@ -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 ZCombineOperation : public NodeOperation {
class ZCombineOperation : public MultiThreadedOperation {
protected:
SocketReader *m_image1Reader;
SocketReader *m_depth1Reader;
@ -46,13 +46,21 @@ class ZCombineOperation : public NodeOperation {
* The inner loop of this operation.
*/
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 ZCombineAlphaOperation : public ZCombineOperation {
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 ZCombineMaskOperation : public NodeOperation {
class ZCombineMaskOperation : public MultiThreadedOperation {
protected:
SocketReader *m_maskReader;
SocketReader *m_image1Reader;
@ -64,9 +72,17 @@ class ZCombineMaskOperation : public NodeOperation {
void initExecution() override;
void deinitExecution() 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;
};
class ZCombineMaskAlphaOperation : public ZCombineMaskOperation {
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