apply-lens.c 8.97 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/* This file is an image processing operation for GEGL
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * 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 17 18 19 20 21 22 23
 *
 * This operation is a port of the GIMP Apply lens plug-in
 * Copyright (C) 1997 Morten Eriksen mortene@pvv.ntnu.no
 *
 * Porting to GEGL:
 * Copyright 2013 Emanuel Schrade <emanuel.schrade@student.kit.edu>
 * Copyright 2013 Stephan Seifermann <stephan.seifermann@student.kit.edu>
 * Copyright 2013 Bastian Pirk <bastian.pirk@student.kit.edu>
 * Copyright 2013 Pascal Giessler <pascal.giessler@student.kit.edu>
24 25 26 27 28
 * Copyright 2015 Thomas Manni <thomas.manni@free.fr>
 */

/* TODO: Find some better algorithm to calculate the roi for each dest
 *       rectangle. Right now it simply asks for the entire image...
29 30 31 32 33
 */

#include "config.h"
#include <glib/gi18n-lib.h>

34
#ifdef GEGL_PROPERTIES
35

36 37
property_double (refraction_index, _("Lens refraction index"), 1.7)
  value_range (1.0, 100.0)
38 39
  ui_range    (1.0, 10.0)
  ui_gamma    (3.0)
40

41 42 43
property_boolean (keep_surroundings, _("Keep original surroundings"), FALSE)
  description(_("Keep image unchanged, where not affected by the lens."))

44
property_color (background_color, _("Background color"), "none")
45
  ui_meta ("role", "color-secondary")
46
  ui_meta ("sensitive", "! keep_surroundings")
47 48 49

#else

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
#include <math.h>
56 57 58 59 60 61 62

typedef struct
{
  gfloat  bg_color[4];
  gdouble a, b, c;
  gdouble asqr, bsqr, csqr;
} AlParamsType;
63 64

/**
65 66
 * Computes the original position (ox, oy) of the
 * distorted point (x, y) after passing through the lens
67 68 69 70
 * which is given by its center and its refraction index.
 * See: Ellipsoid formula: x^2/a^2 + y^2/b^2 + z^2/c^2 = 1.
 */
static void
71 72 73 74 75 76
find_undistorted_pos (gdouble       x,
                      gdouble       y,
                      gdouble       refraction,
                      AlParamsType *params,
                      gdouble      *ox,
                      gdouble      *oy)
77
{
78 79 80 81
  gdouble z;
  gdouble nxangle, nyangle, theta1, theta2;
  gdouble ri1 = 1.0;
  gdouble ri2 = refraction;
82

83
  z = sqrt ((1 - x * x / params->asqr - y * y / params->bsqr) * params->csqr);
84 85

  nxangle = acos (x / sqrt(x * x + z * z));
86
  theta1 = G_PI / 2.0 - nxangle;
87
  theta2 = asin (sin (theta1) * ri1 / ri2);
88 89
  theta2 = G_PI / 2.0 - nxangle - theta2;
  *ox = x - tan (theta2) * z;
90 91

  nyangle = acos (y / sqrt (y * y + z * z));
92
  theta1 = G_PI / 2.0 - nyangle;
93
  theta2 = asin (sin (theta1) * ri1 / ri2);
94 95
  theta2 = G_PI / 2.0 - nyangle - theta2;
  *oy = y - tan (theta2) * z;
96 97 98 99 100
}

static void
prepare (GeglOperation *operation)
{
101
  GeglProperties *o = GEGL_PROPERTIES (operation);
102
  const Babl *format = babl_format_with_space ("RGBA float", gegl_operation_get_source_space (operation, "input"));
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124

  GeglRectangle  *whole_region;
  AlParamsType   *params;

  if (! o->user_data)
    o->user_data = g_slice_new0 (AlParamsType);

  params = (AlParamsType *) o->user_data;

  whole_region = gegl_operation_source_get_bounding_box (operation, "input");

  if (whole_region)
    {
      params->a = 0.5 * whole_region->width;
      params->b = 0.5 * whole_region->height;
      params->c = MIN (params->a, params->b);
      params->asqr = params->a * params->a;
      params->bsqr = params->b * params->b;
      params->csqr = params->c * params->c;
    }

  gegl_color_get_pixel (o->background_color, format, params->bg_color);
125 126 127 128 129

  gegl_operation_set_format (operation, "input", format);
  gegl_operation_set_format (operation, "output", format);
}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
static void
finalize (GObject *object)
{
  GeglOperation *op = (void*) object;
  GeglProperties *o = GEGL_PROPERTIES (op);

  if (o->user_data)
    {
      g_slice_free (AlParamsType, o->user_data);
      o->user_data = NULL;
    }

  G_OBJECT_CLASS (gegl_op_parent_class)->finalize (object);
}

