1 /*
2  */
3 
4 /*
5 
6     Copyright (C) 2014 Ferrero Andrea
7 
8     This program is free software: you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation, either version 3 of the License, or
11     (at your option) any later version.
12 
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     GNU General Public License for more details.
17 
18     You should have received a copy of the GNU General Public License
19     along with this program. If not, see <http://www.gnu.org/licenses/>.
20 
21 
22  */
23 
24 /*
25 
26     These files are distributed with PhotoFlow - http://aferrero2707.github.io/PhotoFlow/
27 
28  */
29 
30 #ifndef PF_DEFRINGE_H
31 #define PF_DEFRINGE_H
32 
33 namespace PF
34 {
35 
36 #define MAGIC_THRESHOLD_COEFF 33.0f
37 
38 const float fib[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 };
39 
40 enum defringe_method_t
41 {
42   MODE_GLOBAL_AVERAGE,
43   MODE_LOCAL_AVERAGE,
44   MODE_STATIC
45 };
46 
47 class DefringePar: public OpParBase
48 {
49   PropertyBase op_mode;
50   Property<float> radius;
51   Property<float> threshold;
52 
53   ProcessorBase* gauss;
54   ProcessorBase* convert2lab;
55   ProcessorBase* convert2input;
56   ProcessorBase* defringe_algo;
57 
58   PF::ICCProfile* in_profile;
59 
60 public:
61   DefringePar();
62 
has_intensity()63   bool has_intensity() { return false; }
64   bool needs_caching();
65 
get_op_mode()66   int get_op_mode() { return op_mode.get_enum_value().first; }
get_radius()67   float get_radius() { return radius.get(); }
get_threshold()68   float get_threshold() { return threshold.get(); }
69 
70   void compute_padding( VipsImage* full_res, unsigned int id, unsigned int level );
71 
72   VipsImage* build(std::vector<VipsImage*>& in, int first,
73       VipsImage* imap, VipsImage* omap,
74       unsigned int& level);
75 };
76 
77 
78 
79 class DefringeAlgoPar: public OpParBase
80 {
81   defringe_method_t op_mode;
82   float sigma;
83   int radius;
84   float threshold;
85 
86   int* xy_avg;
87   int* xy_small;
88   int samples_avg;
89   int samples_small;
90   int dmax, dmax_small;
91 
92 public:
DefringeAlgoPar()93   DefringeAlgoPar(): OpParBase()
94   {
95     radius = threshold = 0;
96     xy_avg = xy_small = NULL;
97     dmax = dmax_small = 0;
98   }
99 
~DefringeAlgoPar()100   ~DefringeAlgoPar()
101   {
102     if( xy_avg ) free( xy_avg );
103     if( xy_small ) free( xy_small );
104   }
105 
get_padding()106   int get_padding() { return( (op_mode == MODE_LOCAL_AVERAGE) ? dmax : dmax_small ); }
107 
108   /* Function to derive the output area from the input area
109    */
transform(const VipsRect * rin,VipsRect * rout,int)110   virtual void transform(const VipsRect* rin, VipsRect* rout, int /*id*/)
111   {
112     int pad = get_padding();
113     rout->left = rin->left+pad;
114     rout->top = rin->top+pad;
115     rout->width = rin->width-pad*2;
116     rout->height = rin->height-pad*2;
117   }
118 
119   /* Function to derive the area to be read from input images,
120      based on the requested output area
121   */
transform_inv(const VipsRect * rout,VipsRect * rin,int)122   virtual void transform_inv(const VipsRect* rout, VipsRect* rin, int /*id*/)
123   {
124     int pad = get_padding();
125     rin->left = rout->left-pad;
126     rin->top = rout->top-pad;
127     rin->width = rout->width+pad*2;
128     rin->height = rout->height+pad*2;
129   }
130 
131 
set_op_mode(defringe_method_t m)132   void set_op_mode( defringe_method_t m ) { op_mode = m; }
get_op_mode()133   defringe_method_t get_op_mode() { return op_mode; }
set_sigma(float s)134   void set_sigma( float s ) { sigma = s; }
get_radius()135   int get_radius() { return radius; }
set_threshold(float t)136   void set_threshold( float t ) { threshold = t; }
get_threshold()137   float get_threshold() { return threshold; }
138 
get_samples_avg()139   int get_samples_avg() { return samples_avg; }
get_xy_avg()140   int* get_xy_avg() { return xy_avg; }
get_samples_small()141   int get_samples_small() { return samples_small; }
get_xy_small()142   int* get_xy_small() { return xy_small; }
143 
fib_latt(int * const x,int * const y,float radius,int step,int idx)144   void fib_latt(int *const x, int *const y, float radius, int step, int idx)
145   {
146     // idx < 1 because division by zero is also a problem in the following line
147     if(idx >= (int)(sizeof(fib) / sizeof(float)) - 1 || idx < 1)
148     {
149       *x = 0;
150       *y = 0;
151       std::cout<<"Fibonacci lattice index wrong/out of bounds in: defringe module"<<std::endl;
152       return;
153     }
154     float px = step / fib[idx], py = step * (fib[idx + 1] / fib[idx]);
155     py -= (int)py;
156     float dx = px * radius, dy = py * radius;
157     *x = round(dx - radius / 2.0);
158     *y = round(dy - radius / 2.0);
159   }
160 
fb_init()161   bool fb_init()
162   {
163     const float sigma_tmp = fmax(0.1f, fabs(sigma));
164     radius = ceil(2.0f * ceilf(sigma_tmp));
165     int samples_wish = (int)(radius * radius);
166     int sampleidx_avg = 0;
167     dmax = 0;
168     dmax_small = 0;
169 
170     // select samples by fibonacci number
171     if(samples_wish > 89) {
172       sampleidx_avg = 12; // 144 samples
173     } else if(samples_wish > 55) {
174       sampleidx_avg = 11; // 89 samples
175     } else if(samples_wish > 34) {
176       sampleidx_avg = 10; // ..you get the idea
177     } else if(samples_wish > 21) {
178       sampleidx_avg = 9;
179     } else if(samples_wish > 13) {
180       sampleidx_avg = 8;
181     } else {
182       // don't use less than 13 samples
183       sampleidx_avg = 7;
184     }
185 
186     const int sampleidx_small = sampleidx_avg - 1;
187     const int small_radius = MAX(radius, 3);
188     const int avg_radius = 24 + radius * 4;
189 
190     samples_small = (int)fib[sampleidx_small];
191     samples_avg = (int)fib[sampleidx_avg];
192 
193     int* tmp;
194 
195     // precompute all required fibonacci lattices:
196     if( xy_avg ) free( xy_avg );
197     xy_avg = (int*)malloc((size_t)2 * sizeof(int) * samples_avg);
198     if (xy_avg != NULL)
199     {
200       tmp = xy_avg;
201       for(int u = 0; u < samples_avg; u++)
202       {
203         int dx, dy;
204         fib_latt(&dx, &dy, avg_radius, u, sampleidx_avg);
205         *tmp++ = dx;
206         *tmp++ = dy;
207         if( dmax < abs(dx) ) dmax = abs(dx);
208         if( dmax < abs(dy) ) dmax = abs(dy);
209       }
210     }
211     else
212     {
213       std::cout<<"Error allocating memory for fibonacci lattice in: defringe module"<<std::endl;
214       return false;
215     }
216 
217     if( xy_small ) free( xy_small );
218     xy_small = (int*)malloc((size_t)2 * sizeof(int) * samples_small);
219     if (xy_small != NULL)
220     {
221       tmp = xy_small;
222       for(int u = 0; u < samples_small; u++)
223       {
224         int dx, dy;
225         fib_latt(&dx, &dy, small_radius, u, sampleidx_small);
226         *tmp++ = dx;
227         *tmp++ = dy;
228         if( dmax_small < abs(dx) ) dmax_small = abs(dx);
229         if( dmax_small < abs(dy) ) dmax_small = abs(dy);
230       }
231     }
232     else
233     {
234       std::cout<<"Error allocating memory for fibonacci lattice in: defringe module"<<std::endl;
235       return false;
236     }
237 
238     return true;
239   }
240 };
241 
242 
243 
244 template < OP_TEMPLATE_DEF >
245 class DefringeProc
246 {
247 public:
render(VipsRegion ** in,int n,int in_first,VipsRegion * imap,VipsRegion * omap,VipsRegion * out,OpParBase * par)248   void render(VipsRegion** in, int n, int in_first,
249       VipsRegion* imap, VipsRegion* omap,
250       VipsRegion* out, OpParBase* par)
251   {
252   }
253 };
254 
255 
256 
257 template < OP_TEMPLATE_DEF >
258 class DefringeAlgoProc
259 {
260 public:
render(VipsRegion ** in,int n,int in_first,VipsRegion * imap,VipsRegion * omap,VipsRegion * out,OpParBase * par)261   void render(VipsRegion** in, int n, int in_first,
262       VipsRegion* imap, VipsRegion* omap,
263       VipsRegion* out, OpParBase* par)
264   {
265   }
266 };
267 
268 
269 
270 template < OP_TEMPLATE_DEF_TYPE_SPEC >
271 class DefringeAlgoProc< OP_TEMPLATE_IMP_TYPE_SPEC(float) >
272 {
273 public:
render(VipsRegion ** ireg,int n,int in_first,VipsRegion * imap,VipsRegion * omap,VipsRegion * oreg,OpParBase * par)274   void render(VipsRegion** ireg, int n, int in_first,
275       VipsRegion* imap, VipsRegion* omap,
276       VipsRegion* oreg, OpParBase* par)
277   {
278     if( n != 2 ) return;
279     if( ireg[0] == NULL ) return;
280     if( ireg[1] == NULL ) return;
281 
282     DefringeAlgoPar* opar = dynamic_cast<DefringeAlgoPar*>(par);
283     if( !opar ) return;
284 
285 #define RESCALE_LAB(ab) ((ab)/255.f)
286 
287     bool bError = false;
288 
289     const float threshold = RESCALE_LAB(opar->get_threshold());
290     VipsRect *ir = &ireg[0]->valid;
291     VipsRect *r = &oreg->valid;
292     const int ch = oreg->im->Bands;
293     const int iwidth = ir->width;
294     const int iheight = ir->height;
295     const int width = r->width;
296     const int height = r->height;
297     const int ilsz = iwidth*ch;
298     const int lsz = width*ch;
299     const int border = r->top - ir->top;
300 
301     float* in = NULL;
302     float* pblur = NULL;
303     float* out = NULL;
304 
305     //const int order = 1; // 0,1,2
306     const int radius = opar->get_radius();
307 
308     float avg_edge_chroma = 0.0f;
309 
310     // save the fibonacci lattices in them later
311     int *xy_avg = NULL;
312     int *xy_small = NULL;
313     float *edge_chroma = NULL;
314 
315     if(width < 2 * radius + 1 || height < 2 * radius + 1)
316       bError = true;
317 
318     // Pre-Computed Fibonacci Lattices
319     int *tmp = NULL;
320 
321     const int samples_small = opar->get_samples_small();
322     const int samples_avg = opar->get_samples_avg();
323 
324     xy_avg = opar->get_xy_avg();
325     xy_small = opar->get_xy_small();
326 
327     if (!bError) {
328       edge_chroma = (float*)malloc(iwidth * iheight * sizeof(float));
329       if (edge_chroma != NULL)
330       {
331         memset(edge_chroma, 0, iwidth * iheight * sizeof(float));
332 
333         for(int v = 0; v < iheight; v++)
334         {
335           in = (float*)VIPS_REGION_ADDR( ireg[0], ir->left, ir->top + v );
336           pblur = (float*)VIPS_REGION_ADDR( ireg[1], ir->left, ir->top + v );
337           float *ec = edge_chroma + v * iwidth;
338 
339           for(int t = 0, tt = 0; t < ilsz; t+=ch, tt++)
340           {
341             // edge-detect on color channels
342             // method: difference of original to gaussian blurred image:
343             //std::cout<<"defringe: t="<<t<<"  ilsz="<<ilsz<<std::endl;
344             float a = in[t + 1] - pblur[t + 1];
345             float b = in[t + 2] - pblur[t + 2];
346 
347             float edge = (a * a + b * b); // range up to 2*(256)^2 -> approx. 0 to 131072
348 
349             // save local edge chroma in out[.. +3] , this is later compared with threshold
350             ec[tt] = edge;
351 
352             // the average chroma of the edge-layer in the roi
353             if(MODE_GLOBAL_AVERAGE == opar->get_op_mode()) avg_edge_chroma += edge;
354           }
355         }
356       }
357       else
358       {
359         std::cout<<"Error allocating memory for edge detection in: defringe module"<<std::endl;
360         bError = true;
361       }
362     }
363 
364     float thresh = 0;
365     if (!bError) {
366       if(MODE_GLOBAL_AVERAGE == opar->get_op_mode())
367       {
368         avg_edge_chroma = avg_edge_chroma / (iwidth * iheight) + RESCALE_LAB(10.0f) * FLT_EPSILON;
369         thresh = fmax(RESCALE_LAB(0.1f), RESCALE_LAB(4.0f) * threshold * avg_edge_chroma / MAGIC_THRESHOLD_COEFF);
370       }
371       else
372       {
373         // this fixed value will later be changed when doing local averaging, or kept as-is in "static" mode
374         avg_edge_chroma = RESCALE_LAB(RESCALE_LAB(MAGIC_THRESHOLD_COEFF));
375         thresh = fmax(RESCALE_LAB(0.1f), threshold);
376       }
377       thresh = RESCALE_LAB(thresh);
378     }
379 
380     if (!bError) {
381       for(int v = 0, vv = border; v < height; v++, vv++)
382       {
383         in = (float*)VIPS_REGION_ADDR( ireg[0], r->left, r->top + v );
384         out = (float*)VIPS_REGION_ADDR( oreg, r->left, r->top + v );
385 
386         int l = vv * iwidth;
387         int lprev = (vv - 1) * iwidth;
388         int lnext = (vv + 1) * iwidth;
389         float *ec = edge_chroma + l;
390 
391         for(int t = 0, tt = border; t < lsz; t+=ch, tt++)
392         {
393           float local_thresh = thresh;
394           // think of compiler setting "-funswitch-loops" to maybe improve these things:
395           if(MODE_LOCAL_AVERAGE == opar->get_op_mode() && ec[tt] > thresh)
396           {
397             float local_avg = 0.0f;
398             // use some and not all values from the neigbourhood to speed things up:
399             const int *tmp = xy_avg;
400             for(int u = 0; u < samples_avg; u++)
401             {
402               int dx = *tmp++;
403               int dy = *tmp++;
404               int x = tt + dx; //provided that the region padding is correct, this is not needed: MAX(0, MIN(width - 1, t + dx));
405               int y = vv + dy; //provided that the region padding is correct, this is not needed: MAX(0, MIN(height - 1, v + dy));
406               local_avg += edge_chroma[y * iwidth + x];
407             }
408             avg_edge_chroma = fmax(RESCALE_LAB(0.01f), (float)local_avg / samples_avg);
409             local_thresh = fmax(RESCALE_LAB(0.1f), RESCALE_LAB(4.0f) * threshold * avg_edge_chroma / MAGIC_THRESHOLD_COEFF);
410           }
411 
412           if(edge_chroma[(size_t)vv * iwidth + tt] > local_thresh
413               // reduces artifacts ("region growing by 1 pixel"):
414               || edge_chroma[lprev + tt - 1] > local_thresh
415               || edge_chroma[lprev + tt] > local_thresh
416               || edge_chroma[lprev + tt + 1] > local_thresh
417               || edge_chroma[l + tt - 1] > local_thresh
418               || edge_chroma[l + tt + 1] > local_thresh
419               || edge_chroma[lnext + tt - 1] > local_thresh
420               || edge_chroma[lnext + tt] > local_thresh
421               || edge_chroma[lnext + tt + 1] > local_thresh)
422           {
423             float atot = 0.f, btot = 0.f;
424             float norm = 0.f;
425             float weight = 0.f;
426 
427             // it seems better to use only some pixels from a larger window instead of all pixels from a smaller window
428             // we use a fibonacci lattice for that, samples amount need to be a fibonacci number, this can then be
429             // scaled to a certain radius
430 
431             // use some neighbourhood pixels for lowest chroma average
432             const int *tmp = xy_small;
433             for(int u = 0; u < samples_small; u++)
434             {
435               int dx = *tmp++;
436               int dy = *tmp++;
437               int x = tt + dx; //provided that the region padding is correct, this is not needed: MAX(0, MIN(width - 1, t + dx));
438               int y = vv + dy; //provided that the region padding is correct, this is not needed: MAX(0, MIN(height - 1, v + dy));
439 
440               // inverse chroma weighted average of neigbouring pixels inside window
441               // also taking average edge chromaticity into account (either global or local average)
442 
443               weight = 1.0f / (edge_chroma[(size_t)y * iwidth + x] + avg_edge_chroma);
444               float *in2 = (float*)VIPS_REGION_ADDR( ireg[0], ir->left, ir->top + y );
445 
446               atot += weight * in2[x * ch + 1];
447               btot += weight * in2[x * ch + 2];
448               norm += weight;
449             }
450 
451             // here we could try using a "balance" between original and changed value, this could be used to
452             // reduce artifcats but on first tries results weren't very convincing,
453             // and there are blend settings available anyway
454 
455             double a = (atot / norm);
456             double b = (btot / norm);
457 
458             out[t + 1] = a;
459             out[t + 2] = b;
460           }
461           else
462           {
463             out[t + 1] = in[t + 1];
464             out[t + 2] = in[t + 2];
465           }
466           out[t] = in[t];
467 
468         }
469       }
470     }
471 
472     if (bError) {
473       // something went wrong, return original image
474       for( int y = 0; y < height; y++ ) {
475         in = (float*)VIPS_REGION_ADDR( ireg[0], r->left, r->top + y );
476         out = (float*)VIPS_REGION_ADDR( oreg, r->left, r->top + y );
477 
478         for( int x = 0; x < width * ch; x++ ) out[x] = in[x];
479       }
480     }
481 
482     if (edge_chroma) free(edge_chroma);
483   }
484 
485 };
486 
487 
488 ProcessorBase* new_defringe();
489 
490 ProcessorBase* new_defringe_algo();
491 
492 }
493 
494 #endif
495 
496 
497