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