diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index a70d78c..51036117 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -345,6 +345,7 @@ compositor_node_categories = [ NodeItem("CompositorNodeDBlur"), NodeItem("CompositorNodePixelate"), NodeItem("CompositorNodeSunBeams"), + NodeItem("CompositorNodeCanny"), ]), CompositorNodeCategory("CMP_OP_VECTOR", "Vector", items=[ NodeItem("CompositorNodeNormal"), diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 9b8febc..ae11341 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -932,6 +932,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree, struct GPUMat #define CMP_NODE_TRACKPOS 271 #define CMP_NODE_INPAINT 272 #define CMP_NODE_DESPECKLE 273 +#define CMP_NODE_CANNY 274 #define CMP_NODE_GLARE 301 #define CMP_NODE_TONEMAP 302 diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c index 869e284..70ee6e0 100644 --- a/source/blender/blenkernel/intern/node.c +++ b/source/blender/blenkernel/intern/node.c @@ -3408,6 +3408,7 @@ static void registerCompositNodes(void) register_node_type_cmp_despeckle(); register_node_type_cmp_defocus(); register_node_type_cmp_sunbeams(); + register_node_type_cmp_canny(); register_node_type_cmp_valtorgb(); register_node_type_cmp_rgbtobw(); diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 261a121..bd5d189 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -183,6 +183,11 @@ set(SRC operations/COM_SunBeamsOperation.cpp operations/COM_SunBeamsOperation.h + nodes/COM_CannyNode.cpp + nodes/COM_CannyNode.h + operations/COM_CannyOperation.cpp + operations/COM_CannyOperation.h + nodes/COM_CryptomatteNode.cpp nodes/COM_CryptomatteNode.h operations/COM_CryptomatteOperation.cpp diff --git a/source/blender/compositor/intern/COM_Converter.cpp b/source/blender/compositor/intern/COM_Converter.cpp index bc41be2..a149955 100644 --- a/source/blender/compositor/intern/COM_Converter.cpp +++ b/source/blender/compositor/intern/COM_Converter.cpp @@ -101,6 +101,7 @@ extern "C" { #include "COM_SplitViewerNode.h" #include "COM_Stabilize2dNode.h" #include "COM_SunBeamsNode.h" +#include "COM_CannyNode.h" #include "COM_SwitchNode.h" #include "COM_SwitchViewNode.h" #include "COM_TextureNode.h" @@ -410,6 +411,9 @@ Node *Converter::convert(bNode *b_node) case CMP_NODE_CRYPTOMATTE: node = new CryptomatteNode(b_node); break; + case CMP_NODE_CANNY: + node = new CannyNode(b_node); + break; } return node; } diff --git a/source/blender/compositor/nodes/COM_CannyNode.cpp b/source/blender/compositor/nodes/COM_CannyNode.cpp new file mode 100644 index 0000000..dbc1700 --- /dev/null +++ b/source/blender/compositor/nodes/COM_CannyNode.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2014, Blender Foundation. + * + * 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. + * + * Contributor: + * Allosteric + */ + +#include "COM_CannyNode.h" +#include "COM_CannyOperation.h" +#include "COM_CombineColorNode.h" +#include "COM_MathBaseOperation.h" + +CannyNode::CannyNode(bNode *editorNode) : Node(editorNode) +{ + /* pass */ +} + + +// +-----+ image +-------+ +-----------+ R +-------------+ +-------+ +------+ +// |input| ---+----> |sobel x| --+--> |non maximum| --+---> |blob analysis| --+--> |combine| --+--> |output| +// +--+--+ | +-------+ | +-----+-----+ | +-------------+ | +--m----+ | +------+ +// | | | ^ | | | +// | | +-------+ | | | G +-------------+ | | +// | +----> |sobel y| --+ | +---> |blob analysis| --+ | As alpha +// | +-------+ | | +-------------+ | | +// | | | | | +// | high/low threshold | | B +-------------+ | +-------+ | +// +-------------------------------------+ +---> |blob analysis| --+--> |maximum| +-+- +// +-------------+ +--m----+ + + +void CannyNode::convertToOperations(NodeConverter &converter, const CompositorContext &/*context*/) const +{ + NodeInput *inputSocketLow = this->getInputSocket(0); + NodeInput *inputSocketHigh = this->getInputSocket(1); + NodeInput *inputSocketImage = this->getInputSocket(2); + NodeOutput *outputSocketImage = this->getOutputSocket(0); + + + // NodeInput *inputSocketImage = this->getInputSocket(1); // for debug + // NodeInput *inputSocketLow = this->getInputSocket(0); // for debug + + // operations to calculate the sobel values + SobelFilterOperation *sobelx = new SobelFilterOperation(); + sobelx->set3x3Filter(1, 0, -1, 2, 0, -2, 1, 0, -1); + converter.addOperation(sobelx); + + SobelFilterOperation *sobely = new SobelFilterOperation(); + sobely->set3x3Filter(1, 2, 1, 0, 0, 0, -1, -2, -1); + converter.addOperation(sobely); + + // operation to omit the non maxima and assign one of the BELOW_LOW_THRESHOLD, ABOVE_HIGH_THRESHOLD, BETWEEN_THRESHOLD, NON_MAX_PIXEL + NonMaximaOperation *nonmax = new NonMaximaOperation(); + nonmax->setData((NodeCanny *)this->getbNode()->storage); + converter.addOperation(nonmax); + + // blob analysis + // determine if BETWEEN_THRESHOLD can be a true edge this takes time + BlobAnalysisOperation *blobR = new BlobAnalysisOperation(); + blobR->setChannel(0); + BlobAnalysisOperation *blobG = new BlobAnalysisOperation(); + blobG->setChannel(1); + BlobAnalysisOperation *blobB = new BlobAnalysisOperation(); + blobB->setChannel(2); + converter.addOperation(blobR); + converter.addOperation(blobG); + converter.addOperation(blobB); + + // combine blobs + CombineChannelsOperation *combine = new CombineChannelsOperation(); + converter.addOperation(combine); + + // calc alpha + MathBaseOperation *max_R_G = new MathMaximumOperation(); + MathBaseOperation *max_RG_B = new MathMaximumOperation(); + converter.addOperation(max_R_G); + converter.addOperation(max_RG_B); + + // connect operations + converter.mapInputSocket(inputSocketImage, sobelx->getInputSocket(0)); + converter.mapInputSocket(inputSocketImage, sobely->getInputSocket(0)); + + converter.mapInputSocket(inputSocketLow, nonmax->getInputSocket(0)); + converter.mapInputSocket(inputSocketHigh, nonmax->getInputSocket(1)); + converter.addLink(sobelx->getOutputSocket(0), nonmax->getInputSocket(2)); + converter.addLink(sobely->getOutputSocket(0), nonmax->getInputSocket(3)); + + converter.addLink(nonmax->getOutputSocket(0), blobR->getInputSocket(0)); + converter.addLink(nonmax->getOutputSocket(0), blobG->getInputSocket(0)); + converter.addLink(nonmax->getOutputSocket(0), blobB->getInputSocket(0)); + + converter.addLink(blobR->getOutputSocket(0), max_R_G->getInputSocket(0)); + converter.addLink(blobG->getOutputSocket(0), max_R_G->getInputSocket(1)); + converter.addLink(blobB->getOutputSocket(0), max_RG_B->getInputSocket(0)); + converter.addLink(max_R_G->getOutputSocket(0), max_RG_B->getInputSocket(1)); + + converter.addLink(blobR->getOutputSocket(0), combine->getInputSocket(0)); + converter.addLink(blobG->getOutputSocket(0), combine->getInputSocket(1)); + converter.addLink(blobB->getOutputSocket(0), combine->getInputSocket(2)); + converter.addLink(max_RG_B->getOutputSocket(0), combine->getInputSocket(3)); + + converter.mapOutputSocket(outputSocketImage, combine->getOutputSocket(0)); +} diff --git a/source/blender/compositor/nodes/COM_CannyNode.h b/source/blender/compositor/nodes/COM_CannyNode.h new file mode 100644 index 0000000..a2d15c4 --- /dev/null +++ b/source/blender/compositor/nodes/COM_CannyNode.h @@ -0,0 +1,56 @@ +/* + * Copyright 2014, Blender Foundation. + * + * 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. + * + * Contributor: + * Lukas Toenne + */ + +#ifndef _COM_CannyNode_h_ +#define _COM_CannyNode_h_ + +#include "COM_Node.h" + +/** + * @brief CannyNode + * @ingroup Node + */ +class CannyNode : public Node { +public: + CannyNode(bNode *editorNode); + void convertToOperations(NodeConverter &converter, const CompositorContext &context) const; +}; + +class SobelFilterNode : public Node { +public: + SobelFilterNode(bNode *editorNode); + void convertToOperations(NodeConverter &converter, const CompositorContext &context) const; +}; + + +class NonMaximaNode : public Node { +public: + NonMaximaNode(bNode *editorNode); + void convertToOperations(NodeConverter &converter, const CompositorContext &context) const; +}; + +class BlobAnalysisNode : public Node { +public: + BlobAnalysisNode(bNode *editorNode); + void convertToOperations(NodeConverter &converter, const CompositorContext &context) const; +}; + +#endif \ No newline at end of file diff --git a/source/blender/compositor/operations/COM_CannyOperation.cpp b/source/blender/compositor/operations/COM_CannyOperation.cpp new file mode 100644 index 0000000..b73567b --- /dev/null +++ b/source/blender/compositor/operations/COM_CannyOperation.cpp @@ -0,0 +1,378 @@ +/* + * Copyright 2014, Blender Foundation. + * + * 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. + * + * Contributor: + * Allosteric + */ + +#include "MEM_guardedalloc.h" + +#include "COM_CannyOperation.h" + +#include "BLI_utildefines.h" + +#include +#include + +#define TAN_225 0.41421356237309503 // tan(22.5) = tan(pi/8) +#define TAN_675 2.414213562373095 // tan(67.5) = tan(pi/2 - pi/8) +#define TAN_1125 -2.414213562373095 // tan(112.5) = tan(pi/2 + pi/8) +#define TAN_1575 -0.41421356237309503 // tan(157.5) = tan(pi - pi/8) + + +// SobelFilterOperation +// almost the same as FilterConvolutionOperation except that filter size is 3 and is not clamped to <=0 +SobelFilterOperation::SobelFilterOperation() : NodeOperation() +{ + this->addInputSocket(COM_DT_COLOR); + this->addOutputSocket(COM_DT_COLOR); + this->setResolutionInputSocketIndex(0); + this->m_inputOperation = NULL; + this->setComplex(true); +} +void SobelFilterOperation::initExecution() +{ + this->m_inputOperation = this->getInputSocketReader(0); +} + +void SobelFilterOperation::set3x3Filter(float f1, float f2, float f3, float f4, float f5, float f6, float f7, float f8, float f9) +{ + this->m_filter[0] = f1; + this->m_filter[1] = f2; + this->m_filter[2] = f3; + this->m_filter[3] = f4; + this->m_filter[4] = f5; + this->m_filter[5] = f6; + this->m_filter[6] = f7; + this->m_filter[7] = f8; + this->m_filter[8] = f9; +} + +void SobelFilterOperation::deinitExecution() +{ + this->m_inputOperation = NULL; +} + + +void SobelFilterOperation::executePixel(float output[4], int x, int y, void * /*data*/) +{ + float in1[4]; + float in2[4]; + int x1 = x - 1; + int x2 = x; + int x3 = x + 1; + int y1 = y - 1; + int y2 = y; + int y3 = y + 1; + CLAMP(x1, 0, getWidth() - 1); + CLAMP(x2, 0, getWidth() - 1); + CLAMP(x3, 0, getWidth() - 1); + CLAMP(y1, 0, getHeight() - 1); + CLAMP(y2, 0, getHeight() - 1); + CLAMP(y3, 0, getHeight() - 1); + + zero_v4(output); + this->m_inputOperation->read(in1, x1, y1, NULL); + madd_v4_v4fl(output, in1, this->m_filter[0]); + this->m_inputOperation->read(in1, x2, y1, NULL); + madd_v4_v4fl(output, in1, this->m_filter[1]); + this->m_inputOperation->read(in1, x3, y1, NULL); + madd_v4_v4fl(output, in1, this->m_filter[2]); + this->m_inputOperation->read(in1, x1, y2, NULL); + madd_v4_v4fl(output, in1, this->m_filter[3]); + this->m_inputOperation->read(in2, x2, y2, NULL); + madd_v4_v4fl(output, in2, this->m_filter[4]); + this->m_inputOperation->read(in1, x3, y2, NULL); + madd_v4_v4fl(output, in1, this->m_filter[5]); + this->m_inputOperation->read(in1, x1, y3, NULL); + madd_v4_v4fl(output, in1, this->m_filter[6]); + this->m_inputOperation->read(in1, x2, y3, NULL); + madd_v4_v4fl(output, in1, this->m_filter[7]); + this->m_inputOperation->read(in1, x3, y3, NULL); + madd_v4_v4fl(output, in1, this->m_filter[8]); + +} + +bool SobelFilterOperation::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output) +{ + rcti newInput; + newInput.xmax = input->xmax + 2; + newInput.xmin = input->xmin - 2; + newInput.ymax = input->ymax + 2; + newInput.ymin = input->ymin - 2; + + return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); +} + + +// NonMaximaOperation +NonMaximaOperation::NonMaximaOperation() : NodeOperation() +{ + this->addInputSocket(COM_DT_VALUE); + this->addInputSocket(COM_DT_VALUE); + this->addInputSocket(COM_DT_COLOR); + this->addInputSocket(COM_DT_COLOR); + this->addOutputSocket(COM_DT_COLOR); + this->m_lowThreshold = NULL; + this->m_highThreshold = NULL; + this->m_sovelx = NULL; + this->m_sovely = NULL; + this->setResolutionInputSocketIndex(2); + this->setComplex(true); +} + +void NonMaximaOperation::initExecution() +{ + this->m_lowThreshold = this->getInputSocketReader(0); + this->m_highThreshold = this->getInputSocketReader(1); + this->m_sovelx = this->getInputSocketReader(2); + this->m_sovely = this->getInputSocketReader(3); +} + +void NonMaximaOperation::deinitExecution() +{ + this->m_lowThreshold = NULL; + this->m_highThreshold = NULL; + this->m_sovelx = NULL; + this->m_sovely = NULL; + +} + + +void NonMaximaOperation::executePixelSampled(float output[4], float x, float y, PixelSampler sampler) +{ + float low_threshold[4]; + float high_threshold[4]; + float sovelx[4]; + float sovely[4]; + float edge_gradient; // originally it is sqrt(x^2+y^2) but sqrt is not required for the algorithm + int direction; + int x0, x1, y0, y1; // pixel in the normal direction of the edge curve + int x2, y2, x3, y3; // experimental + float nei_x[4], nei_y[4]; // pixel value in the normal direction + float nei_eg; // edge gradient of above + // int constant_width = 0; // not used for now + + this->m_sovelx->readSampled(sovelx, x, y, sampler); + this->m_sovely->readSampled(sovely, x, y, sampler); + this->m_lowThreshold->readSampled(low_threshold, x, y, sampler); + this->m_highThreshold->readSampled(high_threshold, x, y, sampler); + + for (int i = 0; i < 3; i++){ + + edge_gradient = SQUARE(sovelx[i]) + SQUARE(sovely[i]); + + if (edge_gradient <= SQUARE(low_threshold[0])){ + output[i] = BELOW_LOW_THRESHOLD; + continue; + } + + // calculation of direction (calculating tan(x/y) is not required) + if (!(this->m_data->use_constant_width)){ + // original algorithm + if (sovelx[i] == 0){ + direction = 90; + } else if (sovelx[i] > 0) { + if (sovely[i] >= TAN_675*sovelx[i]) direction = 90; + else if (sovely[i] >= TAN_225*sovelx[i]) direction = 45; + else if (sovely[i] <= TAN_1125*sovelx[i]) direction = 90; + else if (sovely[i] <= TAN_1575*sovelx[i]) direction = 135; + else direction = 0; + } else { + if (sovely[i] >= TAN_1125*sovelx[i]) direction = 90; + else if (sovely[i] >= TAN_1575*sovelx[i]) direction = 135; + else if (sovely[i] <= TAN_675*sovelx[i]) direction = 90; + else if (sovely[i] <= TAN_225*sovelx[i]) direction = 45; + else direction = 0; + } + } else { + // the line length seems to be more constant this way + if (sovelx[i] == 0){ + direction = 90; + } else if (sovelx[i] > 0) { + if (sovely[i] >= sovelx[i]) direction = 90; + else if (sovely[i] >= -sovelx[i]) direction = 0; + else direction = 90; + } else { + if (sovely[i] >= -sovelx[i]) direction = 90; + else if (sovely[i] >= sovelx[i]) direction = 0; + else direction = 90; + } + } + + // calculate neighbor pixel + switch (direction) { + case 0: + x0 = x - 1; + x1 = x + 1; + y0 = y; + y1 = y; + break; + case 90: + x0 = x; + x1 = x; + y0 = y - 1; + y1 = y + 1; + break; + case 45: + x0 = x - 1; + x1 = x + 1; + y0 = y - 1; + y1 = y + 1; + break; + case 135: + x0 = x - 1; + x1 = x + 1; + y0 = y + 1; + y1 = y - 1; + break; + default: + x0 = x; + x1 = x; + y0 = y; + y1 = y; + break; + } + + if (x0 >= 0 && y0 >= 0 && y0 <= getHeight()){ + this->m_sovelx->read(nei_x, x0, y0, NULL); + this->m_sovely->read(nei_y, x0, y0, NULL); + nei_eg = SQUARE(nei_x[i]) + SQUARE(nei_y[i]); + if (nei_eg > edge_gradient) { + output[i] = NON_MAX_PIXEL; + continue; + } + } + + if (x1 <= getWidth() && y1 >= 0 && y1 <= getHeight()){ + this->m_sovelx->read(nei_x, x1, y1, NULL); + this->m_sovely->read(nei_y, x1, y1, NULL); + nei_eg = SQUARE(nei_x[i]) + SQUARE(nei_y[i]); + if (nei_eg >= edge_gradient) { + output[i] = NON_MAX_PIXEL; + continue; + } + } + + if (edge_gradient > SQUARE(high_threshold[0])){ + output[i] = ABOVE_HIGH_THRESHOLD; + continue; + } + + output[i] = BETWEEN_THRESHOLD; + } + +} + +template std::set Union(std::set& setL, std::set& setR) { + std::set result(setL); + for (auto itr = setR.begin(); itr != setR.end(); itr++) { + result.insert(*itr); + } + return result; +} + +template std::set Product(std::set& setL, std::set& setR) { + std::set result; + for (auto itr = setR.begin(); itr != setR.end(); itr++) { + if (setL.find(*itr) != setL.end()) { + result.insert(*itr); + } + } + return result; +} + +template std::set Difference(std::set& setL, std::set& setR) { + std::set result(setL); + for (auto itr = setR.begin(); itr != setR.end(); itr++) { + result.erase(*itr); + } + return result; +} + +// BlobAnalysisOperation +void BlobAnalysisOperation::generateGlare(float *data, MemoryBuffer *inputTile, NodeGlare *settings) +{ + int i, x, y, xm, xp, ym, yp; + float pixel_value[4]; + std::tuple coord; + std::set> edge_candidate; // coords of pixels that might become a true edge + std::set> te_to_calc; // coords of pixels that is a true edge and might have neighbor pixel + std::set> te_calced; // coords of pixels that is a true edge and the existance of neighboring pixels + std::set> neighbor_edge; // coords of pixels that is neighbor of te_to_calc + // MemoryBuffer *tbuf = inputTile->duplicate(); + bool breaked = false; + + // if pixel value == ABOVE_HIGH_THRESHOLD, the coord is added to te_to_calc + // if pixel value == BETWEEN_THRESHOLD, the coord is added to edge_candidate + // else the coord is ignored + for (y = 0; y < this->getHeight() && (!breaked); y++) { + for (x = 0; x < this->getWidth(); x++) { + inputTile->read(pixel_value, x, y); + coord = std::make_tuple(x, y); + if (pixel_value[this->m_channel] == ABOVE_HIGH_THRESHOLD) te_to_calc.insert(coord); + else if (pixel_value[this->m_channel] == BETWEEN_THRESHOLD) edge_candidate.insert(coord); + } + if (isBreaked()) { + breaked = true; + } + } + + // 1. coords in te_to_calc is eliminated from edge_candidate + // 2. if pixel is a neighbor of pixel in te_to_calc, the coord is added to te_to_calc. coords that was originally in te_to_calc is added to te_calced and removed from te_to_calc + // 3. coords not in edge_candidate is eliminated from te_to_calc + // back to 1 until te_to_calc is empty + + while (!te_to_calc.empty() && (!breaked)){ + edge_candidate = Difference(edge_candidate, te_to_calc); + te_calced = Union(te_calced, te_to_calc); + + // add neighbors + for (auto coord_i = te_to_calc.begin(); coord_i != te_to_calc.end(); coord_i++){ + coord = *coord_i; + x = std::get<0>(coord); + y = std::get<1>(coord); + + // omitting and clamping will make the algorithm too complex + xm = x > 0 ? x - 1: x + 1; + xp = x < getWidth() - 1 ? x + 1: x - 1; + ym = y > 0 ? y - 1: y + 1; + yp = y < getHeight() - 1 ? y + 1: y - 1; + + neighbor_edge.insert(std::make_tuple(xm, ym)); + neighbor_edge.insert(std::make_tuple(x, ym)); + neighbor_edge.insert(std::make_tuple(xp, ym)); + neighbor_edge.insert(std::make_tuple(xm, y )); + neighbor_edge.insert(std::make_tuple(xp, y )); + neighbor_edge.insert(std::make_tuple(xm, yp)); + neighbor_edge.insert(std::make_tuple(x , yp)); + neighbor_edge.insert(std::make_tuple(xp, yp)); + } + te_to_calc = Product(neighbor_edge, edge_candidate); + neighbor_edge.clear(); + if (isBreaked()) { + breaked = true; + } + } + + for (auto coord_i = te_calced.begin(); coord_i != te_calced.end(); coord_i++){ + coord = *coord_i; + x = std::get<0>(coord); + y = std::get<1>(coord); + data[(x + y * getWidth())*4] = 3; // must be 3 instead of 1 for some reason + } +} \ No newline at end of file diff --git a/source/blender/compositor/operations/COM_CannyOperation.h b/source/blender/compositor/operations/COM_CannyOperation.h new file mode 100644 index 0000000..fb7f660 --- /dev/null +++ b/source/blender/compositor/operations/COM_CannyOperation.h @@ -0,0 +1,91 @@ +/* + * Copyright 2014, Blender Foundation. + * + * 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. + * + * Contributor: + * Allosteric + */ + +#ifndef _COM_CannyOperation_h +#define _COM_CannyOperation_h + +#include "COM_NodeOperation.h" +#include "COM_GlareBaseOperation.h" +#include "COM_ConvertOperation.h" + +typedef struct NodeNonMaxima { + float low_threshold; + float high_threshold; +} NodeNonMaxima; + +typedef struct NodeBlobAnalysis { +} NodeBlobAnalysis; + +enum { + BELOW_LOW_THRESHOLD = 0, // x^2+y^2 <= low^2 + ABOVE_HIGH_THRESHOLD = 1, // x^x+y^2 > high^2 + BETWEEN_THRESHOLD = 2, // low^2 < x^2+y^2 <= high^2 + NON_MAX_PIXEL = 3, // x^2+y^2 > low^2 but deleted in non max operation +}; + +class SobelFilterOperation : public NodeOperation { +protected: + SocketReader *m_inputOperation; + float m_filter[9]; + +public: + SobelFilterOperation(); + void set3x3Filter(float f1, float f2, float f3, float f4, float f5, float f6, float f7, float f8, float f9); + bool determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output); + void executePixel(float output[4], int x, int y, void *data); + + void initExecution(); + void deinitExecution(); +}; + +class NonMaximaOperation : public NodeOperation { +public: + NonMaximaOperation(); + + void executePixelSampled(float output[4], float x, float y, PixelSampler sampler); + void setData(NodeCanny *data) { this->m_data = data; } + + void initExecution(); + void deinitExecution(); + +private: + // NodeNonMaxima m_data; + + SocketReader *m_sovelx; + SocketReader *m_sovely; + SocketReader *m_lowThreshold; + SocketReader *m_highThreshold; + + NodeCanny *m_data; +}; + + +class BlobAnalysisOperation : public GlareBaseOperation { +public: + BlobAnalysisOperation() : GlareBaseOperation() {} + void setChannel(int channel) { this->m_channel = channel; } +protected: + void generateGlare(float *data, MemoryBuffer *inputTile, NodeGlare *settings); +private: + int m_channel; +}; + +#endif \ No newline at end of file diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c index 65170c7..695de6d 100644 --- a/source/blender/editors/space_node/drawnode.c +++ b/source/blender/editors/space_node/drawnode.c @@ -2480,6 +2480,11 @@ static void node_composit_buts_sunbeams(uiLayout *layout, bContext *UNUSED(C), P uiItemR(layout, ptr, "ray_length", UI_ITEM_R_SLIDER, NULL, ICON_NONE); } +static void node_composit_buts_canny(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) +{ + uiItemR(layout, ptr, "use_constant_width", 0, NULL, ICON_NONE); +} + static void node_composit_buts_cryptomatte(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) { uiLayout *col = uiLayoutColumn(layout, true); @@ -2731,6 +2736,9 @@ static void node_composit_set_butfunc(bNodeType *ntype) case CMP_NODE_SUNBEAMS: ntype->draw_buttons = node_composit_buts_sunbeams; break; + case CMP_NODE_CANNY: + ntype->draw_buttons = node_composit_buts_canny; + break; case CMP_NODE_CRYPTOMATTE: ntype->draw_buttons = node_composit_buts_cryptomatte; ntype->draw_buttons_ex = node_composit_buts_cryptomatte_ex; diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 832002d..af04c18 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -904,7 +904,10 @@ typedef struct NodeSunBeams { float ray_length; } NodeSunBeams; - +typedef struct NodeCanny { + char use_constant_width; + char pad[3]; +} NodeCanny; typedef struct NodeCryptomatte { float add[3]; float remove[3]; diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 42abf76..5963dc1 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -6956,6 +6956,19 @@ static void def_cmp_sunbeams(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_cmp_canny(StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeCanny", "storage"); + + prop = RNA_def_property(srna, "use_constant_width", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_constant_width", 0); + RNA_def_property_ui_text(prop, "Constant Width", "Line width gets more constant"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + +} + static void def_cmp_cryptomatte(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 8df114a..42ed90b 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -109,6 +109,7 @@ set(SRC composite/nodes/node_composite_splitViewer.c composite/nodes/node_composite_stabilize2d.c composite/nodes/node_composite_sunbeams.c + composite/nodes/node_composite_canny.c composite/nodes/node_composite_texture.c composite/nodes/node_composite_tonemap.c composite/nodes/node_composite_trackpos.c diff --git a/source/blender/nodes/NOD_composite.h b/source/blender/nodes/NOD_composite.h index a728d1e..f15643c 100644 --- a/source/blender/nodes/NOD_composite.h +++ b/source/blender/nodes/NOD_composite.h @@ -127,6 +127,7 @@ void register_node_type_cmp_glare(void); void register_node_type_cmp_tonemap(void); void register_node_type_cmp_lensdist(void); void register_node_type_cmp_sunbeams(void); +void register_node_type_cmp_canny(void); void register_node_type_cmp_colorcorrection(void); void register_node_type_cmp_boxmask(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 22c2afc..2a76a66 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -216,6 +216,7 @@ DefNode( CompositorNode, CMP_NODE_PIXELATE, 0, "PIXEL DefNode( CompositorNode, CMP_NODE_PLANETRACKDEFORM,def_cmp_planetrackdeform,"PLANETRACKDEFORM",PlaneTrackDeform,"Plane Track Deform","" ) DefNode( CompositorNode, CMP_NODE_CORNERPIN, 0, "CORNERPIN", CornerPin, "Corner Pin", "" ) DefNode( CompositorNode, CMP_NODE_SUNBEAMS, def_cmp_sunbeams, "SUNBEAMS", SunBeams, "Sun Beams", "" ) +DefNode( CompositorNode, CMP_NODE_CANNY, def_cmp_canny, "CANNY", Canny, "Canny", "" ) DefNode( CompositorNode, CMP_NODE_CRYPTOMATTE, def_cmp_cryptomatte, "CRYPTOMATTE", Cryptomatte, "Cryptomatte", "" ) DefNode( TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" ) diff --git a/source/blender/nodes/composite/nodes/node_composite_canny.c b/source/blender/nodes/composite/nodes/node_composite_canny.c new file mode 100644 index 0000000..da2107b --- /dev/null +++ b/source/blender/nodes/composite/nodes/node_composite_canny.c @@ -0,0 +1,64 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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. + * + * The Original Code is Copyright (C) 2014 Blender Foundation. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): Allosteric + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/nodes/composite/nodes/node_composite_canny.c + * \ingroup cmpnodes + */ + +#include "node_composite_util.h" + +static bNodeSocketTemplate inputs[] = { + { SOCK_FLOAT, 1, N_("Low Threshold"), 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, + { SOCK_FLOAT, 1, N_("High Threshold"), 0.2f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE}, + { SOCK_RGBA, 1, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, + { -1, 0, "" } +}; +static bNodeSocketTemplate outputs[] = { + { SOCK_RGBA, 0, N_("Image")}, + { -1, 0, "" } +}; + +static void init(bNodeTree *UNUSED(ntree), bNode *node) +{ + NodeCanny *data = MEM_callocN(sizeof(NodeCanny), "canny node"); + + data->use_constant_width = false; + + node->storage = data; +} + +void register_node_type_cmp_canny(void) +{ + static bNodeType ntype; + + cmp_node_type_base(&ntype, CMP_NODE_CANNY, "Canny", NODE_CLASS_OP_FILTER, 0); + node_type_socket_templates(&ntype, inputs, outputs); + node_type_init(&ntype, init); + node_type_storage(&ntype, "NodeCanny", node_free_standard_storage, node_copy_standard_storage); + + nodeRegisterType(&ntype); +}