145
static GeglRectangle
146 147 148
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *roi)
149
{
150 151 152 153 154 155 156
  GeglRectangle  result = {0,0,0,0};
  GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");

  if (!in_rect)
    return result;
  else
    return *in_rect;
157 158 159 160 161 162
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
163
         const GeglRectangle *roi,
164 165
         gint                 level)
{
166 167
  GeglProperties       *o = GEGL_PROPERTIES (operation);
  AlParamsType    *params = (AlParamsType *) o->user_data;
168
  const Babl      *format = gegl_operation_get_format (operation, "output");
169 170 171 172 173 174 175 176 177

  GeglSampler        *sampler;
  GeglBufferIterator *iter;
  gint                x, y;

  sampler = gegl_buffer_sampler_new_at_level (input, format,
                                              GEGL_SAMPLER_CUBIC, level);

  iter = gegl_buffer_iterator_new (output, roi, level, format,
178
                                   GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 2);
179 180 181 182 183

  gegl_buffer_iterator_add (iter, input, roi, level, format,
                            GEGL_ACCESS_READ, GEGL_ABYSS_NONE);

  while (gegl_buffer_iterator_next (iter))
184
    {
185 186
      gfloat *out_pixel = iter->items[0].data;
      gfloat *in_pixel  = iter->items[1].data;
187

188
      for (y = iter->items[0].roi.y; y < iter->items[0].roi.y + iter->items[0].roi.height; y++)
189 190
        {
          gdouble dy, dysqr;
191

192 193
          dy = -((gdouble) y - params->b + 0.5);
          dysqr = dy * dy;
194

195
          for (x = iter->items[0].roi.x; x < iter->items[0].roi.x + iter->items[0].roi.width; x++)
196
            {
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
              gdouble dx, dxsqr;

              dx = (gdouble) x - params->a + 0.5;
              dxsqr = dx * dx;

              if (dysqr < (params->bsqr - (params->bsqr * dxsqr) / params->asqr))
                {
                  /**
                   * If (x, y) is inside the affected region, we can find its original
                   * position and fetch the pixel with the sampler
                   */
                  gdouble ox, oy;
                  find_undistorted_pos (dx, dy, o->refraction_index, params,
                                        &ox, &oy);

                  gegl_sampler_get (sampler, ox + params->a, params->b - oy,
                                    NULL, out_pixel, GEGL_ABYSS_NONE);
                }
215
              else
216 217 218 219 220 221 222 223 224 225 226 227 228 229
                {
                  /**
                   * Otherwise (that is for pixels outside the lens), we could either leave
                   * the image data unchanged, or set it to a specified 'background_color',
                   * depending on the user input.
                   */
                  if (o->keep_surroundings)
                    memcpy (out_pixel, in_pixel, sizeof (gfloat) * 4);
                  else
                    memcpy (out_pixel, params->bg_color, sizeof (gfloat) * 4);
                }

              out_pixel += 4;
              in_pixel  += 4;
230 231
            }
        }
232
    }
233

234
  g_object_unref (sampler);
235 236 237 238 239

  return TRUE;
}

static void
240
gegl_op_class_init (GeglOpClass *klass)
241
{
242
  GObjectClass             *object_class;
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;
  gchar                    *composition =
    "<?xml version='1.0' encoding='UTF-8'?>"
    "<gegl>"
    "<node operation='gegl:apply-lens'>"
    "  <params>"
    "    <param name='refraction_index'>1.7</param>"
    "    <param name='keep_surroundings'>false</param>"
    "    <param name='background_color'>rgba(0, 0.50196, 0.50196, 0.75)</param>"
    "  </params>"
    "</node>"
    "<node operation='gegl:load'>"
    "  <params>"
    "    <param name='path'>standard-input.png</param>"
    "  </params>"
    "</node>"
    "</gegl>";

262
  object_class    = G_OBJECT_CLASS (klass);
263 264 265
  operation_class = GEGL_OPERATION_CLASS (klass);
  filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);

266
  object_class->finalize                   = finalize;
267
  operation_class->threaded                = FALSE;
268
  operation_class->prepare                 = prepare;
269
  operation_class->get_required_for_output = get_required_for_output;
270 271 272 273
  filter_class->process                    = process;

  gegl_operation_class_set_keys (operation_class,
    "name",        "gegl:apply-lens",
274
    "title",       _("Apply Lens"),
275
    "categories",  "map",
276
    "reference-hash", "4230b1cd886d335503ff436f97b82465",
277
    "license",     "GPL3+",
278 279
    "description", _("Simulates the optical distortion caused by having "
                     "an elliptical lens over the image"),
280 281 282 283 284
    "reference-composition", composition,
    NULL);
}

#endif