355 lines
14 KiB
C++
355 lines
14 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright 2012, Blender Foundation.
|
|
*/
|
|
|
|
#include "COM_KeyingNode.h"
|
|
|
|
#include "COM_ExecutionSystem.h"
|
|
|
|
#include "COM_KeyingBlurOperation.h"
|
|
#include "COM_KeyingClipOperation.h"
|
|
#include "COM_KeyingDespillOperation.h"
|
|
#include "COM_KeyingOperation.h"
|
|
|
|
#include "COM_MathBaseOperation.h"
|
|
|
|
#include "COM_ConvertOperation.h"
|
|
#include "COM_SetValueOperation.h"
|
|
|
|
#include "COM_DilateErodeOperation.h"
|
|
|
|
#include "COM_SetAlphaMultiplyOperation.h"
|
|
|
|
#include "COM_GaussianAlphaXBlurOperation.h"
|
|
#include "COM_GaussianAlphaYBlurOperation.h"
|
|
|
|
namespace blender::compositor {
|
|
|
|
KeyingNode::KeyingNode(bNode *editorNode) : Node(editorNode)
|
|
{
|
|
/* pass */
|
|
}
|
|
|
|
NodeOperationOutput *KeyingNode::setupPreBlur(NodeConverter &converter,
|
|
NodeInput *inputImage,
|
|
int size) const
|
|
{
|
|
ConvertRGBToYCCOperation *convertRGBToYCCOperation = new ConvertRGBToYCCOperation();
|
|
convertRGBToYCCOperation->setMode(BLI_YCC_ITU_BT709);
|
|
converter.addOperation(convertRGBToYCCOperation);
|
|
|
|
converter.mapInputSocket(inputImage, convertRGBToYCCOperation->getInputSocket(0));
|
|
|
|
CombineChannelsOperation *combineOperation = new CombineChannelsOperation();
|
|
converter.addOperation(combineOperation);
|
|
|
|
for (int channel = 0; channel < 4; channel++) {
|
|
SeparateChannelOperation *separateOperation = new SeparateChannelOperation();
|
|
separateOperation->setChannel(channel);
|
|
converter.addOperation(separateOperation);
|
|
|
|
converter.addLink(convertRGBToYCCOperation->getOutputSocket(0),
|
|
separateOperation->getInputSocket(0));
|
|
|
|
if (ELEM(channel, 0, 3)) {
|
|
converter.addLink(separateOperation->getOutputSocket(0),
|
|
combineOperation->getInputSocket(channel));
|
|
}
|
|
else {
|
|
KeyingBlurOperation *blurXOperation = new KeyingBlurOperation();
|
|
blurXOperation->setSize(size);
|
|
blurXOperation->setAxis(KeyingBlurOperation::BLUR_AXIS_X);
|
|
converter.addOperation(blurXOperation);
|
|
|
|
KeyingBlurOperation *blurYOperation = new KeyingBlurOperation();
|
|
blurYOperation->setSize(size);
|
|
blurYOperation->setAxis(KeyingBlurOperation::BLUR_AXIS_Y);
|
|
converter.addOperation(blurYOperation);
|
|
|
|
converter.addLink(separateOperation->getOutputSocket(), blurXOperation->getInputSocket(0));
|
|
converter.addLink(blurXOperation->getOutputSocket(), blurYOperation->getInputSocket(0));
|
|
converter.addLink(blurYOperation->getOutputSocket(0),
|
|
combineOperation->getInputSocket(channel));
|
|
}
|
|
}
|
|
|
|
ConvertYCCToRGBOperation *convertYCCToRGBOperation = new ConvertYCCToRGBOperation();
|
|
convertYCCToRGBOperation->setMode(BLI_YCC_ITU_BT709);
|
|
converter.addOperation(convertYCCToRGBOperation);
|
|
|
|
converter.addLink(combineOperation->getOutputSocket(0),
|
|
convertYCCToRGBOperation->getInputSocket(0));
|
|
|
|
return convertYCCToRGBOperation->getOutputSocket(0);
|
|
}
|
|
|
|
NodeOperationOutput *KeyingNode::setupPostBlur(NodeConverter &converter,
|
|
NodeOperationOutput *postBlurInput,
|
|
int size) const
|
|
{
|
|
KeyingBlurOperation *blurXOperation = new KeyingBlurOperation();
|
|
blurXOperation->setSize(size);
|
|
blurXOperation->setAxis(KeyingBlurOperation::BLUR_AXIS_X);
|
|
converter.addOperation(blurXOperation);
|
|
|
|
KeyingBlurOperation *blurYOperation = new KeyingBlurOperation();
|
|
blurYOperation->setSize(size);
|
|
blurYOperation->setAxis(KeyingBlurOperation::BLUR_AXIS_Y);
|
|
converter.addOperation(blurYOperation);
|
|
|
|
converter.addLink(postBlurInput, blurXOperation->getInputSocket(0));
|
|
converter.addLink(blurXOperation->getOutputSocket(), blurYOperation->getInputSocket(0));
|
|
|
|
return blurYOperation->getOutputSocket();
|
|
}
|
|
|
|
NodeOperationOutput *KeyingNode::setupDilateErode(NodeConverter &converter,
|
|
NodeOperationOutput *dilateErodeInput,
|
|
int distance) const
|
|
{
|
|
DilateDistanceOperation *dilateErodeOperation;
|
|
if (distance > 0) {
|
|
dilateErodeOperation = new DilateDistanceOperation();
|
|
dilateErodeOperation->setDistance(distance);
|
|
}
|
|
else {
|
|
dilateErodeOperation = new ErodeDistanceOperation();
|
|
dilateErodeOperation->setDistance(-distance);
|
|
}
|
|
converter.addOperation(dilateErodeOperation);
|
|
|
|
converter.addLink(dilateErodeInput, dilateErodeOperation->getInputSocket(0));
|
|
|
|
return dilateErodeOperation->getOutputSocket(0);
|
|
}
|
|
|
|
NodeOperationOutput *KeyingNode::setupFeather(NodeConverter &converter,
|
|
const CompositorContext &context,
|
|
NodeOperationOutput *featherInput,
|
|
int falloff,
|
|
int distance) const
|
|
{
|
|
/* this uses a modified gaussian blur function otherwise its far too slow */
|
|
eCompositorQuality quality = context.getQuality();
|
|
|
|
/* initialize node data */
|
|
NodeBlurData data;
|
|
memset(&data, 0, sizeof(NodeBlurData));
|
|
data.filtertype = R_FILTER_GAUSS;
|
|
if (distance > 0) {
|
|
data.sizex = data.sizey = distance;
|
|
}
|
|
else {
|
|
data.sizex = data.sizey = -distance;
|
|
}
|
|
|
|
GaussianAlphaXBlurOperation *operationx = new GaussianAlphaXBlurOperation();
|
|
operationx->setData(&data);
|
|
operationx->setQuality(quality);
|
|
operationx->setSize(1.0f);
|
|
operationx->setSubtract(distance < 0);
|
|
operationx->setFalloff(falloff);
|
|
converter.addOperation(operationx);
|
|
|
|
GaussianAlphaYBlurOperation *operationy = new GaussianAlphaYBlurOperation();
|
|
operationy->setData(&data);
|
|
operationy->setQuality(quality);
|
|
operationy->setSize(1.0f);
|
|
operationy->setSubtract(distance < 0);
|
|
operationy->setFalloff(falloff);
|
|
converter.addOperation(operationy);
|
|
|
|
converter.addLink(featherInput, operationx->getInputSocket(0));
|
|
converter.addLink(operationx->getOutputSocket(), operationy->getInputSocket(0));
|
|
|
|
return operationy->getOutputSocket();
|
|
}
|
|
|
|
NodeOperationOutput *KeyingNode::setupDespill(NodeConverter &converter,
|
|
NodeOperationOutput *despillInput,
|
|
NodeInput *inputScreen,
|
|
float factor,
|
|
float colorBalance) const
|
|
{
|
|
KeyingDespillOperation *despillOperation = new KeyingDespillOperation();
|
|
despillOperation->setDespillFactor(factor);
|
|
despillOperation->setColorBalance(colorBalance);
|
|
converter.addOperation(despillOperation);
|
|
|
|
converter.addLink(despillInput, despillOperation->getInputSocket(0));
|
|
converter.mapInputSocket(inputScreen, despillOperation->getInputSocket(1));
|
|
|
|
return despillOperation->getOutputSocket(0);
|
|
}
|
|
|
|
NodeOperationOutput *KeyingNode::setupClip(NodeConverter &converter,
|
|
NodeOperationOutput *clipInput,
|
|
int kernelRadius,
|
|
float kernelTolerance,
|
|
float clipBlack,
|
|
float clipWhite,
|
|
bool edgeMatte) const
|
|
{
|
|
KeyingClipOperation *clipOperation = new KeyingClipOperation();
|
|
clipOperation->setKernelRadius(kernelRadius);
|
|
clipOperation->setKernelTolerance(kernelTolerance);
|
|
clipOperation->setClipBlack(clipBlack);
|
|
clipOperation->setClipWhite(clipWhite);
|
|
clipOperation->setIsEdgeMatte(edgeMatte);
|
|
converter.addOperation(clipOperation);
|
|
|
|
converter.addLink(clipInput, clipOperation->getInputSocket(0));
|
|
|
|
return clipOperation->getOutputSocket(0);
|
|
}
|
|
|
|
void KeyingNode::convertToOperations(NodeConverter &converter,
|
|
const CompositorContext &context) const
|
|
{
|
|
bNode *editorNode = this->getbNode();
|
|
NodeKeyingData *keying_data = (NodeKeyingData *)editorNode->storage;
|
|
|
|
NodeInput *inputImage = this->getInputSocket(0);
|
|
NodeInput *inputScreen = this->getInputSocket(1);
|
|
NodeInput *inputGarbageMatte = this->getInputSocket(2);
|
|
NodeInput *inputCoreMatte = this->getInputSocket(3);
|
|
NodeOutput *outputImage = this->getOutputSocket(0);
|
|
NodeOutput *outputMatte = this->getOutputSocket(1);
|
|
NodeOutput *outputEdges = this->getOutputSocket(2);
|
|
NodeOperationOutput *postprocessedMatte = nullptr, *postprocessedImage = nullptr,
|
|
*edgesMatte = nullptr;
|
|
|
|
/* keying operation */
|
|
KeyingOperation *keyingOperation = new KeyingOperation();
|
|
keyingOperation->setScreenBalance(keying_data->screen_balance);
|
|
converter.addOperation(keyingOperation);
|
|
|
|
converter.mapInputSocket(inputScreen, keyingOperation->getInputSocket(1));
|
|
|
|
if (keying_data->blur_pre) {
|
|
/* Chroma pre-blur operation for input of keying operation. */
|
|
NodeOperationOutput *preBlurredImage = setupPreBlur(
|
|
converter, inputImage, keying_data->blur_pre);
|
|
converter.addLink(preBlurredImage, keyingOperation->getInputSocket(0));
|
|
}
|
|
else {
|
|
converter.mapInputSocket(inputImage, keyingOperation->getInputSocket(0));
|
|
}
|
|
|
|
postprocessedMatte = keyingOperation->getOutputSocket();
|
|
|
|
/* black / white clipping */
|
|
if (keying_data->clip_black > 0.0f || keying_data->clip_white < 1.0f) {
|
|
postprocessedMatte = setupClip(converter,
|
|
postprocessedMatte,
|
|
keying_data->edge_kernel_radius,
|
|
keying_data->edge_kernel_tolerance,
|
|
keying_data->clip_black,
|
|
keying_data->clip_white,
|
|
false);
|
|
}
|
|
|
|
/* output edge matte */
|
|
edgesMatte = setupClip(converter,
|
|
postprocessedMatte,
|
|
keying_data->edge_kernel_radius,
|
|
keying_data->edge_kernel_tolerance,
|
|
keying_data->clip_black,
|
|
keying_data->clip_white,
|
|
true);
|
|
|
|
/* apply garbage matte */
|
|
if (inputGarbageMatte->isLinked()) {
|
|
SetValueOperation *valueOperation = new SetValueOperation();
|
|
valueOperation->setValue(1.0f);
|
|
converter.addOperation(valueOperation);
|
|
|
|
MathSubtractOperation *subtractOperation = new MathSubtractOperation();
|
|
converter.addOperation(subtractOperation);
|
|
|
|
MathMinimumOperation *minOperation = new MathMinimumOperation();
|
|
converter.addOperation(minOperation);
|
|
|
|
converter.addLink(valueOperation->getOutputSocket(), subtractOperation->getInputSocket(0));
|
|
converter.mapInputSocket(inputGarbageMatte, subtractOperation->getInputSocket(1));
|
|
|
|
converter.addLink(subtractOperation->getOutputSocket(), minOperation->getInputSocket(0));
|
|
converter.addLink(postprocessedMatte, minOperation->getInputSocket(1));
|
|
|
|
postprocessedMatte = minOperation->getOutputSocket();
|
|
}
|
|
|
|
/* apply core matte */
|
|
if (inputCoreMatte->isLinked()) {
|
|
MathMaximumOperation *maxOperation = new MathMaximumOperation();
|
|
converter.addOperation(maxOperation);
|
|
|
|
converter.mapInputSocket(inputCoreMatte, maxOperation->getInputSocket(0));
|
|
converter.addLink(postprocessedMatte, maxOperation->getInputSocket(1));
|
|
|
|
postprocessedMatte = maxOperation->getOutputSocket();
|
|
}
|
|
|
|
/* apply blur on matte if needed */
|
|
if (keying_data->blur_post) {
|
|
postprocessedMatte = setupPostBlur(converter, postprocessedMatte, keying_data->blur_post);
|
|
}
|
|
|
|
/* matte dilate/erode */
|
|
if (keying_data->dilate_distance != 0) {
|
|
postprocessedMatte = setupDilateErode(
|
|
converter, postprocessedMatte, keying_data->dilate_distance);
|
|
}
|
|
|
|
/* matte feather */
|
|
if (keying_data->feather_distance != 0) {
|
|
postprocessedMatte = setupFeather(converter,
|
|
context,
|
|
postprocessedMatte,
|
|
keying_data->feather_falloff,
|
|
keying_data->feather_distance);
|
|
}
|
|
|
|
/* set alpha channel to output image */
|
|
SetAlphaMultiplyOperation *alphaOperation = new SetAlphaMultiplyOperation();
|
|
converter.addOperation(alphaOperation);
|
|
|
|
converter.mapInputSocket(inputImage, alphaOperation->getInputSocket(0));
|
|
converter.addLink(postprocessedMatte, alphaOperation->getInputSocket(1));
|
|
|
|
postprocessedImage = alphaOperation->getOutputSocket();
|
|
|
|
/* despill output image */
|
|
if (keying_data->despill_factor > 0.0f) {
|
|
postprocessedImage = setupDespill(converter,
|
|
postprocessedImage,
|
|
inputScreen,
|
|
keying_data->despill_factor,
|
|
keying_data->despill_balance);
|
|
}
|
|
|
|
/* connect result to output sockets */
|
|
converter.mapOutputSocket(outputImage, postprocessedImage);
|
|
converter.mapOutputSocket(outputMatte, postprocessedMatte);
|
|
|
|
if (edgesMatte) {
|
|
converter.mapOutputSocket(outputEdges, edgesMatte);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::compositor
|