1 /* This program is free software; you can redistribute it and/or
2  * modify it under the terms of the GNU General Public License
3  * as published by the Free Software Foundation; either version 2
4  * of the License, or (at your option) any later version.
5  *
6  * This program is distributed in the hope that it will be useful,
7  * but WITHOUT ANY WARRANTY; without even the implied warranty of
8  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  * GNU General Public License for more details.
10  *
11  * You should have received a copy of the GNU General Public License
12  * along with this program; if not, write to the Free Software Foundation,
13  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14  *
15  * Copyright 2014, Blender Foundation.
16  */
17 
18 #include "MEM_guardedalloc.h"
19 
20 #include "COM_SunBeamsOperation.h"
21 
SunBeamsOperation()22 SunBeamsOperation::SunBeamsOperation() : NodeOperation()
23 {
24   this->addInputSocket(COM_DT_COLOR);
25   this->addOutputSocket(COM_DT_COLOR);
26   this->setResolutionInputSocketIndex(0);
27 
28   this->setComplex(true);
29 }
30 
initExecution()31 void SunBeamsOperation::initExecution()
32 {
33   /* convert to pixels */
34   this->m_source_px[0] = this->m_data.source[0] * this->getWidth();
35   this->m_source_px[1] = this->m_data.source[1] * this->getHeight();
36   this->m_ray_length_px = this->m_data.ray_length * max(this->getWidth(), this->getHeight());
37 }
38 
39 /**
40  * Defines a line accumulator for a specific sector,
41  * given by the four matrix entries that rotate from buffer space into the sector
42  *
43  * (x,y) is used to designate buffer space coordinates
44  * (u,v) is used to designate sector space coordinates
45  *
46  * For a target point (x,y) the sector should be chosen such that
47  *   ``u >= v >= 0``
48  * This removes the need to handle all sorts of special cases.
49  *
50  * Template parameters:
51  * fxu : buffer increment in x for sector u+1
52  * fxv : buffer increment in x for sector v+1
53  * fyu : buffer increment in y for sector u+1
54  * fyv : buffer increment in y for sector v+1
55  */
56 template<int fxu, int fxv, int fyu, int fyv> struct BufferLineAccumulator {
57 
58   /* utility functions implementing the matrix transform to/from sector space */
59 
buffer_to_sectorBufferLineAccumulator60   static inline void buffer_to_sector(const float source[2], int x, int y, int &u, int &v)
61   {
62     int x0 = (int)source[0];
63     int y0 = (int)source[1];
64     x -= x0;
65     y -= y0;
66     u = x * fxu + y * fyu;
67     v = x * fxv + y * fyv;
68   }
69 
buffer_to_sectorBufferLineAccumulator70   static inline void buffer_to_sector(const float source[2], float x, float y, float &u, float &v)
71   {
72     int x0 = (int)source[0];
73     int y0 = (int)source[1];
74     x -= (float)x0;
75     y -= (float)y0;
76     u = x * fxu + y * fyu;
77     v = x * fxv + y * fyv;
78   }
79 
sector_to_bufferBufferLineAccumulator80   static inline void sector_to_buffer(const float source[2], int u, int v, int &x, int &y)
81   {
82     int x0 = (int)source[0];
83     int y0 = (int)source[1];
84     x = x0 + u * fxu + v * fxv;
85     y = y0 + u * fyu + v * fyv;
86   }
87 
sector_to_bufferBufferLineAccumulator88   static inline void sector_to_buffer(const float source[2], float u, float v, float &x, float &y)
89   {
90     int x0 = (int)source[0];
91     int y0 = (int)source[1];
92     x = (float)x0 + u * fxu + v * fxv;
93     y = (float)y0 + u * fyu + v * fyv;
94   }
95 
96   /**
97    * Set up the initial buffer pointer and calculate necessary variables for looping.
98    *
99    * Note that sector space is centered around the "source" point while the loop starts
100    * at dist_min from the target pt. This way the loop can be canceled as soon as it runs
101    * out of the buffer rect, because no pixels further along the line can contribute.
102    *
103    * \param x, y: Start location in the buffer
104    * \param num: Total steps in the loop
105    * \param v, dv: Vertical offset in sector space, for line offset perpendicular to the loop axis
106    */
init_buffer_iteratorBufferLineAccumulator107   static float *init_buffer_iterator(MemoryBuffer *input,
108                                      const float source[2],
109                                      const float co[2],
110                                      float dist_min,
111                                      float dist_max,
112                                      int &x,
113                                      int &y,
114                                      int &num,
115                                      float &v,
116                                      float &dv,
117                                      float &falloff_factor)
118   {
119     float pu, pv;
120     buffer_to_sector(source, co[0], co[1], pu, pv);
121 
122     /* line angle */
123     float tan_phi = pv / pu;
124     float dr = sqrtf(tan_phi * tan_phi + 1.0f);
125     float cos_phi = 1.0f / dr;
126 
127     /* clamp u range to avoid influence of pixels "behind" the source */
128     float umin = max_ff(pu - cos_phi * dist_min, 0.0f);
129     float umax = max_ff(pu - cos_phi * dist_max, 0.0f);
130     v = umin * tan_phi;
131     dv = tan_phi;
132 
133     int start = (int)floorf(umax);
134     int end = (int)ceilf(umin);
135     num = end - start;
136 
137     sector_to_buffer(source, end, (int)ceilf(v), x, y);
138 
139     falloff_factor = dist_max > dist_min ? dr / (float)(dist_max - dist_min) : 0.0f;
140 
141     float *iter = input->getBuffer() + COM_NUM_CHANNELS_COLOR * (x + input->getWidth() * y);
142     return iter;
143   }
144 
145   /**
146    * Perform the actual accumulation along a ray segment from source to pt.
147    * Only pixels within dist_min..dist_max contribute.
148    *
149    * The loop runs backwards(!) over the primary sector space axis u, i.e. increasing distance to
150    * pt. After each step it decrements v by dv < 1, adding a buffer shift when necessary.
151    */
evalBufferLineAccumulator152   static void eval(MemoryBuffer *input,
153                    float output[4],
154                    const float co[2],
155                    const float source[2],
156                    float dist_min,
157                    float dist_max)
158   {
159     rcti rect = *input->getRect();
160     int buffer_width = input->getWidth();
161     int x, y, num;
162     float v, dv;
163     float falloff_factor;
164     float border[4];
165 
166     zero_v4(output);
167 
168     if ((int)(co[0] - source[0]) == 0 && (int)(co[1] - source[1]) == 0) {
169       copy_v4_v4(output,
170                  input->getBuffer() + COM_NUM_CHANNELS_COLOR *
171                                           ((int)source[0] + input->getWidth() * (int)source[1]));
172       return;
173     }
174 
175     /* Initialize the iteration variables. */
176     float *buffer = init_buffer_iterator(
177         input, source, co, dist_min, dist_max, x, y, num, v, dv, falloff_factor);
178     zero_v3(border);
179     border[3] = 1.0f;
180 
181     /* v_local keeps track of when to decrement v (see below) */
182     float v_local = v - floorf(v);
183 
184     for (int i = 0; i < num; i++) {
185       float weight = 1.0f - (float)i * falloff_factor;
186       weight *= weight;
187 
188       /* range check, use last valid color when running beyond the image border */
189       if (x >= rect.xmin && x < rect.xmax && y >= rect.ymin && y < rect.ymax) {
190         madd_v4_v4fl(output, buffer, buffer[3] * weight);
191         /* use as border color in case subsequent pixels are out of bounds */
192         copy_v4_v4(border, buffer);
193       }
194       else {
195         madd_v4_v4fl(output, border, border[3] * weight);
196       }
197 
198       /* TODO implement proper filtering here, see
199        * https://en.wikipedia.org/wiki/Lanczos_resampling
200        * https://en.wikipedia.org/wiki/Sinc_function
201        *
202        * using lanczos with x = distance from the line segment,
203        * normalized to a == 0.5f, could give a good result
204        *
205        * for now just divide equally at the end ...
206        */
207 
208       /* decrement u */
209       x -= fxu;
210       y -= fyu;
211       buffer -= (fxu + fyu * buffer_width) * COM_NUM_CHANNELS_COLOR;
212 
213       /* decrement v (in steps of dv < 1) */
214       v_local -= dv;
215       if (v_local < 0.0f) {
216         v_local += 1.0f;
217 
218         x -= fxv;
219         y -= fyv;
220         buffer -= (fxv + fyv * buffer_width) * COM_NUM_CHANNELS_COLOR;
221       }
222     }
223 
224     /* normalize */
225     if (num > 0) {
226       mul_v4_fl(output, 1.0f / (float)num);
227     }
228   }
229 };
230 
231 /**
232  * Dispatch function which selects an appropriate accumulator based on the sector of the target
233  * point, relative to the source.
234  *
235  * The BufferLineAccumulator defines the actual loop over the buffer, with an efficient inner loop
236  * due to using compile time constants instead of a local matrix variable defining the sector
237  * space.
238  */
accumulate_line(MemoryBuffer * input,float output[4],const float co[2],const float source[2],float dist_min,float dist_max)239 static void accumulate_line(MemoryBuffer *input,
240                             float output[4],
241                             const float co[2],
242                             const float source[2],
243                             float dist_min,
244                             float dist_max)
245 {
246   /* coordinates relative to source */
247   float pt_ofs[2] = {co[0] - source[0], co[1] - source[1]};
248 
249   /* The source sectors are defined like so:
250    *
251    *   \ 3 | 2 /
252    *    \  |  /
253    *   4 \ | / 1
254    *      \|/
255    *  -----------
256    *      /|\
257    *   5 / | \ 8
258    *    /  |  \
259    *   / 6 | 7 \
260    *
261    * The template arguments encode the transformation into "sector space",
262    * by means of rotation/mirroring matrix elements.
263    */
264 
265   if (fabsf(pt_ofs[1]) > fabsf(pt_ofs[0])) {
266     if (pt_ofs[0] > 0.0f) {
267       if (pt_ofs[1] > 0.0f) {
268         /* 2 */
269         BufferLineAccumulator<0, 1, 1, 0>::eval(input, output, co, source, dist_min, dist_max);
270       }
271       else {
272         /* 7 */
273         BufferLineAccumulator<0, 1, -1, 0>::eval(input, output, co, source, dist_min, dist_max);
274       }
275     }
276     else {
277       if (pt_ofs[1] > 0.0f) {
278         /* 3 */
279         BufferLineAccumulator<0, -1, 1, 0>::eval(input, output, co, source, dist_min, dist_max);
280       }
281       else {
282         /* 6 */
283         BufferLineAccumulator<0, -1, -1, 0>::eval(input, output, co, source, dist_min, dist_max);
284       }
285     }
286   }
287   else {
288     if (pt_ofs[0] > 0.0f) {
289       if (pt_ofs[1] > 0.0f) {
290         /* 1 */
291         BufferLineAccumulator<1, 0, 0, 1>::eval(input, output, co, source, dist_min, dist_max);
292       }
293       else {
294         /* 8 */
295         BufferLineAccumulator<1, 0, 0, -1>::eval(input, output, co, source, dist_min, dist_max);
296       }
297     }
298     else {
299       if (pt_ofs[1] > 0.0f) {
300         /* 4 */
301         BufferLineAccumulator<-1, 0, 0, 1>::eval(input, output, co, source, dist_min, dist_max);
302       }
303       else {
304         /* 5 */
305         BufferLineAccumulator<-1, 0, 0, -1>::eval(input, output, co, source, dist_min, dist_max);
306       }
307     }
308   }
309 }
310 
initializeTileData(rcti *)311 void *SunBeamsOperation::initializeTileData(rcti * /*rect*/)
312 {
313   void *buffer = getInputOperation(0)->initializeTileData(NULL);
314   return buffer;
315 }
316 
executePixel(float output[4],int x,int y,void * data)317 void SunBeamsOperation::executePixel(float output[4], int x, int y, void *data)
318 {
319   const float co[2] = {(float)x, (float)y};
320 
321   accumulate_line(
322       (MemoryBuffer *)data, output, co, this->m_source_px, 0.0f, this->m_ray_length_px);
323 }
324 
calc_ray_shift(rcti * rect,float x,float y,const float source[2],float ray_length)325 static void calc_ray_shift(rcti *rect, float x, float y, const float source[2], float ray_length)
326 {
327   float co[2] = {(float)x, (float)y};
328   float dir[2], dist;
329 
330   /* move (x,y) vector toward the source by ray_length distance */
331   sub_v2_v2v2(dir, co, source);
332   dist = normalize_v2(dir);
333   mul_v2_fl(dir, min_ff(dist, ray_length));
334   sub_v2_v2(co, dir);
335 
336   int ico[2] = {(int)co[0], (int)co[1]};
337   BLI_rcti_do_minmax_v(rect, ico);
338 }
339 
determineDependingAreaOfInterest(rcti * input,ReadBufferOperation * readOperation,rcti * output)340 bool SunBeamsOperation::determineDependingAreaOfInterest(rcti *input,
341                                                          ReadBufferOperation *readOperation,
342                                                          rcti *output)
343 {
344   /* Enlarges the rect by moving each corner toward the source.
345    * This is the maximum distance that pixels can influence each other
346    * and gives a rect that contains all possible accumulated pixels.
347    */
348   rcti rect = *input;
349   calc_ray_shift(&rect, input->xmin, input->ymin, this->m_source_px, this->m_ray_length_px);
350   calc_ray_shift(&rect, input->xmin, input->ymax, this->m_source_px, this->m_ray_length_px);
351   calc_ray_shift(&rect, input->xmax, input->ymin, this->m_source_px, this->m_ray_length_px);
352   calc_ray_shift(&rect, input->xmax, input->ymax, this->m_source_px, this->m_ray_length_px);
353 
354   return NodeOperation::determineDependingAreaOfInterest(&rect, readOperation, output);
355 }
356