Files
test/source/blender/compositor/operations/COM_KuwaharaAnisotropicStructureTensorOperation.cc
Omar Emara 9ef2310e5f Realtime Compositor: Implement Anisotropic Kuwahara
This patch implements the Anisotropic Kuwahara filter for the Realtime
compositor and replaces the existing CPU implementation with a new one to be
compatible with the GPU implementation. The implementation is based on three
papers on Anisotropic Kuwahara filtering, presented and detailed in the code.

The new implementation exposes two extra parameters that control the sharpness
and directionality of the output, giving more artistic freedom.

While the implementation is different from the existing CPU implementation, it
is a higher quality one that is also faster and conforms better to the methods
described in the papers.

Examples can be seen in the pull request description.

Pull Request: https://projects.blender.org/blender/blender/pulls/110786
2023-08-17 16:58:36 +02:00

156 lines
7.3 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "COM_KuwaharaAnisotropicStructureTensorOperation.h"
#include "BLI_math_base.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
namespace blender::compositor {
KuwaharaAnisotropicStructureTensorOperation::KuwaharaAnisotropicStructureTensorOperation()
{
this->add_input_socket(DataType::Color);
this->add_output_socket(DataType::Color);
this->flags_.is_fullframe_operation = true;
}
void KuwaharaAnisotropicStructureTensorOperation::init_execution()
{
image_reader_ = this->get_input_socket_reader(0);
}
void KuwaharaAnisotropicStructureTensorOperation::deinit_execution()
{
image_reader_ = nullptr;
}
/* Computes the structure tensor of the image using a Dirac delta window function as described in
* section "3.2 Local Structure Estimation" of the paper:
*
* Kyprianidis, Jan Eric. "Image and video abstraction by multi-scale anisotropic Kuwahara
* filtering." 2011.
*
* The structure tensor should then be smoothed using a Gaussian function to eliminate high
* frequency details. */
void KuwaharaAnisotropicStructureTensorOperation::execute_pixel_sampled(float output[4],
float x_float,
float y_float,
PixelSampler /* sampler */)
{
using math::max, math::min, math::dot;
const int x = x_float;
const int y = y_float;
const int width = this->get_width();
const int height = this->get_height();
/* The weight kernels of the filter optimized for rotational symmetry described in section "3.2.1
* Gradient Calculation". */
const float corner_weight = 0.182f;
const float center_weight = 1.0f - 2.0f * corner_weight;
float4 input_color;
float3 x_partial_derivative = float3(0.0f);
image_reader_->read(input_color, max(0, x - 1), min(height - 1, y + 1), nullptr);
x_partial_derivative += input_color.xyz() * -corner_weight;
image_reader_->read(input_color, max(0, x - 1), y, nullptr);
x_partial_derivative += input_color.xyz() * -center_weight;
image_reader_->read(input_color, max(0, x - 1), max(0, y - 1), nullptr);
x_partial_derivative += input_color.xyz() * -corner_weight;
image_reader_->read(input_color, min(width, x + 1), min(height - 1, y + 1), nullptr);
x_partial_derivative += input_color.xyz() * corner_weight;
image_reader_->read(input_color, min(width, x + 1), y, nullptr);
x_partial_derivative += input_color.xyz() * center_weight;
image_reader_->read(input_color, min(width, x + 1), max(0, y - 1), nullptr);
x_partial_derivative += input_color.xyz() * corner_weight;
float3 y_partial_derivative = float3(0.0f);
image_reader_->read(input_color, max(0, x - 1), min(height - 1, y + 1), nullptr);
y_partial_derivative += input_color.xyz() * corner_weight;
image_reader_->read(input_color, x, min(height - 1, y + 1), nullptr);
y_partial_derivative += input_color.xyz() * center_weight;
image_reader_->read(input_color, min(width, x + 1), min(height - 1, y + 1), nullptr);
y_partial_derivative += input_color.xyz() * corner_weight;
image_reader_->read(input_color, max(0, x - 1), max(0, y - 1), nullptr);
y_partial_derivative += input_color.xyz() * -corner_weight;
image_reader_->read(input_color, x, max(0, y - 1), nullptr);
y_partial_derivative += input_color.xyz() * -center_weight;
image_reader_->read(input_color, min(width, x + 1), max(0, y - 1), nullptr);
y_partial_derivative += input_color.xyz() * -corner_weight;
/* We encode the structure tensor in a float4 using a column major storage order. */
float4 structure_tensor = float4(dot(x_partial_derivative, x_partial_derivative),
dot(x_partial_derivative, y_partial_derivative),
dot(x_partial_derivative, y_partial_derivative),
dot(y_partial_derivative, y_partial_derivative));
copy_v4_v4(output, structure_tensor);
}
/* Computes the structure tensor of the image using a Dirac delta window function as described in
* section "3.2 Local Structure Estimation" of the paper:
*
* Kyprianidis, Jan Eric. "Image and video abstraction by multi-scale anisotropic Kuwahara
* filtering." 2011.
*
* The structure tensor should then be smoothed using a Gaussian function to eliminate high
* frequency details. */
void KuwaharaAnisotropicStructureTensorOperation::update_memory_buffer_partial(
MemoryBuffer *output, const rcti &area, Span<MemoryBuffer *> inputs)
{
using math::max, math::min, math::dot;
MemoryBuffer *image = inputs[0];
const int width = image->get_width();
const int height = image->get_height();
/* The weight kernels of the filter optimized for rotational symmetry described in section
* "3.2.1 Gradient Calculation". */
const float corner_weight = 0.182f;
const float center_weight = 1.0f - 2.0f * corner_weight;
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
const int x = it.x;
const int y = it.y;
float3 input_color;
float3 x_partial_derivative = float3(0.0f);
input_color = float3(image->get_elem(max(0, x - 1), min(height - 1, y + 1)));
x_partial_derivative += input_color * -corner_weight;
input_color = float3(image->get_elem(max(0, x - 1), y));
x_partial_derivative += input_color * -center_weight;
input_color = float3(image->get_elem(max(0, x - 1), max(0, y - 1)));
x_partial_derivative += input_color * -corner_weight;
input_color = float3(image->get_elem(min(width - 1, x + 1), min(height - 1, y + 1)));
x_partial_derivative += input_color * corner_weight;
input_color = float3(image->get_elem(min(width - 1, x + 1), y));
x_partial_derivative += input_color * center_weight;
input_color = float3(image->get_elem(min(width - 1, x + 1), max(0, y - 1)));
x_partial_derivative += input_color * corner_weight;
float3 y_partial_derivative = float3(0.0f);
input_color = float3(image->get_elem(max(0, x - 1), min(height - 1, y + 1)));
y_partial_derivative += input_color * corner_weight;
input_color = float3(image->get_elem(x, min(height - 1, y + 1)));
y_partial_derivative += input_color * center_weight;
input_color = float3(image->get_elem(min(width - 1, x + 1), min(height - 1, y + 1)));
y_partial_derivative += input_color * corner_weight;
input_color = float3(image->get_elem(max(0, x - 1), max(0, y - 1)));
y_partial_derivative += input_color * -corner_weight;
input_color = float3(image->get_elem(x, max(0, y - 1)));
y_partial_derivative += input_color * -center_weight;
input_color = float3(image->get_elem(min(width - 1, x + 1), max(0, y - 1)));
y_partial_derivative += input_color * -corner_weight;
/* We encode the structure tensor in a float4 using a column major storage order. */
float4 structure_tensor = float4(dot(x_partial_derivative, x_partial_derivative),
dot(x_partial_derivative, y_partial_derivative),
dot(x_partial_derivative, y_partial_derivative),
dot(y_partial_derivative, y_partial_derivative));
copy_v4_v4(it.out, structure_tensor);
}
}
} // namespace blender::compositor