1 /* This file is an image processing operation for GEGL
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 3 of the License, or
6  * (at your option) any later version.
7  *
8  * This program 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
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  *
16  * This operation is a port of the GIMP Apply lens plug-in
17  * Copyright (C) 1997 Morten Eriksen mortene@pvv.ntnu.no
18  *
19  * Porting to GEGL:
20  * Copyright 2013 Emanuel Schrade <emanuel.schrade@student.kit.edu>
21  * Copyright 2013 Stephan Seifermann <stephan.seifermann@student.kit.edu>
22  * Copyright 2013 Bastian Pirk <bastian.pirk@student.kit.edu>
23  * Copyright 2013 Pascal Giessler <pascal.giessler@student.kit.edu>
24  * Copyright 2015 Thomas Manni <thomas.manni@free.fr>
25  */
26 
27 /* TODO: Find some better algorithm to calculate the roi for each dest
28  *       rectangle. Right now it simply asks for the entire image...
29  */
30 
31 #include "config.h"
32 #include <glib/gi18n-lib.h>
33 
34 #ifdef GEGL_PROPERTIES
35 
36 property_double (refraction_index, _("Lens refraction index"), 1.7)
37   value_range (1.0, 100.0)
38   ui_range    (1.0, 10.0)
39   ui_gamma    (3.0)
40 
41 property_boolean (keep_surroundings, _("Keep original surroundings"), FALSE)
42   description(_("Keep image unchanged, where not affected by the lens."))
43 
44 property_color (background_color, _("Background color"), "none")
45   ui_meta ("role", "color-secondary")
46   ui_meta ("sensitive", "! keep_surroundings")
47 
48 #else
49 
50 #define GEGL_OP_FILTER
51 #define GEGL_OP_NAME     apply_lens
52 #define GEGL_OP_C_SOURCE apply-lens.c
53 
54 #include "gegl-op.h"
55 
56 typedef struct
57 {
58   gfloat  bg_color[4];
59   gdouble a, b, c;
60   gdouble asqr, bsqr, csqr;
61 } AlParamsType;
62 
63 /**
64  * Computes the original position (ox, oy) of the
65  * distorted point (x, y) after passing through the lens
66  * which is given by its center and its refraction index.
67  * See: Ellipsoid formula: x^2/a^2 + y^2/b^2 + z^2/c^2 = 1.
68  */
69 static void
70 find_undistorted_pos (gdouble       x,
71                       gdouble       y,
72                       gdouble       refraction,
73                       AlParamsType *params,
74                       gdouble      *ox,
75                       gdouble      *oy)
76 {
77   gdouble z;
78   gdouble nxangle, nyangle, theta1, theta2;
79   gdouble ri1 = 1.0;
80   gdouble ri2 = refraction;
81 
82   z = sqrt ((1 - x * x / params->asqr - y * y / params->bsqr) * params->csqr);
83 
84   nxangle = acos (x / sqrt(x * x + z * z));
85   theta1 = G_PI / 2.0 - nxangle;
86   theta2 = asin (sin (theta1) * ri1 / ri2);
87   theta2 = G_PI / 2.0 - nxangle - theta2;
88   *ox = x - tan (theta2) * z;
89 
90   nyangle = acos (y / sqrt (y * y + z * z));
91   theta1 = G_PI / 2.0 - nyangle;
92   theta2 = asin (sin (theta1) * ri1 / ri2);
93   theta2 = G_PI / 2.0 - nyangle - theta2;
94   *oy = y - tan (theta2) * z;
95 }
96 
97 static void
98 prepare (GeglOperation *operation)
99 {
100   GeglProperties *o = GEGL_PROPERTIES (operation);
101   const Babl *format = babl_format_with_space ("RGBA float", gegl_operation_get_source_space (operation, "input"));
102 
103   GeglRectangle  *whole_region;
104   AlParamsType   *params;
105 
106   if (! o->user_data)
107     o->user_data = g_slice_new0 (AlParamsType);
108 
109   params = (AlParamsType *) o->user_data;
110 
111   whole_region = gegl_operation_source_get_bounding_box (operation, "input");
112 
113   if (whole_region && ! gegl_rectangle_is_infinite_plane (whole_region))
114     {
115       params->a = 0.5 * whole_region->width;
116       params->b = 0.5 * whole_region->height;
117       params->c = MIN (params->a, params->b);
118       params->asqr = params->a * params->a;
119       params->bsqr = params->b * params->b;
120       params->csqr = params->c * params->c;
121     }
122 
123   gegl_color_get_pixel (o->background_color, format, params->bg_color);
124 
125   gegl_operation_set_format (operation, "input", format);
126   gegl_operation_set_format (operation, "output", format);
127 }
128 
129 static void
130 finalize (GObject *object)
131 {
132   GeglOperation *op = (void*) object;
133   GeglProperties *o = GEGL_PROPERTIES (op);
134 
135   if (o->user_data)
136     {
137       g_slice_free (AlParamsType, o->user_data);
138       o->user_data = NULL;
139     }
140 
141   G_OBJECT_CLASS (gegl_op_parent_class)->finalize (object);
142 }
143 
144 static GeglRectangle
145 get_required_for_output (GeglOperation       *operation,
146                          const gchar         *input_pad,
147                          const GeglRectangle *roi)
148 {
149   const GeglRectangle *in_rect =
150             gegl_operation_source_get_bounding_box (operation, "input");
151 
152   if (! in_rect || gegl_rectangle_is_infinite_plane (in_rect))
153     {
154       return *roi;
155     }
156 
157   return *in_rect;
158 }
159 
160 static gboolean
161 process (GeglOperation       *operation,
162          GeglBuffer          *input,
163          GeglBuffer          *output,
164          const GeglRectangle *roi,
165          gint                 level)
166 {
167   GeglProperties       *o = GEGL_PROPERTIES (operation);
168   AlParamsType    *params = (AlParamsType *) o->user_data;
169   const Babl      *format = gegl_operation_get_format (operation, "output");
170 
171   GeglSampler        *sampler;
172   GeglBufferIterator *iter;
173   gint                x, y;
174 
175   sampler = gegl_buffer_sampler_new_at_level (input, format,
176                                               GEGL_SAMPLER_CUBIC, level);
177 
178   iter = gegl_buffer_iterator_new (output, roi, level, format,
179                                    GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 2);
180 
181   gegl_buffer_iterator_add (iter, input, roi, level, format,
182                             GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
183 
184   while (gegl_buffer_iterator_next (iter))
185     {
186       gfloat *out_pixel = iter->items[0].data;
187       gfloat *in_pixel  = iter->items[1].data;
188 
189       for (y = iter->items[0].roi.y; y < iter->items[0].roi.y + iter->items[0].roi.height; y++)
190         {
191           gdouble dy, dysqr;
192 
193           dy = -((gdouble) y - params->b + 0.5);
194           dysqr = dy * dy;
195 
196           for (x = iter->items[0].roi.x; x < iter->items[0].roi.x + iter->items[0].roi.width; x++)
197             {
198               gdouble dx, dxsqr;
199 
200               dx = (gdouble) x - params->a + 0.5;
201               dxsqr = dx * dx;
202 
203               if (dysqr < (params->bsqr - (params->bsqr * dxsqr) / params->asqr))
204                 {
205                   /**
206                    * If (x, y) is inside the affected region, we can find its original
207                    * position and fetch the pixel with the sampler
208                    */
209                   gdouble ox, oy;
210                   find_undistorted_pos (dx, dy, o->refraction_index, params,
211                                         &ox, &oy);
212 
213                   gegl_sampler_get (sampler, ox + params->a, params->b - oy,
214                                     NULL, out_pixel, GEGL_ABYSS_NONE);
215                 }
216               else
217                 {
218                   /**
219                    * Otherwise (that is for pixels outside the lens), we could either leave
220                    * the image data unchanged, or set it to a specified 'background_color',
221                    * depending on the user input.
222                    */
223                   if (o->keep_surroundings)
224                     memcpy (out_pixel, in_pixel, sizeof (gfloat) * 4);
225                   else
226                     memcpy (out_pixel, params->bg_color, sizeof (gfloat) * 4);
227                 }
228 
229               out_pixel += 4;
230               in_pixel  += 4;
231             }
232         }
233     }
234 
235   g_object_unref (sampler);
236 
237   return TRUE;
238 }
239 
240 static gboolean
241 operation_process (GeglOperation        *operation,
242                    GeglOperationContext *context,
243                    const gchar          *output_prop,
244                    const GeglRectangle  *result,
245                    gint                  level)
246 {
247   GeglOperationClass  *operation_class;
248 
249   const GeglRectangle *in_rect =
250     gegl_operation_source_get_bounding_box (operation, "input");
251 
252   if (in_rect && gegl_rectangle_is_infinite_plane (in_rect))
253     {
254       gpointer in = gegl_operation_context_get_object (context, "input");
255       gegl_operation_context_take_object (context, "output",
256                                           g_object_ref (G_OBJECT (in)));
257       return TRUE;
258     }
259 
260   operation_class = GEGL_OPERATION_CLASS (gegl_op_parent_class);
261 
262   return operation_class->process (operation, context, output_prop, result,
263                                    gegl_operation_context_get_level (context));
264 }
265 
266 static void
267 gegl_op_class_init (GeglOpClass *klass)
268 {
269   GObjectClass             *object_class;
270   GeglOperationClass       *operation_class;
271   GeglOperationFilterClass *filter_class;
272   gchar                    *composition =
273     "<?xml version='1.0' encoding='UTF-8'?>"
274     "<gegl>"
275     "<node operation='gegl:apply-lens'>"
276     "  <params>"
277     "    <param name='refraction_index'>1.7</param>"
278     "    <param name='keep_surroundings'>false</param>"
279     "    <param name='background_color'>rgba(0, 0.50196, 0.50196, 0.75)</param>"
280     "  </params>"
281     "</node>"
282     "<node operation='gegl:load'>"
283     "  <params>"
284     "    <param name='path'>standard-input.png</param>"
285     "  </params>"
286     "</node>"
287     "</gegl>";
288 
289   object_class    = G_OBJECT_CLASS (klass);
290   operation_class = GEGL_OPERATION_CLASS (klass);
291   filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);
292 
293   object_class->finalize                   = finalize;
294   operation_class->prepare                 = prepare;
295   operation_class->get_required_for_output = get_required_for_output;
296   operation_class->process                 = operation_process;
297   filter_class->process                    = process;
298 
299   gegl_operation_class_set_keys (operation_class,
300     "name",        "gegl:apply-lens",
301     "title",       _("Apply Lens"),
302     "categories",  "map",
303     "reference-hash", "4230b1cd886d335503ff436f97b82465",
304     "reference-hashB", "b2ff4e3d701fa6d6a1f277fd79237d07",
305     "license",     "GPL3+",
306     "description", _("Simulates the optical distortion caused by having "
307                      "an elliptical lens over the image"),
308     "reference-composition", composition,
309     NULL);
310 }
311 
312 #endif
313