Compositor: Add Anti-Aliasing node

This is an implementation of Enhanced Subpixel Morphological Antialiasing (SMAA)

The algorithm was proposed by:
  Jorge Jimenez, Jose I. Echevarria, Tiago Sousa, Diego Gutierrez

This node provides only SMAA 1x mode, so the operation will be done with no spatial
multisampling nor temporal supersampling. See Patch for comparisons.

The existing AA operation seems to be used only for binary images by some other nodes.
Using SMAA for binary images needs no important parameter such as "threshold", so we
perhaps can switch the operation to SMAA, though that changes existing behavior.

Notes:
1. The program code assumes the screen coordinates are DirectX style that the
   vertical direction is upside-down, so "top" and "bottom" actually represent bottom
   and top, respectively.

Thanks for Habib Gahbiche (zazizizou) to polish and finalize this patch.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D2411
This commit is contained in:
Habib Gahbiche 2021-03-29 07:44:27 +02:00 committed by Jeroen Bakker
parent 6af4163a3f
commit 805d947810
23 changed files with 6480 additions and 1 deletions

View File

@ -109,3 +109,7 @@ endif()
if(WITH_MOD_FLUID)
add_subdirectory(mantaflow)
endif()
if (WITH_COMPOSITOR)
add_subdirectory(smaa_areatex)
endif()

26
extern/smaa_areatex/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,26 @@
# ***** 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) 2017, Blender Foundation
# All rights reserved.
#
# The Original Code is: all of this file.
#
# Contributor(s): IRIE Shinsuke
#
# ***** END GPL LICENSE BLOCK *****
add_executable(smaa_areatex smaa_areatex.cpp)

5
extern/smaa_areatex/README.blender vendored Normal file
View File

@ -0,0 +1,5 @@
Project: smaa-cpp
URL: https://github.com/iRi-E/smaa-cpp
License: MIT
Upstream version: 0.4.0
Local modifications:

1208
extern/smaa_areatex/smaa_areatex.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

@ -1 +1 @@
Subproject commit bcd08a9506d33bdd7358201031b04d041ef22d94
Subproject commit 63492d3d0334e1827f611f8fe5a931f3ccbddfc0

View File

