Compositor: Re-write Pixelate node for CPU compositor
The old implementation was a simple rounding operation and was not implemented for full-frame compositor. The issue with the old implementation is that it will not give satisfactory results for images with high frequency details, including cases when is used for a preview on Cycles render with low number of samples. Additionally, when applied on animated footage it produces very noisy result. The new algorithm uses an explicit pixel size setting, which allows the node to be used on its own, without need to have scale-down and scale-up nodes. It also uses neighbour averaging, which produces better looking result during animation and noisy input images. The old tiled compositor setup will render without changes with this change. This commit does not include modifications in the GPU compositor implementation. Ref #88150 Pull Request: https://projects.blender.org/blender/blender/pulls/117223
This commit is contained in:
parent
f9c3d51322
commit
baab14ca38
|
@ -16,18 +16,16 @@ PixelateNode::PixelateNode(bNode *editor_node) : Node(editor_node)
|
|||
void PixelateNode::convert_to_operations(NodeConverter &converter,
|
||||
const CompositorContext & /*context*/) const
|
||||
{
|
||||
const bNode *editor_node = this->get_bnode();
|
||||
|
||||
NodeInput *input_socket = this->get_input_socket(0);
|
||||
NodeOutput *output_socket = this->get_output_socket(0);
|
||||
DataType datatype = input_socket->get_data_type();
|
||||
|
||||
if (input_socket->is_linked()) {
|
||||
NodeOutput *link = input_socket->get_link();
|
||||
datatype = link->get_data_type();
|
||||
}
|
||||
|
||||
PixelateOperation *operation = new PixelateOperation(datatype);
|
||||
PixelateOperation *operation = new PixelateOperation();
|
||||
converter.add_operation(operation);
|
||||
|
||||
operation->set_pixel_size(editor_node->custom1);
|
||||
|
||||
converter.map_input_socket(input_socket, operation->get_input_socket(0));
|
||||
converter.map_output_socket(output_socket, operation->get_output_socket(0));
|
||||
}
|
||||
|
|
|
@ -4,16 +4,20 @@
|
|||
|
||||
#include "COM_PixelateOperation.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
PixelateOperation::PixelateOperation(DataType data_type)
|
||||
PixelateOperation::PixelateOperation()
|
||||
{
|
||||
this->add_input_socket(data_type);
|
||||
this->add_output_socket(data_type);
|
||||
this->add_input_socket(DataType::Color);
|
||||
this->add_output_socket(DataType::Color);
|
||||
this->set_canvas_input_index(0);
|
||||
input_operation_ = nullptr;
|
||||
|
||||
flags_.can_be_constant = true;
|
||||
|
||||
pixel_size_ = 1;
|
||||
}
|
||||
|
||||
void PixelateOperation::init_execution()
|
||||
|
@ -26,14 +30,97 @@ void PixelateOperation::deinit_execution()
|
|||
input_operation_ = nullptr;
|
||||
}
|
||||
|
||||
void PixelateOperation::execute_pixel_sampled(float output[4],
|
||||
float x,
|
||||
float y,
|
||||
PixelSampler sampler)
|
||||
bool PixelateOperation::determine_depending_area_of_interest(rcti *input,
|
||||
ReadBufferOperation *read_operation,
|
||||
rcti *output)
|
||||
{
|
||||
float nx = round(x);
|
||||
float ny = round(y);
|
||||
input_operation_->read_sampled(output, nx, ny, sampler);
|
||||
rcti new_input;
|
||||
|
||||
new_input.xmin = input->xmin;
|
||||
new_input.xmax = input->xmax + pixel_size_ - 1;
|
||||
new_input.ymax = input->ymax;
|
||||
new_input.ymax = input->ymax + pixel_size_ - 1;
|
||||
|
||||
return NodeOperation::determine_depending_area_of_interest(&new_input, read_operation, output);
|
||||
}
|
||||
|
||||
void PixelateOperation::execute_pixel_sampled(float output[4],
|
||||
const float x,
|
||||
const float y,
|
||||
const PixelSampler sampler)
|
||||
{
|
||||
const int width = this->get_width();
|
||||
const int height = this->get_height();
|
||||
|
||||
const int x_start = (int(x) / pixel_size_) * pixel_size_;
|
||||
const int y_start = (int(y) / pixel_size_) * pixel_size_;
|
||||
|
||||
const int x_end = std::min(x_start + pixel_size_, width);
|
||||
const int y_end = std::min(y_start + pixel_size_, height);
|
||||
|
||||
float4 color_accum(0, 0, 0, 0);
|
||||
|
||||
for (int iy = y_start; iy < y_end; ++iy) {
|
||||
for (int ix = x_start; ix < x_end; ++ix) {
|
||||
float4 color;
|
||||
input_operation_->read_sampled(color, ix, iy, sampler);
|
||||
|
||||
color_accum += color;
|
||||
}
|
||||
}
|
||||
|
||||
const int scale = (x_end - x_start) * (y_end - y_start);
|
||||
|
||||
copy_v4_v4(output, color_accum / float(scale));
|
||||
}
|
||||
|
||||
void PixelateOperation::get_area_of_interest(const int /*input_idx*/,
|
||||
const rcti &output_area,
|
||||
rcti &r_input_area)
|
||||
{
|
||||
r_input_area.xmin = output_area.xmin;
|
||||
r_input_area.ymin = output_area.ymin;
|
||||
|
||||
r_input_area.xmax = output_area.xmax + pixel_size_ - 1;
|
||||
r_input_area.ymax = output_area.ymax + pixel_size_ - 1;
|
||||
}
|
||||
|
||||
void PixelateOperation::update_memory_buffer_partial(MemoryBuffer *output,
|
||||
const rcti &area,
|
||||
Span<MemoryBuffer *> inputs)
|
||||
{
|
||||
MemoryBuffer *image = inputs[0];
|
||||
|
||||
if (image->is_a_single_elem()) {
|
||||
copy_v4_v4(output->get_elem(0, 0), image->get_elem(0, 0));
|
||||
return;
|
||||
}
|
||||
|
||||
const int width = image->get_width();
|
||||
const int height = image->get_height();
|
||||
|
||||
for (BuffersIterator<float> it = output->iterate_with(inputs, area); !it.is_end(); ++it) {
|
||||
const int x_start = (it.x / pixel_size_) * pixel_size_;
|
||||
const int y_start = (it.y / pixel_size_) * pixel_size_;
|
||||
|
||||
const int x_end = std::min(x_start + pixel_size_, width);
|
||||
const int y_end = std::min(y_start + pixel_size_, height);
|
||||
|
||||
float4 color_accum(0, 0, 0, 0);
|
||||
|
||||
for (int y = y_start; y < y_end; ++y) {
|
||||
for (int x = x_start; x < x_end; ++x) {
|
||||
float4 color;
|
||||
image->read_elem(x, y, color);
|
||||
|
||||
color_accum += color;
|
||||
}
|
||||
}
|
||||
|
||||
const int scale = (x_end - x_start) * (y_end - y_start);
|
||||
|
||||
copy_v4_v4(it.out, color_accum / float(scale));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "COM_NodeOperation.h"
|
||||
#include "COM_MultiThreadedOperation.h"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
|
@ -15,19 +15,26 @@ namespace blender::compositor {
|
|||
* For some setups you don want this.
|
||||
* This operation will remove the sub-pixel accuracy
|
||||
*/
|
||||
class PixelateOperation : public NodeOperation {
|
||||
class PixelateOperation : public MultiThreadedOperation {
|
||||
private:
|
||||
/**
|
||||
* \brief cached reference to the input operation
|
||||
*/
|
||||
SocketReader *input_operation_;
|
||||
|
||||
int pixel_size_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* \brief PixelateOperation
|
||||
* \param data_type: the datatype to create this operator for (saves datatype conversions)
|
||||
*/
|
||||
PixelateOperation(DataType data_type);
|
||||
PixelateOperation();
|
||||
|
||||
void set_pixel_size(const int pixel_size)
|
||||
{
|
||||
if (pixel_size < 1) {
|
||||
pixel_size_ = 1;
|
||||
return;
|
||||
}
|
||||
pixel_size_ = pixel_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief initialization of the execution
|
||||
|
@ -39,6 +46,10 @@ class PixelateOperation : public NodeOperation {
|
|||
*/
|
||||
void deinit_execution() override;
|
||||
|
||||
bool determine_depending_area_of_interest(rcti *input,
|
||||
ReadBufferOperation *read_operation,
|
||||
rcti *output) override;
|
||||
|
||||
/**
|
||||
* \brief execute_pixel
|
||||
* \param output: result
|
||||
|
@ -47,6 +58,11 @@ class PixelateOperation : public NodeOperation {
|
|||
* \param sampler: sampler
|
||||
*/
|
||||
void execute_pixel_sampled(float output[4], float x, float y, PixelSampler sampler) override;
|
||||
|
||||
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override;
|
||||
void update_memory_buffer_partial(MemoryBuffer *output,
|
||||
const rcti &area,
|
||||
Span<MemoryBuffer *> inputs) override;
|
||||
};
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
|
|
@ -8305,6 +8305,21 @@ static void def_cmp_trackpos(StructRNA *srna)
|
|||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
}
|
||||
|
||||
static void def_cmp_pixelate(StructRNA *srna)
|
||||
{
|
||||
PropertyRNA *prop;
|
||||
|
||||
/* The range of the pixel size is chosen so that it is positive and above zero, and also does not
|
||||
* exceed the underlying int16_t type. The size limit matches the maximum size used by blur
|
||||
* nodes. */
|
||||
prop = RNA_def_property(srna, "pixel_size", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "custom1");
|
||||
RNA_def_property_range(prop, 1, 2048);
|
||||
RNA_def_property_int_default(prop, 1);
|
||||
RNA_def_property_ui_text(prop, "Pixel Size", "Pixel size of the output image");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
|
||||
}
|
||||
|
||||
static void def_cmp_translate(StructRNA *srna)
|
||||
{
|
||||
static const EnumPropertyItem translate_items[] = {
|
||||
|
|
|
@ -207,7 +207,7 @@ DefNode(CompositorNode, CMP_NODE_MASK, def_cmp_mask, "MASK",
|
|||
DefNode(CompositorNode, CMP_NODE_KEYINGSCREEN, def_cmp_keyingscreen, "KEYINGSCREEN", KeyingScreen, "Keying Screen", "" )
|
||||
DefNode(CompositorNode, CMP_NODE_KEYING, def_cmp_keying, "KEYING", Keying, "Keying", "" )
|
||||
DefNode(CompositorNode, CMP_NODE_TRACKPOS, def_cmp_trackpos, "TRACKPOS", TrackPos, "Track Position", "" )
|
||||
DefNode(CompositorNode, CMP_NODE_PIXELATE, 0, "PIXELATE", Pixelate, "Pixelate", "" )
|
||||
DefNode(CompositorNode, CMP_NODE_PIXELATE, def_cmp_pixelate, "PIXELATE", Pixelate, "Pixelate", "" )
|
||||
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", "" )
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
/* **************** Pixelate ******************** */
|
||||
|
@ -22,6 +25,16 @@ static void cmp_node_pixelate_declare(NodeDeclarationBuilder &b)
|
|||
b.add_output<decl::Color>("Color");
|
||||
}
|
||||
|
||||
static void node_composit_init_pixelate(bNodeTree * /*ntree*/, bNode *node)
|
||||
{
|
||||
node->custom1 = 1;
|
||||
}
|
||||
|
||||
static void node_composit_buts_pixelate(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
|
||||
{
|
||||
uiItemR(layout, ptr, "pixel_size", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
|
||||
}
|
||||
|
||||
using namespace blender::realtime_compositor;
|
||||
|
||||
class PixelateOperation : public NodeOperation {
|
||||
|
@ -78,6 +91,8 @@ void register_node_type_cmp_pixelate()
|
|||
|
||||
cmp_node_type_base(&ntype, CMP_NODE_PIXELATE, "Pixelate", NODE_CLASS_OP_FILTER);
|
||||
ntype.declare = file_ns::cmp_node_pixelate_declare;
|
||||
ntype.draw_buttons = file_ns::node_composit_buts_pixelate;
|
||||
ntype.initfunc = file_ns::node_composit_init_pixelate;
|
||||
ntype.get_compositor_operation = file_ns::get_compositor_operation;
|
||||
|
||||
nodeRegisterType(&ntype);
|
||||
|
|
Loading…
Reference in New Issue