1 /* This file is an image processing operation for GEGL
2  *
3  * GEGL is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 3 of the License, or (at your option) any later version.
7  *
8  * GEGL is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
15  * This program is free software: you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
27  *
28  * 2002, 2014, 2016 (c) Øyvind Kolås pippin@gimp.org
29  */
30 
31 #include "config.h"
32 #include <glib/gi18n-lib.h>
33 
34 #ifdef GEGL_PROPERTIES
35 
36 property_color (from_0, _("From 0"), "black")
37 property_color (to_0, _("To 0"), "black")
38 property_double (weight_0, _("weight 0"), 100.0)
39              ui_range (0.0, 220.0)
40 property_color (from_1, _("From 1"), "black")
41 property_color (to_1, _("To 1"), "black")
42 property_double (weight_1, _("weight 1"), 100.0)
43              ui_range (0.0, 220.0)
44 property_color (from_2, _("From 2"), "black")
45 property_color (to_2, _("To 2"), "black")
46 property_double (weight_2, _("weight 2"), 100.0)
47              ui_range (0.0, 220.0)
48 property_color (from_3, _("From 3"), "black")
49 property_color (to_3, _("To 3"), "black")
50 property_double (weight_3, _("weight 3"), 100.0)
51              ui_range (0.0, 220.0)
52 property_color (from_4, _("From 4"), "black")
53 property_color (to_4, _("To 4"), "black")
54 property_double (weight_4, _("weight 4"), 100.0)
55              ui_range (0.0, 220.0)
56 property_color (from_5, _("From 5"), "black")
57 property_color (to_5, _("To 5"), "black")
58 property_double (weight_5, _("weight 5"), 100.0)
59              ui_range (0.0, 220.0)
60 property_color (from_6, _("From 6"), "black")
61 property_color (to_6, _("To 6"), "black")
62 property_double (weight_6, _("weight 6"), 100.0)
63              ui_range (0.0, 220.0)
64 property_color (from_7, _("From 7"), "black")
65 property_color (to_7, _("To 7"), "black")
66 property_double (weight_7, _("weight 7"), 100.0)
67              ui_range (0.0, 220.0)
68 property_double (weight, _("global weight scale"), 1.0)
69              ui_range (0.0, 1.0)
70 property_double (amount, _("amount"), 1.0)
71              ui_range (0.0, 1.0)
72 
73 #else
74 
75 #define GEGL_OP_POINT_FILTER
76 #define GEGL_OP_NAME color_warp
77 #define GEGL_OP_C_SOURCE color-warp.c
78 
79 #include "gegl-op.h"
80 
81 #define MAX_PAIRS 64
82 
83 typedef struct CoordPair {
84   float a[3];
85   float b[3];
86   float weight;
87 } CoordPair;
88 
89 typedef struct CoordWarp {
90   CoordPair pair[MAX_PAIRS];
91   int count;
92 } CoordWarp;
93 
94 static CoordWarp *cw_new (void)
95 {
96   CoordWarp *cw;
97   cw = g_new0 (CoordWarp, 1);
98   return cw;
99 }
100 
101 static void cw_clear_pairs (CoordWarp   *cw)
102 {
103   cw->count = 0;
104 }
105 
106 static void cw_destroy (CoordWarp   *cw)
107 {
108   g_free (cw);
109 }
110 
111 static void cw_add_pair (CoordWarp   *cw,
112                          const float *coord_a,
113                          const float *coord_b,
114                          float        weight)
115 {
116   int d;
117   if (cw->count +1 >= MAX_PAIRS)
118     return;
119   for (d = 0; d < 3; d++)
120     cw->pair[cw->count].a[d] = coord_a[d];
121   for (d = 0; d < 3; d++)
122     cw->pair[cw->count].b[d] = coord_b[d];
123   cw->pair[cw->count].weight = weight;
124   cw->count++;
125 }
126 
127 static inline float sq_dist (CoordWarp *cw, const float *coord_a, const float *coord_b)
128 {
129   int d;
130   float sq_sum = 0;
131   for (d = 0; d < 3; d++)
132     sq_sum += (coord_b[d] - coord_a[d]) * (coord_b[d] - coord_a[d]);
133   return sq_sum;
134 }
135 
136 static inline float calc_weight (float dist, float lowest_dist, float coord_weight)
137 {
138   float influence = coord_weight;
139   return expf (-dist / influence);
140 }
141 
142 static void cw_map (CoordWarp   *cw,
143                     const float *coord_a,
144                     float       *coord_b)
145 {
146   int i;
147   int lowest_dist_no = 0;
148   double lowest_dist = 12345678900000.0;
149   double sum_wc = 0.0;
150   float warp[3] = {0,};
151 
152   for (i = 0; i < cw->count; i++)
153   {
154     float sqd = sq_dist (cw, coord_a, cw->pair[i].a);
155     if (sqd < lowest_dist)
156     {
157       lowest_dist = sqd;
158       lowest_dist_no = i;
159     }
160   }
161 
162   for (i = 0; i < cw->count; i++)
163   {
164     float sqd = sq_dist (cw, coord_a, cw->pair[i].a);
165     sum_wc += lowest_dist / sqd;
166   }
167 
168   if (lowest_dist > 0.0)
169   {
170     for (i = 0; i < cw->count; i++)
171     {
172       int d;
173       float sqd = sq_dist (cw, coord_a, cw->pair[i].a);
174       float weight = calc_weight (sqd, lowest_dist, cw->pair[i].weight);
175       weight = weight / sum_wc;
176 
177       for (d = 0; d < 3; d++)
178         warp[d] += (cw->pair[i].a[d] - cw->pair[i].b[d]) * weight;
179     }
180   }
181   else
182   {
183     int d;
184     for (d = 0; d < 3; d++)
185       warp[d] = (cw->pair[lowest_dist_no].a[d] - cw->pair[lowest_dist_no].b[d]);
186   }
187 
188   {
189     int d;
190     for (d = 0; d < 3; d++)
191       coord_b[d] = coord_a[d] - warp[d];
192   }
193 }
194 
195 static void maybe_add_pair (CoordWarp *cw,
196                             GeglColor *colorA,
197                             GeglColor *colorB,
198                             float      weight,
199                             const Babl *colorformat)
200 {
201   gfloat from[4];
202   gfloat to[4];
203   gegl_color_get_pixel (colorA, colorformat, from);
204   gegl_color_get_pixel (colorB, colorformat, to);
205   if (from[0] == 0.0 &&
206       from[1] == 0.0 &&
207       from[2] == 0.0 &&
208       to[0] == 0.0 &&
209       to[1] == 0.0 &&
210       to[2] == 0.0)
211   {
212   }
213   else
214   {
215     cw_add_pair (cw, from, to, weight);
216   }
217 }
218 
219 static void prepare (GeglOperation *operation)
220 {
221   CoordWarp *cw;
222   GeglProperties *o = GEGL_PROPERTIES (operation);
223   const Babl *space = gegl_operation_get_source_space (operation, "input");
224   const Babl *format = babl_format_with_space ("CIE Lab float", space);
225   gegl_operation_set_format (operation, "input", format);
226   gegl_operation_set_format (operation, "output", format);
227 
228   if (o->user_data == NULL)
229     o->user_data = cw_new ();
230   cw = o->user_data;
231 
232   cw_clear_pairs (cw);
233   maybe_add_pair (cw, o->from_0, o->to_0, o->weight * o->weight_0, format);
234   maybe_add_pair (cw, o->from_1, o->to_1, o->weight * o->weight_1, format);
235   maybe_add_pair (cw, o->from_2, o->to_2, o->weight * o->weight_2, format);
236   maybe_add_pair (cw, o->from_3, o->to_3, o->weight * o->weight_3, format);
237   maybe_add_pair (cw, o->from_4, o->to_4, o->weight * o->weight_4, format);
238   maybe_add_pair (cw, o->from_5, o->to_5, o->weight * o->weight_5, format);
239   maybe_add_pair (cw, o->from_6, o->to_6, o->weight * o->weight_6, format);
240   maybe_add_pair (cw, o->from_7, o->to_7, o->weight * o->weight_7, format);
241 }
242 
243 static void
244 finalize (GObject *object)
245 {
246   GeglProperties *o = GEGL_PROPERTIES (object);
247 
248   if (o->user_data)
249     {
250       cw_destroy (o->user_data);
251       o->user_data = NULL;
252     }
253 
254   G_OBJECT_CLASS (gegl_op_parent_class)->finalize (object);
255 }
256 
257 static gboolean
258 process (GeglOperation       *op,
259          void                *in_buf,
260          void                *out_buf,
261          glong                n_pixels,
262          const GeglRectangle *roi,
263          gint                 level)
264 {
265   GeglProperties *o         = GEGL_PROPERTIES (op);
266   gfloat         *in_pixel  = in_buf;
267   gfloat         *out_pixel = out_buf;
268   CoordWarp      *cw        = o->user_data;
269   float           amount    = o->amount;
270 
271   in_pixel  = in_buf;
272   out_pixel = out_buf;
273 
274   while (n_pixels--)
275     {
276       if (amount == 1.0)
277       {
278         cw_map (cw, in_pixel, out_pixel);
279       }
280       else
281       {
282         int d;
283         float res[4];
284         cw_map (cw, in_pixel, res);
285         for (d = 0; d < 3; d++)
286           out_pixel[d] = in_pixel[d] * (1.0-amount) + res[d] * amount;
287       }
288       in_pixel  += 3;
289       out_pixel += 3;
290     }
291 
292   return TRUE;
293 }
294 
295 static void
296 gegl_op_class_init (GeglOpClass *klass)
297 {
298   GObjectClass                  *object_class;
299   GeglOperationClass            *operation_class;
300   GeglOperationPointFilterClass *point_filter_class;
301   gchar                         *composition =
302     "<?xml version='1.0' encoding='UTF-8'?>"
303     "<gegl>"
304     "  <node operation='gegl:crop' width='200' height='200'/>"
305     "  <node operation='gegl:over'>"
306     "    <node operation='gegl:color-warp'>"
307     "      <params>"
308     "        <param name='from-0'>rgb(1.0000, 1.0000, 1.0000)</param>"
309     "        <param name='to-0'>rgb(0.9900, 0.4500, 0.8500)</param>"
310     "        <param name='weight-0'>100.00</param>"
311     "        <param name='from-1'>rgb(0.0000, 0.0000, 0.0000)</param>"
312     "        <param name='to-1'>rgb(0.5000, 0.0000, 0.5000)</param>"
313     "        <param name='weight-1'>33.33</param>"
314     "        <param name='weight'>1.00</param>"
315     "        <param name='amount'>0.50</param>"
316     "      </params>"
317     "    </node>"
318     "    <node operation='gegl:load' path='standard-input.png'/>"
319     "  </node>"
320     "  <node operation='gegl:checkerboard'>"
321     "    <params>"
322     "      <param name='color1'>rgb(0.25,0.25,0.25)</param>"
323     "      <param name='color2'>rgb(0.75,0.75,0.75)</param>"
324     "    </params>"
325     "  </node>"
326     "</gegl>";
327 
328   object_class       = G_OBJECT_CLASS (klass);
329   operation_class    = GEGL_OPERATION_CLASS (klass);
330   point_filter_class = GEGL_OPERATION_POINT_FILTER_CLASS (klass);
331   object_class->finalize      = finalize;
332   operation_class->prepare    = prepare;
333   point_filter_class->process = process;
334 
335   gegl_operation_class_set_keys (operation_class,
336     "name",                  "gegl:color-warp",
337     "title",                 _("Color warp"),
338     "categories",            "color",
339     "reference-composition", composition,
340     "reference-hash",        "637569c3382fc061ee45513eaebf22d6",
341     "description", _("Warps the colors of an image between colors with weighted distortion factors, color pairs which are black to black get ignored when constructing the mapping."),
342     NULL);
343 }
344 
345 #endif
346