Compositor: add new node: Kuwahara filter #107015

Merged
Habib Gahbiche merged 22 commits from zazizizou/blender:com-kuwahara-filter-node into main 2023-06-08 16:14:51 +02:00
2 changed files with 49 additions and 41 deletions
Showing only changes of commit f5a92f9fd6 - Show all commits

View File

@ -43,12 +43,15 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
float y, float y,
PixelSampler sampler) PixelSampler sampler)
{ {
BLI_assert(this->get_width() == s_xx_reader_->get_width()); const int width = this->get_width();
BLI_assert(this->get_height() == s_xx_reader_->get_height()); const int height = this->get_width();
zazizizou marked this conversation as resolved Outdated

Should we consider something from:

  • Pass-through the input as-is
  • Output magenta

So that if someone saves file with the Kuwahara node in it and someone opens it without enabling full-frame compositor we do not leave the output uninitialized?

Should we consider something from: - Pass-through the input as-is - Output magenta So that if someone saves file with the Kuwahara node in it and someone opens it without enabling full-frame compositor we do not leave the output uninitialized?
BLI_assert(this->get_width() == s_yy_reader_->get_width());
BLI_assert(this->get_height() == s_yy_reader_->get_height()); BLI_assert(width == s_xx_reader_->get_width());
BLI_assert(this->get_width() == s_xy_reader_->get_width()); BLI_assert(height == s_xx_reader_->get_height());
BLI_assert(this->get_height() == s_xy_reader_->get_height()); BLI_assert(width == s_yy_reader_->get_width());
BLI_assert(height == s_yy_reader_->get_height());
BLI_assert(width == s_xy_reader_->get_width());
BLI_assert(height == s_xy_reader_->get_height());
/* Values recommended by authors in original paper. */ /* Values recommended by authors in original paper. */
const float angle = 2.0 * M_PI / n_div_; const float angle = 2.0 * M_PI / n_div_;
@ -93,8 +96,8 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
continue; continue;
/* Rotate and scale the kernel. This is the "anisotropic" part. */ /* Rotate and scale the kernel. This is the "anisotropic" part. */
int dx2 = (int)(sx * (cos(theta) * dx - sin(theta) * dy)); int dx2 = int(sx * (cos(theta) * dx - sin(theta) * dy));
int dy2 = (int)(sy * (sin(theta) * dx + cos(theta) * dy)); int dy2 = int(sy * (sin(theta) * dx + cos(theta) * dy));
int xx = x + dx2; int xx = x + dx2;
int yy = y + dy2; int yy = y + dy2;
@ -102,20 +105,20 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
if (xx < 0) { if (xx < 0) {
zazizizou marked this conversation as resolved Outdated

It might help making it an explicit const int height = this->get_height() outside of the loop. I am not sure compiler is smart enough to do it for us.
Although, also not suer it will give measurable time impact, but still feels like a good thing to do.

It might help making it an explicit `const int height = this->get_height()` outside of the loop. I am not sure compiler is smart enough to do it for us. Although, also not suer it will give measurable time impact, but still feels like a good thing to do.
xx = 0; xx = 0;
} }
if (xx >= this->get_width()) { if (xx >= width) {
xx = this->get_width() - 1; xx = width - 1;
} }
if (yy < 0) { if (yy < 0) {
yy = 0; yy = 0;
} }
if (yy >= this->get_height()) { if (yy >= height) {
yy = this->get_height() - 1; yy = height - 1;
} }
double ddx2 = (double)dx2; const double ddx2 = double(dx2);
zazizizou marked this conversation as resolved Outdated

For the arithmetic types you can do int(foo) https://wiki.blender.org/wiki/Style_Guide/C_Cpp#C.2B.2B_Type_Cast

Probably will make code a bit easier to read.

For the arithmetic types you can do `int(foo)` https://wiki.blender.org/wiki/Style_Guide/C_Cpp#C.2B.2B_Type_Cast Probably will make code a bit easier to read.
double ddy2 = (double)dy2; const double ddy2 = double(dy2);
double theta = atan2(ddy2, ddx2) + M_PI; const double theta = atan2(ddy2, ddx2) + M_PI;
int t = static_cast<int>(floor(theta / angle)) % n_div_; const int t = int(floor(theta / angle)) % n_div_;
double d2 = dx2 * dx2 + dy2 * dy2; double d2 = dx2 * dx2 + dy2 * dy2;
double g = exp(-d2 / (2.0 * kernel_size_)); double g = exp(-d2 / (2.0 * kernel_size_));
float color[4]; float color[4];
@ -135,9 +138,10 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
double de = 0.0; double de = 0.0;
zazizizou marked this conversation as resolved
Review

In other performance critical areas we do

const float weight_inv = 1.0f / weight;
a = ... foo * weight_inv ..;
b = ... bar * weight_inv ..;
c = ... baz * weight_inv ..;

Again, not something which i know for sure will show performance impact, but might worth doing so nevertheless.

In other performance critical areas we do ``` const float weight_inv = 1.0f / weight; a = ... foo * weight_inv ..; b = ... bar * weight_inv ..; c = ... baz * weight_inv ..; ``` Again, not something which i know for sure will show performance impact, but might worth doing so nevertheless.
double nu = 0.0; double nu = 0.0;
for (int i = 0; i < n_div_; i++) { for (int i = 0; i < n_div_; i++) {
mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; double weight_inv = 1.0 / weight[i];
sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; mean[i] = weight[i] != 0 ? mean[i] * weight_inv : 0.0;
var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; sum[i] = weight[i] != 0 ? sum[i] * weight_inv : 0.0;
var[i] = weight[i] != 0 ? var[i] * weight_inv : 0.0;
var[i] = var[i] - sum[i] * sum[i]; var[i] = var[i] - sum[i] * sum[i];
var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON;
double w = powf(var[i], -q); double w = powf(var[i], -q);
@ -147,7 +151,6 @@ void KuwaharaAnisotropicOperation::execute_pixel_sampled(float output[4],
} }
double val = nu > EPS ? de / nu : 0.0; double val = nu > EPS ? de / nu : 0.0;
CLAMP_MAX(val, 1.0f);
output[ch] = val; output[ch] = val;
} }
@ -189,12 +192,15 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
MemoryBuffer *s_yy = inputs[2]; MemoryBuffer *s_yy = inputs[2];
MemoryBuffer *s_xy = inputs[3]; MemoryBuffer *s_xy = inputs[3];
BLI_assert(image->get_width() == s_xx->get_width()); const int width = image->get_width();
BLI_assert(image->get_height() == s_xx->get_height()); const int height = image->get_width();
BLI_assert(image->get_width() == s_yy->get_width());
BLI_assert(image->get_height() == s_yy->get_height()); BLI_assert(width == s_xx->get_width());
BLI_assert(image->get_width() == s_xy->get_width()); BLI_assert(height == s_xx->get_height());
BLI_assert(image->get_height() == s_xy->get_height()); BLI_assert(width == s_yy->get_width());
BLI_assert(height == s_yy->get_height());
BLI_assert(width == s_xy->get_width());
BLI_assert(height == s_xy->get_height());
/* Values recommended by authors in original paper. */ /* Values recommended by authors in original paper. */
const float angle = 2.0 * M_PI / n_div_; const float angle = 2.0 * M_PI / n_div_;
@ -240,8 +246,8 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
continue; continue;
/* Rotate and scale the kernel. This is the "anisotropic" part. */ /* Rotate and scale the kernel. This is the "anisotropic" part. */
int dx2 = (int)(sx * (cos(theta) * dx - sin(theta) * dy)); int dx2 = int(sx * (cos(theta) * dx - sin(theta) * dy));
int dy2 = (int)(sy * (sin(theta) * dx + cos(theta) * dy)); int dy2 = int(sy * (sin(theta) * dx + cos(theta) * dy));
int xx = x + dx2; int xx = x + dx2;
int yy = y + dy2; int yy = y + dy2;
@ -249,23 +255,23 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
if (xx < 0) { if (xx < 0) {
xx = 0; xx = 0;
} }
if (xx >= image->get_width()) { if (xx >= width) {
xx = image->get_width() - 1; xx = width - 1;
} }
if (yy < 0) { if (yy < 0) {
yy = 0; yy = 0;
} }
if (yy >= image->get_height()) { if (yy >= height) {
yy = image->get_height() - 1; yy = height - 1;
} }
double ddx2 = (double)dx2; const double ddx2 = double(dx2);
double ddy2 = (double)dy2; const double ddy2 = double(dy2);
double theta = atan2(ddy2, ddx2) + M_PI; const double theta = atan2(ddy2, ddx2) + M_PI;
int t = static_cast<int>(floor(theta / angle)) % n_div_; const int t = int(floor(theta / angle)) % n_div_;
double d2 = dx2 * dx2 + dy2 * dy2; double d2 = dx2 * dx2 + dy2 * dy2;
double g = exp(-d2 / (2.0 * kernel_size_)); double g = exp(-d2 / (2.0 * kernel_size_));
double v = image->get_value(xx, yy, ch); const double v = image->get_value(xx, yy, ch);
float color[4]; float color[4];
image->read_elem(xx, yy, color); image->read_elem(xx, yy, color);
/* todo(zazizizou): only compute lum once per region */ /* todo(zazizizou): only compute lum once per region */
@ -282,9 +288,10 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
double de = 0.0; double de = 0.0;
double nu = 0.0; double nu = 0.0;
for (int i = 0; i < n_div_; i++) { for (int i = 0; i < n_div_; i++) {
mean[i] = weight[i] != 0 ? mean[i] / weight[i] : 0.0; double weight_inv = 1.0 / weight[i];
sum[i] = weight[i] != 0 ? sum[i] / weight[i] : 0.0; mean[i] = weight[i] != 0 ? mean[i] * weight_inv : 0.0;
var[i] = weight[i] != 0 ? var[i] / weight[i] : 0.0; sum[i] = weight[i] != 0 ? sum[i] * weight_inv : 0.0;
var[i] = weight[i] != 0 ? var[i] * weight_inv : 0.0;
var[i] = var[i] - sum[i] * sum[i]; var[i] = var[i] - sum[i] * sum[i];
var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON; var[i] = var[i] > FLT_EPSILON ? sqrt(var[i]) : FLT_EPSILON;
double w = powf(var[i], -q); double w = powf(var[i], -q);
@ -294,7 +301,6 @@ void KuwaharaAnisotropicOperation::update_memory_buffer_partial(MemoryBuffer *ou
} }
double val = nu > EPS ? de / nu : 0.0; double val = nu > EPS ? de / nu : 0.0;
CLAMP_MAX(val, 1.0f);
it.out[ch] = val; it.out[ch] = val;
} }

