whirl-pinch.c 7.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/* This file is an image processing operation for GEGL
 *
 * 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.
 *
 * GEGL 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with GEGL; if not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2010 Barak Itkin <lightningismyname@gmail.org>
 *
 * Based on "Whirl and Pinch" GIMP plugin
 * Copyright (C) 1997 Federico Mena Quintero
 * Copyright (C) 1997 Scott Goehring
 *
 * The workshop/mirrors.c operation by Alexia Death and Øyvind Kolås
 * was used as a template for this op file.
 */

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

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


34 35
#ifdef GEGL_PROPERTIES

36 37 38 39 40 41 42 43 44 45 46 47 48
property_double (whirl, _("Whirl"), 90.0)
    description (_("Whirl angle (degrees)"))
    ui_range    (-720, 720)
    ui_meta     ("unit", "degree")

property_double (pinch, _("Pinch"), 0.0)
    description (_("Pinch amount"))
    value_range (-1.0, 1.0)

property_double (radius, _("Radius"), 1.0)
    description(_("Radius (1.0 is the largest circle that fits in the "
               "image, and 2.0 goes all the way to the corners)"))
    value_range (0.0, 2.0)
49 50 51

#else

52 53
#define GEGL_OP_FILTER
#define GEGL_OP_C_FILE "whirl-pinch.c"
54

55
#include "gegl-op.h"
56 57
#include <math.h>

58 59 60
/* This function is a slightly modified version from the one in the
 * original plugin
 */
61
static gboolean
62 63 64 65 66 67 68 69 70 71 72
calc_undistorted_coords (gdouble  wx,
                         gdouble  wy,
                         gdouble  cen_x,
                         gdouble  cen_y,
                         gdouble  scale_x,
                         gdouble  scale_y,
                         gdouble  whirl,
                         gdouble  pinch,
                         gdouble  wpradius,
                         gdouble *x,
                         gdouble *y)
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 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
{
  gdouble  dx, dy;
  gdouble  d, factor;
  gdouble  dist;
  gdouble  ang, sina, cosa;
  gboolean inside;

  gdouble  radius = MAX(cen_x, cen_y);
  gdouble  radius2 = radius * radius * wpradius;
  /* Distances to center, scaled */

  dx = (wx - cen_x) * scale_x;
  dy = (wy - cen_y) * scale_y;

  /* Distance^2 to center of *circle* (scaled ellipse) */

  d = dx * dx + dy * dy;

  /*  If we are inside circle, then distort.
   *  Else, just return the same position
   */

  inside = (d < radius2);

  /* If d is 0, then we are exactly at the center, so the transform has
   * no effect. The original version of the gimp plugin simply created
   * an offset of the original points to avoid this issue, but that is
   * less accurate...
   */
  if (inside && d > 0)
    {
      dist = sqrt(d / wpradius) / radius;

      /* Pinch */

      factor = pow (sin (G_PI_2 * dist), -pinch);

      dx *= factor;
      dy *= factor;

      /* Whirl */

      factor = 1.0 - dist;

      ang = whirl * factor * factor;

      sina = sin (ang);
      cosa = cos (ang);

      *x = (cosa * dx - sina * dy) / scale_x + cen_x;
      *y = (sina * dx + cosa * dy) / scale_y + cen_y;
    }
  else
    {
      *x = wx;
      *y = wy;
    }

  return inside;
}

/* Apply the actual transform */

