spherize.c 10.1 KB
Newer Older
Ell's avatar
Ell committed
1 2
/* This file is an image processing operation for GEGL
 *
Ell's avatar
Ell committed
3 4 5 6
 * GEGL is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
Ell's avatar
Ell committed
7
 *
Ell's avatar
Ell committed
8
 * GEGL is distributed in the hope that it will be useful,
Ell's avatar
Ell committed
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Ell's avatar
Ell committed
10 11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
Ell's avatar
Ell committed
12
 *
Ell's avatar
Ell committed
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/>.
Ell's avatar
Ell committed
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
 *
 * Copyright (C) 2017 Ell
 */

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

#ifdef GEGL_PROPERTIES

enum_start (gegl_spherize_mode)
  enum_value (GEGL_SPHERIZE_MODE_RADIAL,     "radial",     N_("Radial"))
  enum_value (GEGL_SPHERIZE_MODE_HORIZONTAL, "horizontal", N_("Horizontal"))
  enum_value (GEGL_SPHERIZE_MODE_VERTICAL,   "vertical",   N_("Vertical"))
enum_end (GeglSpherizeMode)

property_enum (mode, _("Mode"),
               GeglSpherizeMode, gegl_spherize_mode,
               GEGL_SPHERIZE_MODE_RADIAL)
  description (_("Displacement mode"))

35
property_double (angle_of_view, _("Angle of view"), 0.0)
Ell's avatar
Ell committed
36 37
  description (_("Camera angle of view"))
  value_range (0.0, 180.0)
38
  ui_meta     ("unit", "degree")
Ell's avatar
Ell committed
39

40
property_double (curvature, _("Curvature"), 1.0)
41
  description (_("Spherical cap apex angle, as a fraction of the co-angle of view"))
42 43 44 45 46 47 48 49 50
  value_range (0.0, 1.0) /* note that the code can handle negative curvatures
                          * (in the [-1, 0) range), in which case the image is
                          * wrapped around the back face, rather than the front
                          * face, of the spherical cap.  we disable negative
                          * curvatures atm, in particular, since they produce
                          * the same result when the angle of view is 0, and
                          * since their upper-bound, wrt the angle of view, is
                          * arbitrary.
                          */
Ell's avatar
Ell committed
51

52 53 54 55
property_double (amount, _("Amount"), 1.0)
  description (_("Displacement scaling factor (negative values refer to the inverse displacement)"))
  value_range (-1.0, 1.0)

Ell's avatar
Ell committed
56 57
property_enum (sampler_type, _("Resampling method"),
  GeglSamplerType, gegl_sampler_type, GEGL_SAMPLER_LINEAR)
58
  description (_("Mathematical method for reconstructing pixel values"))
Ell's avatar
Ell committed
59 60 61 62 63 64 65 66 67 68

#else

#define GEGL_OP_FILTER
#define GEGL_OP_NAME     spherize
#define GEGL_OP_C_SOURCE spherize.c

#include "gegl-op.h"
#include <math.h>

69 70
#define EPSILON 1e-10

Ell's avatar
Ell committed
71 72 73 74
static gboolean
is_nop (GeglOperation *operation)
{
  GeglProperties *o = GEGL_PROPERTIES (operation);
75
  GeglRectangle  *in_rect;
Ell's avatar
Ell committed
76

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
  if (fabs (o->curvature) < EPSILON || fabs (o->amount) < EPSILON)
    return TRUE;

  in_rect = gegl_operation_source_get_bounding_box (operation, "input");

  switch (o->mode)
    {
    case GEGL_SPHERIZE_MODE_RADIAL:
      return in_rect->width < 1 || in_rect->height < 1;

    case GEGL_SPHERIZE_MODE_HORIZONTAL:
      return in_rect->width < 1;

    case GEGL_SPHERIZE_MODE_VERTICAL:
      return in_rect->height < 1;
    }

  g_return_val_if_reached (TRUE);
Ell's avatar
Ell committed
95 96 97 98 99 100 101 102 103
}