View File

@ -9423,6 +9423,7 @@ static void def_cmp_kuwahara(StructRNA *srna)
prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE); prop = RNA_def_property(srna, "size", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "size"); RNA_def_property_int_sdna(prop, NULL, "size");
RNA_def_property_range(prop, 1.0, 100.0);
RNA_def_property_ui_range(prop, 1, 100, 1, -1); RNA_def_property_ui_range(prop, 1, 100, 1, -1);
RNA_def_property_ui_text( RNA_def_property_ui_text(
prop, "Size", "Size of filter. Larger values give stronger stylized effect"); prop, "Size", "Size of filter. Larger values give stronger stylized effect");
@ -9436,6 +9437,7 @@ static void def_cmp_kuwahara(StructRNA *srna)
prop = RNA_def_property(srna, "smoothing", PROP_INT, PROP_NONE); prop = RNA_def_property(srna, "smoothing", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "smoothing"); RNA_def_property_int_sdna(prop, NULL, "smoothing");
RNA_def_property_range(prop, 0.0, 50.0);
RNA_def_property_ui_range(prop, 0, 50, 1, -1); RNA_def_property_ui_range(prop, 0, 50, 1, -1);
RNA_def_property_ui_text(prop, RNA_def_property_ui_text(prop,
"Smoothing", "Smoothing",