static void
137 138 139 140 141 142 143 144 145 146
apply_whirl_pinch (gdouble              whirl,
                   gdouble              pinch,
                   gdouble              radius,
                   gdouble              cen_x,
                   gdouble              cen_y,
                   const Babl          *format,
                   GeglBuffer          *src,
                   GeglRectangle       *in_boundary,
                   GeglBuffer          *dst,
                   GeglRectangle       *boundary,
147 148 149 150 151 152
                   const GeglRectangle *roi)
{
  gfloat *dst_buf;
  gint row, col;
  gdouble scale_x, scale_y;
  gdouble cx, cy;
153
  GeglSampler *sampler;
154 155 156 157 158 159 160

  /* Get buffer in which to place dst pixels. */
  dst_buf = g_new0 (gfloat, roi->width * roi->height * 4);

  whirl = whirl * G_PI / 180;

  scale_x = 1.0;
161
  scale_y = (gdouble) in_boundary->width / in_boundary->height;
162
  sampler = gegl_buffer_sampler_new (src, babl_format ("RaGaBaA float"),
163
                                     GEGL_SAMPLER_NOHALO);
164 165 166

  for (row = 0; row < roi->height; row++) {
    for (col = 0; col < roi->width; col++) {
167 168 169 170 171 172 173 174 175 176 177 178 179
        GeglMatrix2 scale;
#define gegl_unmap(u,v,du,dv) \
        { \
          calc_undistorted_coords (u, v,\
                                   cen_x, cen_y,\
                                   scale_x, scale_y,\
                                   whirl, pinch, radius,\
                                   &cx, &cy);\
          du=cx;dv=cy;\
        }
        gegl_sampler_compute_scale (scale, roi->x + col, roi->y + row);
        gegl_unmap (roi->x + col, roi->y + row, cx, cy);

180
        gegl_sampler_get (sampler, cx, cy, &scale, &dst_buf[(row * roi->width + col) * 4], GEGL_ABYSS_NONE);
181 182 183 184
    } /* for */
  } /* for */

  /* Store dst pixels. */
185
  gegl_buffer_set (dst, roi, 0, format, dst_buf, GEGL_AUTO_ROWSTRIDE);
186 187

  g_free (dst_buf);
188
  g_object_unref (sampler);
189 190 191 192 193 194 195 196 197 198 199 200
}

/*****************************************************************************/

/* Compute the region for which this operation is defined.
 */
static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
  GeglRectangle  result = {0,0,0,0};
  GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");

201
  if (!in_rect)
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    return result;
  else
    return *in_rect;
}

/* Compute the input rectangle required to compute the specified region of interest (roi).
 */
static GeglRectangle
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *roi)
{
  GeglRectangle *result = gegl_operation_source_get_bounding_box (operation, "input");

  return *result;
}

/* Specify the input and output buffer formats.
 */
static void
prepare (GeglOperation *operation)
{
  gegl_operation_set_format (operation, "input", babl_format ("RaGaBaA float"));
  gegl_operation_set_format (operation, "output", babl_format ("RaGaBaA float"));
}

/* Perform the specified operation.
 */
static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
234 235
         const GeglRectangle *result,
         gint                 level)
236
{
237 238 239
  GeglProperties *o        = GEGL_PROPERTIES (operation);
  GeglRectangle   boundary = gegl_operation_get_bounding_box (operation);
  const Babl     *format   = babl_format ("RaGaBaA float");
240 241

  apply_whirl_pinch (o->whirl,
242 243 244 245 246 247
                     o->pinch,
                     o->radius,
                     boundary.width / 2.0,
                     boundary.height / 2.0,
                     format,
                     input,
248
                     &boundary,
249
                     output,
250
                     &boundary,
251
                     result);
252 253 254 255 256
  return TRUE;
}


static void
257
gegl_op_class_init (GeglOpClass *klass)
258 259 260 261 262 263 264 265 266 267 268 269
{
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;

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

  filter_class->process = process;
  operation_class->prepare = prepare;
  operation_class->get_bounding_box = get_bounding_box;
  operation_class->get_required_for_output = get_required_for_output;

270
  gegl_operation_class_set_keys (operation_class,
271
    "name",               "gegl:whirl-pinch",
272
    "title",              _("Whirl Pinch"),
273
    "categories",         "distort",
274
    "license",            "GPL3+",
275
    "position-dependent", "true",
276 277
    "description", _("Distort an image by whirling and pinching"),
    NULL);
278 279 280
}

#endif