static GeglRectangle
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *roi)
{
  GeglRectangle result = *roi;

104
  if (! is_nop (operation))
Ell's avatar
Ell committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    {
      GeglProperties *o       = GEGL_PROPERTIES (operation);
      GeglRectangle  *in_rect = gegl_operation_source_get_bounding_box (operation, "input");

      if (in_rect)
        {
          switch (o->mode)
            {
            case GEGL_SPHERIZE_MODE_RADIAL:
              result = *in_rect;
              break;

            case GEGL_SPHERIZE_MODE_HORIZONTAL:
              result.x     = in_rect->x;
              result.width = in_rect->width;
              break;

            case GEGL_SPHERIZE_MODE_VERTICAL:
              result.y      = in_rect->y;
              result.height = in_rect->height;
              break;
            }
        }
    }

  return result;
}

static gboolean
parent_process (GeglOperation        *operation,
                GeglOperationContext *context,
                const gchar          *output_prop,
                const GeglRectangle  *result,
                gint                  level)
{
  if (is_nop (operation))
    {
      GObject *input;

      input = gegl_operation_context_get_object (context, "input");

      gegl_operation_context_set_object (context, "output", input);

      return TRUE;
    }

  return GEGL_OPERATION_CLASS (gegl_op_parent_class)->process (operation,
                                                               context,
                                                               output_prop,
                                                               result, level);
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
         const GeglRectangle *roi,
         gint                 level)
{
  GeglProperties      *o      = GEGL_PROPERTIES (operation);
165
  const Babl          *format = gegl_operation_get_format (operation, "output");
Ell's avatar
Ell committed
166 167 168 169 170 171 172
  GeglSampler         *sampler;
  GeglBufferIterator  *iter;
  const GeglRectangle *in_extent;
  gdouble              cx, cy;
  gdouble              dx = 0.0, dy = 0.0;
  gdouble              coangle_of_view_2;
  gdouble              focal_length;
173
  gdouble              curvature_sign;
174
  gdouble              cap_angle_2;
Ell's avatar
Ell committed
175
  gdouble              cap_radius;
176
  gdouble              cap_depth;
177 178
  gdouble              factor;
  gdouble              f, f2, r, r_inv, r2, p, f_p, f_p2, f_pf, a, a_inv, sgn;
179
  gboolean             perspective;
180
  gboolean             inverse;
Ell's avatar
Ell committed
181 182 183 184 185 186
  gint                 i, j;

  sampler = gegl_buffer_sampler_new_at_level (input, format,
                                              o->sampler_type, level);

  iter = gegl_buffer_iterator_new (output, roi, level, format,
187
                                   GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 2);
Ell's avatar
Ell committed
188 189 190 191 192 193 194 195 196 197 198 199

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

  in_extent = gegl_operation_source_get_bounding_box (operation, "input");

  cx = in_extent->x + in_extent->width  / 2.0;
  cy = in_extent->y + in_extent->height / 2.0;

  if (o->mode == GEGL_SPHERIZE_MODE_RADIAL ||
      o->mode == GEGL_SPHERIZE_MODE_HORIZONTAL)
    {
200
      dx = 2.0 / (in_extent->width - 1);
Ell's avatar
Ell committed
201 202 203 204
    }
  if (o->mode == GEGL_SPHERIZE_MODE_RADIAL ||
      o->mode == GEGL_SPHERIZE_MODE_VERTICAL)
    {
205
      dy = 2.0 / (in_extent->height - 1);
Ell's avatar
Ell committed
206 207 208 209
    }

  coangle_of_view_2 = MAX (180.0 - o->angle_of_view, 0.01) * G_PI / 360.0;
  focal_length      = tan (coangle_of_view_2);
210 211
  curvature_sign    = o->curvature > 0.0 ? +1.0 : -1.0;
  cap_angle_2       = fabs (o->curvature) * coangle_of_view_2;
212
  cap_radius        = 1.0 / sin (cap_angle_2);
213 214
  cap_depth         = curvature_sign * cap_radius * cos (cap_angle_2);
  factor            = fabs (o->amount);
215 216 217 218 219 220 221 222 223 224 225 226

  f     = focal_length;
  f2    = f * f;
  r     = cap_radius;
  r_inv = 1 / r;
  r2    = r * r;
  p     = cap_depth;
  f_p   = f + p;
  f_p2  = f_p * f_p;
  f_pf  = f_p * f;
  a     = cap_angle_2;
  a_inv = 1 / a;
227
  sgn   = curvature_sign;
228 229 230

  perspective = o->angle_of_view > EPSILON;
  inverse     = o->amount < 0.0;
Ell's avatar
Ell committed
231 232 233

  while (gegl_buffer_iterator_next (iter))
    {
234 235 236
      gfloat       *out_pixel = iter->items[0].data;
      const gfloat *in_pixel  = iter->items[1].data;
      GeglRectangle *roi = &iter->items[0].roi;
Ell's avatar
Ell committed
237 238
      gfloat        x,  y;

239
      y = dy * (roi->y + 0.5 - cy);
Ell's avatar
Ell committed
240

241
      for (j = roi->y; j < roi->y + roi->height; j++, y += dy)
Ell's avatar
Ell committed
242
        {
243
          x = dx * (roi->x + 0.5 - cx);
Ell's avatar
Ell committed
244

245
          for (i = roi->x; i < roi->x + roi->width; i++, x += dx)
Ell's avatar
Ell committed
246 247 248 249 250
            {
              gfloat d2;

              d2 = x * x + y * y;

251
              if (d2 > EPSILON && d2 < 1.0 - EPSILON)
Ell's avatar
Ell committed
252 253
                {
                  gdouble d     = sqrt (d2);
254
                  gdouble src_d = d;
Ell's avatar
Ell committed
255 256
                  gdouble src_x, src_y;

257
                  if (! inverse)
258 259
                    {
                      gdouble d2_f2 = d2 + f2;
Ell's avatar
Ell committed
260

261
                      if (perspective)
262
                        src_d = (f_pf - sgn * sqrt (d2_f2 * r2 - f_p2 * d2)) * d / d2_f2;
Ell's avatar
Ell committed
263

264 265 266
                      src_d = (G_PI_2 - acos (src_d * r_inv)) * a_inv;
                    }
                  else
Ell's avatar
Ell committed
267
                    {
268 269 270
                      src_d = r * cos (G_PI_2 - src_d * a);

                      if (perspective)
271
                        src_d = f * src_d / (f_p - sgn * sqrt (r2 - src_d * src_d));
Ell's avatar
Ell committed
272 273
                    }

274 275 276
                  if (factor < 1.0)
                    src_d = d + (src_d - d) * factor;

277 278
                  src_x = dx ? cx + src_d * x / (dx * d) :
                               i + 0.5;
Ell's avatar
Ell committed
279
                  src_y = dy ? cy + src_d * y / (dy * d) :
280 281
                               j + 0.5;

Ell's avatar
Ell committed
282 283 284 285 286
                  gegl_sampler_get (sampler, src_x, src_y,
                                    NULL, out_pixel, GEGL_ABYSS_NONE);
                }
              else
                {
287
                  memcpy (out_pixel, in_pixel, sizeof (gfloat) * 4);
Ell's avatar
Ell committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
                }

              out_pixel += 4;
              in_pixel  += 4;
            }
        }
    }

  g_object_unref (sampler);

  return TRUE;
}

static void
gegl_op_class_init (GeglOpClass *klass)
{
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;

  operation_class = GEGL_OPERATION_CLASS (klass);
  filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);

310 311 312
  operation_class->get_invalidated_by_change = get_required_for_output;
  operation_class->get_required_for_output   = get_required_for_output;
  operation_class->process                   = parent_process;
Ell's avatar
Ell committed
313

314
  filter_class->process                      = process;
Ell's avatar
Ell committed
315 316 317 318 319 320

  gegl_operation_class_set_keys (operation_class,
    "name",               "gegl:spherize",
    "title",              _("Spherize"),
    "categories",         "distort:map",
    "position-dependent", "true",
321
    "description",        _("Wrap image around a spherical cap"),
Ell's avatar
Ell committed
322 323 324 325
    NULL);
}

#endif