@ -368,6 +368,7 @@ compositor_node_categories = [
NodeItem("CompositorNodePixelate"),
NodeItem("CompositorNodeSunBeams"),
NodeItem("CompositorNodeDenoise"),
NodeItem("CompositorNodeAntiAliasing"),
]),
CompositorNodeCategory("CMP_OP_VECTOR", "Vector", items=[
NodeItem("CompositorNodeNormal"),

View File

@ -1190,6 +1190,7 @@ void ntreeGPUMaterialNodes(struct bNodeTree *localtree,
#define CMP_NODE_TRACKPOS 271
#define CMP_NODE_INPAINT 272
#define CMP_NODE_DESPECKLE 273
#define CMP_NODE_ANTIALIASING 274
#define CMP_NODE_GLARE 301
#define CMP_NODE_TONEMAP 302

File diff suppressed because it is too large Load Diff

View File

@ -4704,6 +4704,7 @@ static void registerCompositNodes()
register_node_type_cmp_defocus();
register_node_type_cmp_sunbeams();
register_node_type_cmp_denoise();
register_node_type_cmp_antialiasing();
register_node_type_cmp_valtorgb();
register_node_type_cmp_rgbtobw();

View File

@ -192,6 +192,13 @@ MINLINE double ratiod(double min, double max, double pos)
return range == 0 ? 0 : ((pos - min) / range);
}
/* Map a normalized value, i.e. from interval [0, 1] to interval [a, b] */
MINLINE float scalenorm(float a, float b, float x)
{
BLI_assert(x <= 1 && x >= 0);
return (x * (b - a)) + a;
}
/* used for zoom values*/
MINLINE float power_of_2(float val)
{

View File

@ -294,6 +294,9 @@ set(SRC
nodes/COM_FilterNode.h
nodes/COM_InpaintNode.cc
nodes/COM_InpaintNode.h
nodes/COM_AntiAliasingNode.cc
nodes/COM_AntiAliasingNode.h
operations/COM_BlurBaseOperation.cc
operations/COM_BlurBaseOperation.h
operations/COM_BokehBlurOperation.cc
@ -320,6 +323,8 @@ set(SRC
operations/COM_MovieDistortionOperation.h
operations/COM_VariableSizeBokehBlurOperation.cc
operations/COM_VariableSizeBokehBlurOperation.h
operations/COM_SMAAOperation.cc
operations/COM_SMAAOperation.h
# Matte nodes
nodes/COM_BoxMaskNode.cc
@ -566,6 +571,23 @@ data_to_c(
add_definitions(-DCL_USE_DEPRECATED_OPENCL_1_1_APIS)
set(GENSRC_DIR ${CMAKE_CURRENT_BINARY_DIR}/operations)
set(GENSRC ${GENSRC_DIR}/COM_SMAAAreaTexture.h)
add_custom_command(
OUTPUT ${GENSRC}
COMMAND ${CMAKE_COMMAND} -E make_directory ${GENSRC_DIR}
COMMAND "$<TARGET_FILE:smaa_areatex>" ${GENSRC}
DEPENDS smaa_areatex
)
add_custom_target(smaa_areatex_header
SOURCES ${GENSRC}
)
list(APPEND SRC
${GENSRC}
)
unset(GENSRC)
unset(GENSRC_DIR)
if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
@ -584,3 +606,5 @@ if(WITH_OPENIMAGEDENOISE)
endif()
blender_add_lib(bf_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_dependencies(bf_compositor smaa_areatex_header)

View File

@ -26,6 +26,7 @@
#include "COM_NodeOperationBuilder.h"
#include "COM_AlphaOverNode.h"
#include "COM_AntiAliasingNode.h"
#include "COM_BilateralBlurNode.h"
#include "COM_BlurNode.h"
#include "COM_BokehBlurNode.h"
@ -418,6 +419,9 @@ Node *COM_convert_bnode(bNode *b_node)
case CMP_NODE_EXPOSURE:
node = new ExposureNode(b_node);
break;
case CMP_NODE_ANTIALIASING:
node = new AntiAliasingNode(b_node);
break;
}
return node;
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2017, 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: IRIE Shinsuke
*/
#include "COM_AntiAliasingNode.h"
#include "COM_SMAAOperation.h"
#include "DNA_node_types.h"
void AntiAliasingNode::convertToOperations(NodeConverter &converter,
const CompositorContext & /*context*/) const
{
bNode *node = this->getbNode();
NodeAntiAliasingData *data = (NodeAntiAliasingData *)node->storage;
/* Edge Detection (First Pass) */
SMAAEdgeDetectionOperation *operation1 = nullptr;
operation1 = new SMAAEdgeDetectionOperation();
operation1->setThreshold(data->threshold);
operation1->setLocalContrastAdaptationFactor(data->contrast_limit);
converter.addOperation(operation1);
converter.mapInputSocket(getInputSocket(0), operation1->getInputSocket(0));
/* Blending Weight Calculation Pixel Shader (Second Pass) */
SMAABlendingWeightCalculationOperation *operation2 =
new SMAABlendingWeightCalculationOperation();
operation2->setCornerRounding(data->corner_rounding);
converter.addOperation(operation2);
converter.addLink(operation1->getOutputSocket(), operation2->getInputSocket(0));
/* Neighborhood Blending Pixel Shader (Third Pass) */
SMAANeighborhoodBlendingOperation *operation3 = new SMAANeighborhoodBlendingOperation();
converter.addOperation(operation3);
converter.mapInputSocket(getInputSocket(0), operation3->getInputSocket(0));
converter.addLink(operation2->getOutputSocket(), operation3->getInputSocket(1));
converter.mapOutputSocket(getOutputSocket(0), operation3->getOutputSocket());
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2017, 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: IRIE Shinsuke
*/
#ifndef _COM_AntiAliasingNode_h_
#define _COM_AntiAliasingNode_h_
#include "COM_Node.h"
/**
* @brief AntiAliasingNode
* @ingroup Node
*/
class AntiAliasingNode : public Node {
public:
AntiAliasingNode(bNode *editorNode) : Node(editorNode)
{
}
void convertToOperations(NodeConverter &converter, const CompositorContext &context) const;
};
#endif

View File

@ -0,0 +1,865 @@
/*
* Copyright 2017, 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: IRIE Shinsuke
*/
#include "COM_SMAAOperation.h"
#include "BLI_math.h"
#include "COM_SMAAAreaTexture.h"
extern "C" {
#include "IMB_colormanagement.h"
}
/*
* An implementation of Enhanced Subpixel Morphological Antialiasing (SMAA)
*
* The algorithm was proposed by:
* Jorge Jimenez, Jose I. Echevarria, Tiago Sousa, Diego Gutierrez
*
* http://www.iryoku.com/smaa/
*
* This file is based on smaa-cpp:
*
* https://github.com/iRi-E/smaa-cpp
*
* Currently only SMAA 1x mode is provided, so the operation will be done
* with no spatial multisampling nor temporal supersampling.
*
* Note: This program assumes the screen coordinates are DirectX style, so
* the vertical direction is upside-down. "top" and "bottom" actually mean
* bottom and top, respectively.
*/
/*-----------------------------------------------------------------------------*/
/* Non-Configurable Defines */
#define SMAA_AREATEX_SIZE 80
#define SMAA_AREATEX_MAX_DISTANCE 20
#define SMAA_AREATEX_MAX_DISTANCE_DIAG 20
#define SMAA_MAX_SEARCH_STEPS 362 /* 362 - 1 = 19^2 */
#define SMAA_MAX_SEARCH_STEPS_DIAG 19
/*-----------------------------------------------------------------------------*/
/* Internal Functions to Sample Pixel Color from Image */
static inline void sample(SocketReader *reader, int x, int y, float color[4])
{
if (x < 0 || x >= reader->getWidth() || y < 0 || y >= reader->getHeight()) {
color[0] = color[1] = color[2] = color[3] = 0.0;
return;
}
reader->read(color, x, y, nullptr);
}
static void sample_bilinear_vertical(
SocketReader *reader, int x, int y, float yoffset, float color[4])
{
float iy = floorf(yoffset);
float fy = yoffset - iy;
y += (int)iy;
float color00[4], color01[4];
sample(reader, x + 0, y + 0, color00);
sample(reader, x + 0, y + 1, color01);
color[0] = interpf(color01[0], color00[0], fy);
color[1] = interpf(color01[1], color00[1], fy);
color[2] = interpf(color01[2], color00[2], fy);
color[3] = interpf(color01[3], color00[3], fy);
}
static void sample_bilinear_horizontal(
SocketReader *reader, int x, int y, float xoffset, float color[4])
{
float ix = floorf(xoffset);
float fx = xoffset - ix;
x += (int)ix;
float color00[4], color10[4];
sample(reader, x + 0, y + 0, color00);
sample(reader, x + 1, y + 0, color10);
color[0] = interpf(color10[0], color00[0], fx);
color[1] = interpf(color10[1], color00[1], fx);
color[2] = interpf(color10[2], color00[2], fx);
color[3] = interpf(color10[3], color00[3], fx);
}
/*-----------------------------------------------------------------------------*/
/* Internal Functions to Sample Blending Weights from AreaTex */
static inline const float *areatex_sample_internal(const float *areatex, int x, int y)
{
return &areatex[(CLAMPIS(x, 0, SMAA_AREATEX_SIZE - 1) +
CLAMPIS(y, 0, SMAA_AREATEX_SIZE - 1) * SMAA_AREATEX_SIZE) *
2];
}
/**
* We have the distance and both crossing edges. So, what are the areas
* at each side of current edge?
*/
static void area(int d1, int d2, int e1, int e2, float weights[2])
{
/* The areas texture is compressed quadratically: */
float x = (float)(SMAA_AREATEX_MAX_DISTANCE * e1) + sqrtf((float)d1);
float y = (float)(SMAA_AREATEX_MAX_DISTANCE * e2) + sqrtf((float)d2);
float ix = floorf(x), iy = floorf(y);
float fx = x - ix, fy = y - iy;
int X = (int)ix, Y = (int)iy;
const float *weights00 = areatex_sample_internal(areatex, X + 0, Y + 0);
const float *weights10 = areatex_sample_internal(areatex, X + 1, Y + 0);
const float *weights01 = areatex_sample_internal(areatex, X + 0, Y + 1);
const float *weights11 = areatex_sample_internal(areatex, X + 1, Y + 1);
weights[0] = interpf(
interpf(weights11[0], weights01[0], fx), interpf(weights10[0], weights00[0], fx), fy);
weights[1] = interpf(
interpf(weights11[1], weights01[1], fx), interpf(weights10[1], weights00[1], fx), fy);
}
/**
* Similar to area(), this calculates the area corresponding to a certain
* diagonal distance and crossing edges 'e'.
*/
static void area_diag(int d1, int d2, int e1, int e2, float weights[2])
{
int x = SMAA_AREATEX_MAX_DISTANCE_DIAG * e1 + d1;
int y = SMAA_AREATEX_MAX_DISTANCE_DIAG * e2 + d2;
const float *w = areatex_sample_internal(areatex_diag, x, y);
copy_v2_v2(weights, w);
}
/*-----------------------------------------------------------------------------*/
/* Edge Detection (First Pass) */
/*-----------------------------------------------------------------------------*/
SMAAEdgeDetectionOperation::SMAAEdgeDetectionOperation()
{
this->addInputSocket(DataType::Color); /* image */
this->addInputSocket(DataType::Value); /* depth, material ID, etc. */
this->addOutputSocket(DataType::Color);
this->setComplex(true);
this->m_imageReader = nullptr;
this->m_valueReader = nullptr;
this->m_threshold = 0.1f;
this->m_contrast_limit = 2.0f;
}
void SMAAEdgeDetectionOperation::initExecution()
{
this->m_imageReader = this->getInputSocketReader(0);
this->m_valueReader = this->getInputSocketReader(1);
}
void SMAAEdgeDetectionOperation::deinitExecution()
{
this->m_imageReader = nullptr;
this->m_valueReader = nullptr;
}
void SMAAEdgeDetectionOperation::setThreshold(float threshold)
{
/* UI values are between 0 and 1 for simplicity but algorithm expects values between 0 and 0.5 */
m_threshold = scalenorm(0, 0.5, threshold);
}
void SMAAEdgeDetectionOperation::setLocalContrastAdaptationFactor(float factor)
{
/* UI values are between 0 and 1 for simplicity but algorithm expects values between 1 and 10 */
m_contrast_limit = scalenorm(1, 10, factor);
}
bool SMAAEdgeDetectionOperation::determineDependingAreaOfInterest(
rcti *input, ReadBufferOperation *readOperation, rcti *output)
{
rcti newInput;
newInput.xmax = input->xmax + 1;
newInput.xmin = input->xmin - 2;
newInput.ymax = input->ymax + 1;
newInput.ymin = input->ymin - 2;
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
void SMAAEdgeDetectionOperation::executePixel(float output[4], int x, int y, void * /*data*/)
{
float color[4];
/* Calculate luma deltas: */
sample(m_imageReader, x, y, color);
float L = IMB_colormanagement_get_luminance(color);
sample(m_imageReader, x - 1, y, color);
float Lleft = IMB_colormanagement_get_luminance(color);
sample(m_imageReader, x, y - 1, color);
float Ltop = IMB_colormanagement_get_luminance(color);
float Dleft = fabsf(L - Lleft);
float Dtop = fabsf(L - Ltop);
/* We do the usual threshold: */
output[0] = (x > 0 && Dleft >= m_threshold) ? 1.0f : 0.0f;
output[1] = (y > 0 && Dtop >= m_threshold) ? 1.0f : 0.0f;
output[2] = 0.0f;
output[3] = 1.0f;
/* Then discard if there is no edge: */
if (is_zero_v2(output)) {
return;
}
/* Calculate right and bottom deltas: */
sample(m_imageReader, x + 1, y, color);
float Lright = IMB_colormanagement_get_luminance(color);
sample(m_imageReader, x, y + 1, color);
float Lbottom = IMB_colormanagement_get_luminance(color);
float Dright = fabsf(L - Lright);
float Dbottom = fabsf(L - Lbottom);
/* Calculate the maximum delta in the direct neighborhood: */
float maxDelta = fmaxf(fmaxf(Dleft, Dright), fmaxf(Dtop, Dbottom));
/* Calculate luma used for both left and top edges: */
sample(m_imageReader, x - 1, y - 1, color);
float Llefttop = IMB_colormanagement_get_luminance(color);
/* Left edge */
if (output[0] != 0.0f) {
/* Calculate deltas around the left pixel: */
sample(m_imageReader, x - 2, y, color);
float Lleftleft = IMB_colormanagement_get_luminance(color);
sample(m_imageReader, x - 1, y + 1, color);
float Lleftbottom = IMB_colormanagement_get_luminance(color);
float Dleftleft = fabsf(Lleft - Lleftleft);
float Dlefttop = fabsf(Lleft - Llefttop);
float Dleftbottom = fabsf(Lleft - Lleftbottom);
/* Calculate the final maximum delta: */
maxDelta = fmaxf(maxDelta, fmaxf(Dleftleft, fmaxf(Dlefttop, Dleftbottom)));
/* Local contrast adaptation: */
if (maxDelta > m_contrast_limit * Dleft) {
output[0] = 0.0f;
}
}
/* Top edge */
if (output[1] != 0.0f) {
/* Calculate top-top delta: */
sample(m_imageReader, x, y - 2, color);
float Ltoptop = IMB_colormanagement_get_luminance(color);
sample(m_imageReader, x + 1, y - 1, color);
float Ltopright = IMB_colormanagement_get_luminance(color);
float Dtoptop = fabsf(Ltop - Ltoptop);
float Dtopleft = fabsf(Ltop - Llefttop);
float Dtopright = fabsf(Ltop - Ltopright);
/* Calculate the final maximum delta: */
maxDelta = fmaxf(maxDelta, fmaxf(Dtoptop, fmaxf(Dtopleft, Dtopright)));
/* Local contrast adaptation: */
if (maxDelta > m_contrast_limit * Dtop) {
output[1] = 0.0f;
}
}
}
/*-----------------------------------------------------------------------------*/
/* Blending Weight Calculation (Second Pass) */
/*-----------------------------------------------------------------------------*/
SMAABlendingWeightCalculationOperation::SMAABlendingWeightCalculationOperation()
{
this->addInputSocket(DataType::Color); /* edges */
this->addOutputSocket(DataType::Color);
this->setComplex(true);
this->m_imageReader = nullptr;
this->m_corner_rounding = 25;
}
void *SMAABlendingWeightCalculationOperation::initializeTileData(rcti *rect)
{
return getInputOperation(0)->initializeTileData(rect);
}
void SMAABlendingWeightCalculationOperation::initExecution()
{
this->m_imageReader = this->getInputSocketReader(0);
}
void SMAABlendingWeightCalculationOperation::setCornerRounding(float rounding)
{
/* UI values are between 0 and 1 for simplicity but algorithm expects values between 0 and 100 */
m_corner_rounding = static_cast<int>(scalenorm(0, 100, rounding));
}
void SMAABlendingWeightCalculationOperation::executePixel(float output[4],
int x,
int y,
void * /*data*/)
{
float edges[4], c[4];
zero_v4(output);
sample(m_imageReader, x, y, edges);
/* Edge at north */
if (edges[1] > 0.0f) {
/* Diagonals have both north and west edges, so calculating weights for them */
/* in one of the boundaries is enough. */
calculateDiagWeights(x, y, edges, output);
/* We give priority to diagonals, so if we find a diagonal we skip */
/* horizontal/vertical processing. */
if (!is_zero_v2(output)) {
return;
}
/* Find the distance to the left and the right: */
int left = searchXLeft(x, y);
int right = searchXRight(x, y);
int d1 = x - left, d2 = right - x;
/* Fetch the left and right crossing edges: */
int e1 = 0, e2 = 0;
sample(m_imageReader, left, y - 1, c);
if (c[0] > 0.0) {
e1 += 1;
}
sample(m_imageReader, left, y, c);
if (c[0] > 0.0) {
e1 += 2;
}
sample(m_imageReader, right + 1, y - 1, c);
if (c[0] > 0.0) {
e2 += 1;
}
sample(m_imageReader, right + 1, y, c);
if (c[0] > 0.0) {
e2 += 2;
}
/* Ok, we know how this pattern looks like, now it is time for getting */
/* the actual area: */
area(d1, d2, e1, e2, output); /* R, G */
/* Fix corners: */
if (m_corner_rounding) {
detectHorizontalCornerPattern(output, left, right, y, d1, d2);
}
}
/* Edge at west */
if (edges[0] > 0.0f) {
/* Did we already do diagonal search for this west edge from the left neighboring pixel? */
if (isVerticalSearchUnneeded(x, y)) {
return;
}
/* Find the distance to the top and the bottom: */
int top = searchYUp(x, y);
int bottom = searchYDown(x, y);
int d1 = y - top, d2 = bottom - y;
/* Fetch the top ang bottom crossing edges: */
int e1 = 0, e2 = 0;
sample(m_imageReader, x - 1, top, c);
if (c[1] > 0.0) {
e1 += 1;
}
sample(m_imageReader, x, top, c);
if (c[1] > 0.0) {
e1 += 2;
}
sample(m_imageReader, x - 1, bottom + 1, c);
if (c[1] > 0.0) {
e2 += 1;
}
sample(m_imageReader, x, bottom + 1, c);
if (c[1] > 0.0) {
e2 += 2;
}
/* Get the area for this direction: */
area(d1, d2, e1, e2, output + 2); /* B, A */
/* Fix corners: */
if (m_corner_rounding) {
detectVerticalCornerPattern(output + 2, x, top, bottom, d1, d2);
}
}
}
void SMAABlendingWeightCalculationOperation::deinitExecution()
{
this->m_imageReader = nullptr;
}
bool SMAABlendingWeightCalculationOperation::determineDependingAreaOfInterest(
rcti *input, ReadBufferOperation *readOperation, rcti *output)
{
rcti newInput;
newInput.xmax = input->xmax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG + 1);
newInput.xmin = input->xmin -
fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG + 1);
newInput.ymax = input->ymax + fmax(SMAA_MAX_SEARCH_STEPS, SMAA_MAX_SEARCH_STEPS_DIAG);
newInput.ymin = input->ymin -
fmax(fmax(SMAA_MAX_SEARCH_STEPS - 1, 1), SMAA_MAX_SEARCH_STEPS_DIAG);
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}
/*-----------------------------------------------------------------------------*/
/* Diagonal Search Functions */
/**
* These functions allows to perform diagonal pattern searches.
*/
int SMAABlendingWeightCalculationOperation::searchDiag1(int x, int y, int dir, bool *found)
{
float e[4];
int end = x + SMAA_MAX_SEARCH_STEPS_DIAG * dir;
*found = false;
while (x != end) {
x += dir;
y -= dir;
sample(m_imageReader, x, y, e);
if (e[1] == 0.0f) {
*found = true;
break;
}
if (e[0] == 0.0f) {
*found = true;
return (dir < 0) ? x : x - dir;
}
}
return x - dir;
}
int SMAABlendingWeightCalculationOperation::searchDiag2(int x, int y, int dir, bool *found)
{
float e[4];
int end = x + SMAA_MAX_SEARCH_STEPS_DIAG * dir;
*found = false;
while (x != end) {
x += dir;
y += dir;
sample(m_imageReader, x, y, e);
if (e[1] == 0.0f) {
*found = true;
break;
}
sample(m_imageReader, x + 1, y, e);
if (e[0] == 0.0f) {
*found = true;
return (dir > 0) ? x : x - dir;
}
}
return x - dir;
}
/**
* This searches for diagonal patterns and returns the corresponding weights.
*/
void SMAABlendingWeightCalculationOperation::calculateDiagWeights(int x,
int y,
const float edges[2],
float weights[2])
{
int d1, d2;
bool d1_found, d2_found;
float e[4], c[4];
zero_v2(weights);
if (SMAA_MAX_SEARCH_STEPS_DIAG <= 0) {
return;
}
/* Search for the line ends: */
if (edges[0] > 0.0f) {
d1 = x - searchDiag1(x, y, -1, &d1_found);
}
else {
d1 = 0;
d1_found = true;
}
d2 = searchDiag1(x, y, 1, &d2_found) - x;
if (d1 + d2 > 2) { /* d1 + d2 + 1 > 3 */
int e1 = 0, e2 = 0;
if (d1_found) {
/* Fetch the crossing edges: */
int left = x - d1, bottom = y + d1;
sample(m_imageReader, left - 1, bottom, c);
if (c[1] > 0.0) {
e1 += 2;
}
sample(m_imageReader, left, bottom, c);
if (c[0] > 0.0) {
e1 += 1;
}
}
if (d2_found) {
/* Fetch the crossing edges: */
int right = x + d2, top = y - d2;
sample(m_imageReader, right + 1, top, c);
if (c[1] > 0.0) {
e2 += 2;
}
sample(m_imageReader, right + 1, top - 1, c);
if (c[0] > 0.0) {
e2 += 1;
}
}
/* Fetch the areas for this line: */
area_diag(d1, d2, e1, e2, weights);
}
/* Search for the line ends: */
d1 = x - searchDiag2(x, y, -1, &d1_found);
sample(m_imageReader, x + 1, y, e);
if (e[0] > 0.0f) {
d2 = searchDiag2(x, y, 1, &d2_found) - x;
}
else {
d2 = 0;
d2_found = true;
}
if (d1 + d2 > 2) { /* d1 + d2 + 1 > 3 */
int e1 = 0, e2 = 0;
if (d1_found) {
/* Fetch the crossing edges: */
int left = x - d1, top = y - d1;
sample(m_imageReader, left - 1, top, c);
if (c[1] > 0.0) {
e1 += 2;
}
sample(m_imageReader, left, top - 1, c);
if (c[0] > 0.0) {
e1 += 1;
}
}
if (d2_found) {
/* Fetch the crossing edges: */
int right = x + d2, bottom = y + d2;
sample(m_imageReader, right + 1, bottom, c);
if (c[1] > 0.0) {
e2 += 2;
}
if (c[0] > 0.0) {
e2 += 1;
}
}
/* Fetch the areas for this line: */
float w[2];
area_diag(d1, d2, e1, e2, w);
weights[0] += w[1];
weights[1] += w[0];
}
}
bool SMAABlendingWeightCalculationOperation::isVerticalSearchUnneeded(int x, int y)
{
int d1, d2;
bool found;
float e[4];
if (SMAA_MAX_SEARCH_STEPS_DIAG <= 0) {
return false;
}
/* Search for the line ends: */
sample(m_imageReader, x - 1, y, e);
if (e[1] > 0.0f) {
d1 = x - searchDiag2(x - 1, y, -1, &found);
}
else {
d1 = 0;
}
d2 = searchDiag2(x - 1, y, 1, &found) - x;
return (d1 + d2 > 2); /* d1 + d2 + 1 > 3 */
}
/*-----------------------------------------------------------------------------*/
/* Horizontal/Vertical Search Functions */
int SMAABlendingWeightCalculationOperation::searchXLeft(int x, int y)
{
int end = x - SMAA_MAX_SEARCH_STEPS;
float e[4];
while (x > end) {
sample(m_imageReader, x, y, e);
if (e[1] == 0.0f) { /* Is the edge not activated? */
break;
}
if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return x;
}
sample(m_imageReader, x, y - 1, e);
if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return x;
}
x--;
}
return x + 1;
}
int SMAABlendingWeightCalculationOperation::searchXRight(int x, int y)
{
int end = x + SMAA_MAX_SEARCH_STEPS;
float e[4];
while (x < end) {
x++;
sample(m_imageReader, x, y, e);
if (e[1] == 0.0f || /* Is the edge not activated? */
e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
sample(m_imageReader, x, y - 1, e);
if (e[0] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
}
return x - 1;
}
int SMAABlendingWeightCalculationOperation::searchYUp(int x, int y)
{
int end = y - SMAA_MAX_SEARCH_STEPS;
float e[4];
while (y > end) {
sample(m_imageReader, x, y, e);
if (e[0] == 0.0f) { /* Is the edge not activated? */
break;
}
if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return y;
}
sample(m_imageReader, x - 1, y, e);
if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
return y;
}
y--;
}
return y + 1;
}
int SMAABlendingWeightCalculationOperation::searchYDown(int x, int y)
{
int end = y + SMAA_MAX_SEARCH_STEPS;
float e[4];
while (y < end) {
y++;
sample(m_imageReader, x, y, e);
if (e[0] == 0.0f || /* Is the edge not activated? */
e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
sample(m_imageReader, x - 1, y, e);
if (e[1] != 0.0f) { /* Or is there a crossing edge that breaks the line? */
break;
}
}
return y - 1;
}
/*-----------------------------------------------------------------------------*/
/* Corner Detection Functions */
void SMAABlendingWeightCalculationOperation::detectHorizontalCornerPattern(
float weights[2], int left, int right, int y, int d1, int d2)
{
float factor[2] = {1.0f, 1.0f};
float rounding = m_corner_rounding / 100.0f;
float e[4];
/* Reduce blending for pixels in the center of a line. */
rounding *= (d1 == d2) ? 0.5f : 1.0f;
/* Near the left corner */
if (d1 <= d2) {
sample(m_imageReader, left, y + 1, e);
factor[0] -= rounding * e[0];
sample(m_imageReader, left, y - 2, e);
factor[1] -= rounding * e[0];
}
/* Near the right corner */
if (d1 >= d2) {
sample(m_imageReader, right + 1, y + 1, e);
factor[0] -= rounding * e[0];
sample(m_imageReader, right + 1, y - 2, e);
factor[1] -= rounding * e[0];
}
weights[0] *= CLAMPIS(factor[0], 0.0f, 1.0f);
weights[1] *= CLAMPIS(factor[1], 0.0f, 1.0f);
}
void SMAABlendingWeightCalculationOperation::detectVerticalCornerPattern(
float weights[2], int x, int top, int bottom, int d1, int d2)
{
float factor[2] = {1.0f, 1.0f};
float rounding = m_corner_rounding / 100.0f;
float e[4];
/* Reduce blending for pixels in the center of a line. */
rounding *= (d1 == d2) ? 0.5f : 1.0f;
/* Near the top corner */
if (d1 <= d2) {
sample(m_imageReader, x + 1, top, e);
factor[0] -= rounding * e[1];
sample(m_imageReader, x - 2, top, e);
factor[1] -= rounding * e[1];
}
/* Near the bottom corner */
if (d1 >= d2) {
sample(m_imageReader, x + 1, bottom + 1, e);
factor[0] -= rounding * e[1];
sample(m_imageReader, x - 2, bottom + 1, e);
factor[1] -= rounding * e[1];
}
weights[0] *= CLAMPIS(factor[0], 0.0f, 1.0f);
weights[1] *= CLAMPIS(factor[1], 0.0f, 1.0f);
}
/*-----------------------------------------------------------------------------*/
/* Neighborhood Blending (Third Pass) */
/*-----------------------------------------------------------------------------*/
SMAANeighborhoodBlendingOperation::SMAANeighborhoodBlendingOperation()
{
this->addInputSocket(DataType::Color); /* image */
this->addInputSocket(DataType::Color); /* blend */
this->addOutputSocket(DataType::Color);
this->setComplex(true);
this->m_image1Reader = nullptr;
this->m_image2Reader = nullptr;
}
void *SMAANeighborhoodBlendingOperation::initializeTileData(rcti *rect)
{
return getInputOperation(0)->initializeTileData(rect);
}
void SMAANeighborhoodBlendingOperation::initExecution()
{
this->m_image1Reader = this->getInputSocketReader(0);
this->m_image2Reader = this->getInputSocketReader(1);
}
void SMAANeighborhoodBlendingOperation::executePixel(float output[4],
int x,
int y,
void * /*data*/)
{
float w[4];
/* Fetch the blending weights for current pixel: */
sample(m_image2Reader, x, y, w);
float left = w[2], top = w[0];
sample(m_image2Reader, x + 1, y, w);
float right = w[3];
sample(m_image2Reader, x, y + 1, w);
float bottom = w[1];
/* Is there any blending weight with a value greater than 0.0? */
if (right + bottom + left + top < 1e-5f) {
sample(m_image1Reader, x, y, output);
return;
}
/* Calculate the blending offsets: */
void (*samplefunc)(SocketReader * reader, int x, int y, float xoffset, float color[4]);
float offset1, offset2, weight1, weight2, color1[4], color2[4];
if (fmaxf(right, left) > fmaxf(bottom, top)) { /* max(horizontal) > max(vertical) */
samplefunc = sample_bilinear_horizontal;
offset1 = right;
offset2 = -left;
weight1 = right / (right + left);
weight2 = left / (right + left);
}
else {
samplefunc = sample_bilinear_vertical;
offset1 = bottom;
offset2 = -top;
weight1 = bottom / (bottom + top);
weight2 = top / (bottom + top);
}
/* We exploit bilinear filtering to mix current pixel with the chosen neighbor: */
samplefunc(m_image1Reader, x, y, offset1, color1);
samplefunc(m_image1Reader, x, y, offset2, color2);
mul_v4_v4fl(output, color1, weight1);
madd_v4_v4fl(output, color2, weight2);
}
void SMAANeighborhoodBlendingOperation::deinitExecution()
{
this->m_image1Reader = nullptr;
this->m_image2Reader = nullptr;
}
bool SMAANeighborhoodBlendingOperation::determineDependingAreaOfInterest(
rcti *input, ReadBufferOperation *readOperation, rcti *output)
{
rcti newInput;
newInput.xmax = input->xmax + 1;
newInput.xmin = input->xmin - 1;
newInput.ymax = input->ymax + 1;
newInput.ymin = input->ymin - 1;
return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
}

View File

@ -0,0 +1,147 @@
/*
* Copyright 2017, 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: IRIE Shinsuke
*/
#ifndef _COM_SMAAOperation_h
#define _COM_SMAAOperation_h
#include "COM_NodeOperation.h"
/*-----------------------------------------------------------------------------*/
/* Edge Detection (First Pass) */
class SMAAEdgeDetectionOperation : public NodeOperation {
protected:
SocketReader *m_imageReader;
SocketReader *m_valueReader;
float m_threshold;
float m_contrast_limit;
public:
SMAAEdgeDetectionOperation();
/**
* the inner loop of this program
*/
virtual void executePixel(float output[4], int x, int y, void *data) override;
/**
* Initialize the execution
*/
void initExecution() override;
/**
* Deinitialize the execution
*/
void deinitExecution() override;
void setThreshold(float threshold);
void setLocalContrastAdaptationFactor(float factor);
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output) override;
};
/*-----------------------------------------------------------------------------*/
/* Blending Weight Calculation (Second Pass) */
class SMAABlendingWeightCalculationOperation : public NodeOperation {
private:
SocketReader *m_imageReader;
int m_corner_rounding;
public:
SMAABlendingWeightCalculationOperation();
/**
* the inner loop of this program
*/
void executePixel(float output[4], int x, int y, void *data);
/**
* Initialize the execution
*/
void initExecution();
void *initializeTileData(rcti *rect);
/**
* Deinitialize the execution
*/
void deinitExecution();
void setCornerRounding(float rounding);
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output);
private:
/* Diagonal Search Functions */
int searchDiag1(int x, int y, int dir, bool *found);
int searchDiag2(int x, int y, int dir, bool *found);
void calculateDiagWeights(int x, int y, const float edges[2], float weights[2]);
bool isVerticalSearchUnneeded(int x, int y);
/* Horizontal/Vertical Search Functions */
int searchXLeft(int x, int y);
int searchXRight(int x, int y);
int searchYUp(int x, int y);
int searchYDown(int x, int y);
/* Corner Detection Functions */
void detectHorizontalCornerPattern(float weights[2], int left, int right, int y, int d1, int d2);
void detectVerticalCornerPattern(float weights[2], int x, int top, int bottom, int d1, int d2);
};
/*-----------------------------------------------------------------------------*/
/* Neighborhood Blending (Third Pass) */
class SMAANeighborhoodBlendingOperation : public NodeOperation {
private:
SocketReader *m_image1Reader;
SocketReader *m_image2Reader;
public:
SMAANeighborhoodBlendingOperation();
/**
* the inner loop of this program
*/
void executePixel(float output[4], int x, int y, void *data);
/**
* Initialize the execution
*/
void initExecution();
void *initializeTileData(rcti *rect);
/**
* Deinitialize the execution
*/
void deinitExecution();
bool determineDependingAreaOfInterest(rcti *input,
ReadBufferOperation *readOperation,
rcti *output);
};
#endif

View File

@ -1528,6 +1528,17 @@ static void node_composit_buts_defocus(uiLayout *layout, bContext *C, PointerRNA
uiItemR(sub, ptr, "z_scale", DEFAULT_FLAGS, NULL, ICON_NONE);
}
static void node_composit_buts_antialiasing(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiLayout *col;
col = uiLayoutColumn(layout, false);
uiItemR(col, ptr, "threshold", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "contrast_limit", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "corner_rounding", 0, NULL, ICON_NONE);
}
/* qdn: glare node */
static void node_composit_buts_glare(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
@ -2799,6 +2810,9 @@ static void node_composit_set_butfunc(bNodeType *ntype)
case CMP_NODE_DEFOCUS:
ntype->draw_buttons = node_composit_buts_defocus;
break;
case CMP_NODE_ANTIALIASING:
ntype->draw_buttons = node_composit_buts_antialiasing;
break;
case CMP_NODE_GLARE:
ntype->draw_buttons = node_composit_buts_glare;
break;

View File

@ -715,6 +715,12 @@ typedef struct NodeBilateralBlurData {
char _pad[2];
} NodeBilateralBlurData;
typedef struct NodeAntiAliasingData {
float threshold;
float contrast_limit;
float corner_rounding;
} NodeAntiAliasingData;
/* NOTE: Only for do-version code. */
typedef struct NodeHueSat {
float hue, sat, val;

View File

@ -8662,6 +8662,41 @@ static void def_cmp_denoise(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_cmp_antialiasing(StructRNA *srna)
{
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeAntiAliasingData", "storage");
prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "threshold");
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
RNA_def_property_ui_text(
prop,
"Threshold",
"Threshold to detect edges (smaller threshold makes more sensitive detection)");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "contrast_limit", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "contrast_limit");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
RNA_def_property_ui_text(
prop,
"Contrast Limit",
"How much to eliminate spurious edges to avoid artifacts (the larger value makes less "
"active; the value 2.0, for example, means discard a detected edge if there is a "
"neighboring edge that has 2.0 times bigger contrast than the current one)");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "corner_rounding", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "corner_rounding");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1, 3);
RNA_def_property_ui_text(prop, "Corner Rounding", "How much sharp corners will be rounded");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
/* -- Texture Nodes --------------------------------------------------------- */
static void def_tex_output(StructRNA *srna)

View File

@ -46,6 +46,7 @@ set(INC
set(SRC
composite/nodes/node_composite_alphaOver.c
composite/nodes/node_composite_antialiasing.c
composite/nodes/node_composite_bilateralblur.c
composite/nodes/node_composite_blur.c
composite/nodes/node_composite_bokehblur.c

View File

@ -79,6 +79,7 @@ void register_node_type_cmp_inpaint(void);
void register_node_type_cmp_despeckle(void);
void register_node_type_cmp_defocus(void);
void register_node_type_cmp_denoise(void);
void register_node_type_cmp_antialiasing(void);
void register_node_type_cmp_valtorgb(void);
void register_node_type_cmp_rgbtobw(void);

View File

@ -224,6 +224,7 @@ DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE, def_cmp_cryptomatte, "CRYPTO
DefNode(CompositorNode, CMP_NODE_CRYPTOMATTE_LEGACY, def_cmp_cryptomatte_legacy, "CRYPTOMATTE", Cryptomatte, "Cryptomatte (Legacy)", "" )
DefNode(CompositorNode, CMP_NODE_DENOISE, def_cmp_denoise, "DENOISE", Denoise, "Denoise", "" )
DefNode(CompositorNode, CMP_NODE_EXPOSURE, 0, "EXPOSURE", Exposure, "Exposure", "" )
DefNode(CompositorNode, CMP_NODE_ANTIALIASING, def_cmp_antialiasing, "ANTIALIASING", AntiAliasing, "Anti-Aliasing", "" )
DefNode(TextureNode, TEX_NODE_OUTPUT, def_tex_output, "OUTPUT", Output, "Output", "" )
DefNode(TextureNode, TEX_NODE_CHECKER, 0, "CHECKER", Checker, "Checker", "" )

View File

@ -0,0 +1,65 @@
/*
* ***** 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) 2017 Blender Foundation.
* All rights reserved.
*
* The Original Code is: all of this file.
*
* Contributor(s): IRIE Shinsuke
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/nodes/composite/nodes/node_composite_antialiasing.c
* \ingroup cmpnodes
*/
#include "node_composite_util.h"
/* **************** Anti-Aliasing (SMAA 1x) ******************** */
static bNodeSocketTemplate cmp_node_antialiasing_in[] = {
{SOCK_RGBA, N_("Image"), 1.0f, 1.0f, 1.0f, 1.0f}, {-1, ""}};
static bNodeSocketTemplate cmp_node_antialiasing_out[] = {{SOCK_RGBA, N_("Image")}, {-1, ""}};
static void node_composit_init_antialiasing(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeAntiAliasingData *data = MEM_callocN(sizeof(NodeAntiAliasingData), "node antialiasing data");
data->threshold = 1.0f;
data->contrast_limit = 0.2f;
data->corner_rounding = 0.25f;
node->storage = data;
}
void register_node_type_cmp_antialiasing(void)
{
static bNodeType ntype;
cmp_node_type_base(
&ntype, CMP_NODE_ANTIALIASING, "Anti-Aliasing", NODE_CLASS_OP_FILTER, NODE_PREVIEW);
node_type_socket_templates(&ntype, cmp_node_antialiasing_in, cmp_node_antialiasing_out);
node_type_size(&ntype, 170, 140, 200);
node_type_init(&ntype, node_composit_init_antialiasing);
node_type_storage(
&ntype, "NodeAntiAliasingData", node_free_standard_storage, node_copy_standard_storage);
nodeRegisterType(&ntype);
}