Blender V4.3
COM_FastGaussianBlurOperation.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <climits>
6
8
9namespace blender::compositor {
10
16
18{
20
21 /* Compute the Gaussian sigma from the radius, where the radius is in pixels. Blender's filter is
22 * truncated at |x| > 3 * sigma as can be seen in the R_FILTER_GAUSS case of the RE_filter_value
23 * function, so we divide by three to get the approximate sigma value. */
24 sigma_x_ = data_.sizex * size_ / 3.0f;
25 sigma_y_ = data_.sizey * size_ / 3.0f;
26}
27
32
34{
35 if (iirgaus_) {
36 delete iirgaus_;
37 iirgaus_ = nullptr;
38 }
39}
40
41void FastGaussianBlurOperation::set_size(int size_x, int size_y)
42{
43 /* TODO: there should be a better way to use the operation without knowing specifics of the blur
44 * node (i.e. data_). We could use factory pattern to solve this problem. */
45 data_.sizex = size_x;
46 data_.sizey = size_y;
47 sizeavailable_ = true;
48}
49
51{
53 double q, q2, sc, cf[4], tsM[9], tsu[3], tsv[3];
54 double *X, *Y, *W;
55 const uint src_width = src->get_width();
56 const uint src_height = src->get_height();
57 uint x, y, src_dim_max;
58 uint i;
59 float *buffer = src->get_buffer();
60 const uint8_t num_channels = src->get_num_channels();
61
62 /* <0.5 not valid, though can have a possibly useful sort of sharpening effect. */
63 if (sigma < 0.5f) {
64 return;
65 }
66
67 if ((xy < 1) || (xy > 3)) {
68 xy = 3;
69 }
70
71 /* XXX The YVV macro defined below explicitly expects sources of at least 3x3 pixels,
72 * so just skipping blur along faulty direction if src's def is below that limit! */
73 if (src_width < 3) {
74 xy &= ~1;
75 }
76 if (src_height < 3) {
77 xy &= ~2;
78 }
79 if (xy < 1) {
80 return;
81 }
82
83 /* See "Recursive Gabor Filtering" by Young/VanVliet
84 * all factors here in double-precision.
85 * Required, because for single-precision floating point seems to blow up if `sigma > ~200`. */
86 if (sigma >= 3.556f) {
87 q = 0.9804f * (sigma - 3.556f) + 2.5091f;
88 }
89 else { /* `sigma >= 0.5`. */
90 q = (0.0561f * sigma + 0.5784f) * sigma - 0.2568f;
91 }
92 q2 = q * q;
93 sc = (1.1668 + q) * (3.203729649 + (2.21566 + q) * q);
94 /* No gabor filtering here, so no complex multiplies, just the regular coefficients.
95 * all negated here, so as not to have to recalc Triggs/Sdika matrix. */
96 cf[1] = q * (5.788961737 + (6.76492 + 3.0 * q) * q) / sc;
97 cf[2] = -q2 * (3.38246 + 3.0 * q) / sc;
98 /* 0 & 3 unchanged. */
99 cf[3] = q2 * q / sc;
100 cf[0] = 1.0 - cf[1] - cf[2] - cf[3];
101
102 /* Triggs/Sdika border corrections,
103 * it seems to work, not entirely sure if it is actually totally correct,
104 * Besides J.M.Geusebroek's `anigauss.c` (see http://www.science.uva.nl/~mark),
105 * found one other implementation by Cristoph Lampert,
106 * but neither seem to be quite the same, result seems to be ok so far anyway.
107 * Extra scale factor here to not have to do it in filter,
108 * though maybe this had something to with the precision errors */
109 sc = cf[0] / ((1.0 + cf[1] - cf[2] + cf[3]) * (1.0 - cf[1] - cf[2] - cf[3]) *
110 (1.0 + cf[2] + (cf[1] - cf[3]) * cf[3]));
111 tsM[0] = sc * (-cf[3] * cf[1] + 1.0 - cf[3] * cf[3] - cf[2]);
112 tsM[1] = sc * ((cf[3] + cf[1]) * (cf[2] + cf[3] * cf[1]));
113 tsM[2] = sc * (cf[3] * (cf[1] + cf[3] * cf[2]));
114 tsM[3] = sc * (cf[1] + cf[3] * cf[2]);
115 tsM[4] = sc * (-(cf[2] - 1.0) * (cf[2] + cf[3] * cf[1]));
116 tsM[5] = sc * (-(cf[3] * cf[1] + cf[3] * cf[3] + cf[2] - 1.0) * cf[3]);
117 tsM[6] = sc * (cf[3] * cf[1] + cf[2] + cf[1] * cf[1] - cf[2] * cf[2]);
118 tsM[7] = sc * (cf[1] * cf[2] + cf[3] * cf[2] * cf[2] - cf[1] * cf[3] * cf[3] -
119 cf[3] * cf[3] * cf[3] - cf[3] * cf[2] + cf[3]);
120 tsM[8] = sc * (cf[3] * (cf[1] + cf[3] * cf[2]));
121
122#define YVV(L) \
123 { \
124 W[0] = cf[0] * X[0] + cf[1] * X[0] + cf[2] * X[0] + cf[3] * X[0]; \
125 W[1] = cf[0] * X[1] + cf[1] * W[0] + cf[2] * X[0] + cf[3] * X[0]; \
126 W[2] = cf[0] * X[2] + cf[1] * W[1] + cf[2] * W[0] + cf[3] * X[0]; \
127 for (i = 3; i < L; i++) { \
128 W[i] = cf[0] * X[i] + cf[1] * W[i - 1] + cf[2] * W[i - 2] + cf[3] * W[i - 3]; \
129 } \
130 tsu[0] = W[L - 1] - X[L - 1]; \
131 tsu[1] = W[L - 2] - X[L - 1]; \
132 tsu[2] = W[L - 3] - X[L - 1]; \
133 tsv[0] = tsM[0] * tsu[0] + tsM[1] * tsu[1] + tsM[2] * tsu[2] + X[L - 1]; \
134 tsv[1] = tsM[3] * tsu[0] + tsM[4] * tsu[1] + tsM[5] * tsu[2] + X[L - 1]; \
135 tsv[2] = tsM[6] * tsu[0] + tsM[7] * tsu[1] + tsM[8] * tsu[2] + X[L - 1]; \
136 Y[L - 1] = cf[0] * W[L - 1] + cf[1] * tsv[0] + cf[2] * tsv[1] + cf[3] * tsv[2]; \
137 Y[L - 2] = cf[0] * W[L - 2] + cf[1] * Y[L - 1] + cf[2] * tsv[0] + cf[3] * tsv[1]; \
138 Y[L - 3] = cf[0] * W[L - 3] + cf[1] * Y[L - 2] + cf[2] * Y[L - 1] + cf[3] * tsv[0]; \
139 /* `i != UINT_MAX` is really `i >= 0`, but necessary for `uint` wrapping. */ \
140 for (i = L - 4; i != UINT_MAX; i--) { \
141 Y[i] = cf[0] * W[i] + cf[1] * Y[i + 1] + cf[2] * Y[i + 2] + cf[3] * Y[i + 3]; \
142 } \
143 } \
144 (void)0
145
146 /* Intermediate buffers. */
147 src_dim_max = std::max(src_width, src_height);
148 X = (double *)MEM_callocN(src_dim_max * sizeof(double), "IIR_gauss X buf");
149 Y = (double *)MEM_callocN(src_dim_max * sizeof(double), "IIR_gauss Y buf");
150 W = (double *)MEM_callocN(src_dim_max * sizeof(double), "IIR_gauss W buf");
151 if (xy & 1) { /* H. */
152 int offset;
153 for (y = 0; y < src_height; y++) {
154 const int yx = y * src_width;
155 offset = yx * num_channels + chan;
156 for (x = 0; x < src_width; x++) {
157 X[x] = buffer[offset];
158 offset += num_channels;
159 }
160 YVV(src_width);
161 offset = yx * num_channels + chan;
162 for (x = 0; x < src_width; x++) {
163 buffer[offset] = Y[x];
164 offset += num_channels;
165 }
166 }
167 }
168 if (xy & 2) { /* V. */
169 int offset;
170 const int add = src_width * num_channels;
171
172 for (x = 0; x < src_width; x++) {
173 offset = x * num_channels + chan;
174 for (y = 0; y < src_height; y++) {
175 X[y] = buffer[offset];
176 offset += add;
177 }
178 YVV(src_height);
179 offset = x * num_channels + chan;
180 for (y = 0; y < src_height; y++) {
181 buffer[offset] = Y[y];
182 offset += add;
183 }
184 }
185 }
186
187 MEM_freeN(X);
188 MEM_freeN(W);
189 MEM_freeN(Y);
190#undef YVV
191}
192
194 const rcti &output_area,
195 rcti &r_input_area)
196{
197 switch (input_idx) {
199 r_input_area = this->get_canvas();
200 break;
201 default:
202 BlurBaseOperation::get_area_of_interest(input_idx, output_area, r_input_area);
203 return;
204 }
205}
206
208 const rcti &area,
210{
211 /* TODO(manzanilla): Add a render test and make #IIR_gauss multi-threaded with support for
212 * an output buffer. */
213 const MemoryBuffer *input = inputs[IMAGE_INPUT_INDEX];
214 MemoryBuffer *image = nullptr;
215 const bool is_full_output = BLI_rcti_compare(&output->get_rect(), &area);
216 if (is_full_output) {
217 image = output;
218 }
219 else {
220 image = new MemoryBuffer(get_output_socket()->get_data_type(), area);
221 }
222 image->copy_from(input, area);
223
224 if ((sigma_x_ == sigma_y_) && (sigma_x_ > 0.0f)) {
225 for (const int c : IndexRange(COM_DATA_TYPE_COLOR_CHANNELS)) {
226 IIR_gauss(image, sigma_x_, c, 3);
227 }
228 }
229 else {
230 if (sigma_x_ > 0.0f) {
231 for (const int c : IndexRange(COM_DATA_TYPE_COLOR_CHANNELS)) {
232 IIR_gauss(image, sigma_x_, c, 1);
233 }
234 }
235 if (sigma_y_ > 0.0f) {
236 for (const int c : IndexRange(COM_DATA_TYPE_COLOR_CHANNELS)) {
237 IIR_gauss(image, sigma_y_, c, 2);
238 }
239 }
240 }
241
242 if (!is_full_output) {
243 output->copy_from(image, area);
244 delete image;
245 }
246}
247
248} // namespace blender::compositor
#define BLI_assert(a)
Definition BLI_assert.h:50
bool BLI_rcti_compare(const struct rcti *rect_a, const struct rcti *rect_b)
unsigned int uint
#define YVV(L)
@ R_FILTER_FAST_GAUSS
#define output
virtual void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override
Get input operation area being read by this operation on rendering given output area.
void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override
Get input operation area being read by this operation on rendering given output area.
static void IIR_gauss(MemoryBuffer *src, float sigma, unsigned int channel, unsigned int xy)
void update_memory_buffer_started(MemoryBuffer *output, const rcti &area, Span< MemoryBuffer * > inputs) override
a MemoryBuffer contains access to the data
const int get_width() const
get the width of this MemoryBuffer
const int get_height() const
get the height of this MemoryBuffer
float * get_buffer()
get the data of this MemoryBuffer
NodeOperationOutput * get_output_socket(unsigned int index=0)
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
DataType
possible data types for sockets
Definition COM_defines.h:21
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
static void add(blender::Map< std::string, std::string > &messages, Message &msg)
Definition msgfmt.cc:227
constexpr int COM_DATA_TYPE_COLOR_CHANNELS
Definition COM_defines.h:58
unsigned char uint8_t
Definition stdint.h:78
int xy[2]
Definition wm_draw.cc:170