motion-blur-circular.c 9.34 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* 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/>.
 *
16
 * Copyright (C) 2013 Téo Mazars  <teo.mazars@ensimag.fr>
17 18
 *
 * This operation is inspired by and uses parts of blur-motion.c
19
 * from GIMP 2.8.4:
20
 *
21 22 23 24 25 26 27 28 29 30
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 * Copyright (C) 1996 Torsten Martinsen       <torsten@danbbs.dk>
 * Copyright (C) 1996 Federico Mena Quintero  <federico@nuclecu.unam.mx>
 * Copyright (C) 1996 Heinz W. Werntges       <quartic@polloux.fciencias.unam.mx>
 * Copyright (C) 1997 Daniel Skarda           <0rfelyus@atrey.karlin.mff.cuni.cz>
 * Copyright (C) 2007 Joerg Gittinger         <sw@gittingerbox.de>
 *
 * This operation is also inspired by GEGL's blur-motion-linear.c :
 *
 * Copyright (C) 2006 Øyvind Kolås  <pippin@gimp.org>
31 32
 */

33

34 35 36 37 38
#include "config.h"

#include <glib/gi18n-lib.h>
#include <math.h>

39 40
#ifdef GEGL_PROPERTIES

41 42 43
property_double (center_x, _("Center X"), 0.5)
    ui_range    (0.0, 1.0)
    ui_meta     ("unit", "relative-coordinate")
44 45
    ui_meta     ("axis", "x")

46 47 48
property_double (center_y, _("Center Y"), 0.5)
    ui_range    (0.0, 1.0)
    ui_meta     ("unit", "relative-coordinate")
49
    ui_meta     ("axis", "y")
50

51
/* FIXME: With a large angle, we lose AreaFilter's flavours */
52 53 54 55
property_double (angle, _("Angle"), 5.0)
    description (_("Rotation blur angle. A large angle may take some time to render"))
    value_range (-180.0, 180.0)
    ui_meta     ("unit", "degree")
56 57 58

#else

59
#define GEGL_OP_AREA_FILTER
60
#define GEGL_OP_NAME     motion_blur_circular
61
#define GEGL_OP_C_SOURCE motion-blur-circular.c
62

63
#include "gegl-op.h"
64

65
#define SQR(c) ((c) * (c))
66 67 68 69 70 71 72

#define NOMINAL_NUM_IT  100
#define SQRT_2          1.41

static void
prepare (GeglOperation *operation)
{
73
  GeglOperationAreaFilter *op_area = GEGL_OPERATION_AREA_FILTER (operation);
74
  GeglProperties              *o       = GEGL_PROPERTIES (operation);
75
  GeglRectangle           *whole_region;
76
  gdouble                  angle   = o->angle * G_PI / 180.0;
77

78 79 80
  while (angle < 0.0)
    angle += 2 * G_PI;

81
  whole_region = gegl_operation_source_get_bounding_box (operation, "input");
82 83 84

  if (whole_region != NULL)
    {
85 86 87 88 89 90 91 92
      gdouble center_x = gegl_coordinate_relative_to_pixel (o->center_x, 
                                                            whole_region->width);
      gdouble center_y = gegl_coordinate_relative_to_pixel (o->center_y,
                                                            whole_region->height);
      gdouble maxr_x = MAX (fabs (center_x - whole_region->x),
                            fabs (center_x - whole_region->x - whole_region->width));
      gdouble maxr_y = MAX (fabs (center_y - whole_region->y),
                            fabs (center_y - whole_region->y - whole_region->height));
93

94 95 96
      if (angle >= G_PI)
        angle = G_PI;

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
      op_area->left = op_area->right
        = ceil (maxr_y * sin (angle / 2.0)) + 1;

      op_area->top = op_area->bottom
        = ceil (maxr_x * sin (angle / 2.0)) + 1;
    }
  else
    {
      op_area->left   =
      op_area->right  =
      op_area->top    =
      op_area->bottom = 0;
    }

  gegl_operation_set_format (operation, "input",  babl_format ("RaGaBaA float"));
  gegl_operation_set_format (operation, "output", babl_format ("RaGaBaA float"));
}


116 117
static inline gfloat *
get_pixel_color (gfloat              *in_buf,
118
                 const GeglRectangle *rect,
119 120
                 gint                 x,
                 gint                 y)
121 122 123
{
  gint ix = x - rect->x;
  gint iy = y - rect->y;
124

125 126 127 128 129 130 131
  ix = CLAMP (ix, 0, rect->width  - 1);
  iy = CLAMP (iy, 0, rect->height - 1);

  return &in_buf[(iy * rect->width + ix) * 4];
}

static inline gdouble
132 133
compute_phi (gdouble xr,
             gdouble yr)
134 135
{
  gdouble phi;
136

137 138 139 140 141 142 143 144 145 146 147 148 149
  if (fabs (xr) > 0.00001)
    {
      phi = atan (yr / xr);
      if (xr < 0.0)
        phi = G_PI + phi;
    }
  else
    {
      if (yr >= 0.0)
        phi = G_PI_2;
      else
        phi = -G_PI_2;
    }
150

151 152 153 154 155 156 157 158 159 160
  return phi;
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
         const GeglRectangle *roi,
         gint                 level)
{
161
  GeglOperationAreaFilter *op_area = GEGL_OPERATION_AREA_FILTER (operation);
162
  GeglProperties          *o       = GEGL_PROPERTIES (operation);
163 164 165 166 167
  gfloat                  *in_buf, *out_buf, *out_pixel;
  gint                     x, y;
  GeglRectangle            src_rect;
  GeglRectangle           *whole_region;
  gdouble                  angle;
168
  gdouble                  center_x, center_y;
169

170
  whole_region = gegl_operation_source_get_bounding_box (operation, "input");
171

172 173 174 175 176 177
  center_x = gegl_coordinate_relative_to_pixel (
                    o->center_x, whole_region->width);
  center_y = gegl_coordinate_relative_to_pixel (
                    o->center_y, whole_region->height);


178 179 180 181 182 183 184 185 186 187
  src_rect = *roi;
  src_rect.x -= op_area->left;
  src_rect.y -= op_area->top;
  src_rect.width += op_area->left + op_area->right;
  src_rect.height += op_area->top + op_area->bottom;

  in_buf    = g_new  (gfloat, src_rect.width * src_rect.height * 4);
  out_buf   = g_new0 (gfloat, roi->width * roi->height * 4);
  out_pixel = out_buf;

188 189
  gegl_buffer_get (input, &src_rect, 1.0, babl_format ("RaGaBaA float"),
                   in_buf, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
190

191
  angle = o->angle * G_PI / 180.0;
192

193 194 195
  while (angle < 0.0)
    angle += 2 * G_PI;

196 197 198 199 200 201 202 203 204
  for (y = roi->y; y < roi->height + roi->y; ++y)
    {
      for (x = roi->x; x < roi->width + roi->x; ++x)
        {
          gint c, i;
          gdouble phi_base, phi_start, phi_step;
          gfloat sum[] = {0, 0, 0, 0};
          gint count = 0;

205 206
          gdouble xr = x - center_x;
          gdouble yr = y - center_y;
207 208 209 210 211 212 213 214 215 216
          gdouble radius  = sqrt (SQR (xr) + SQR (yr));

          /* This is not the "real" length, a bit shorter */
          gdouble arc_length = radius * angle * SQRT_2;

          /* ensure quality with small angles */
          gint n = MAX (ceil (arc_length), 3);

          /* performance concern */
          if (n > NOMINAL_NUM_IT)
217
            n = NOMINAL_NUM_IT + (gint) sqrt (n - NOMINAL_NUM_IT);
218 219 220 221 222 223 224 225 226 227 228

          phi_base = compute_phi (xr, yr);
          phi_start = phi_base + angle / 2.0;
          phi_step = angle / (gdouble) n;

          /* Iterate other the arc */
          for (i = 0; i < n; i++)
            {
              gfloat s_val = sin (phi_start - i * phi_step);
              gfloat c_val = cos (phi_start - i * phi_step);

229 230
              gfloat ix = center_x + radius * c_val;
              gfloat iy = center_y + radius * s_val;
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273

              if (ix >= whole_region->x && ix < whole_region->x + whole_region->width &&
                  iy >= whole_region->y && iy < whole_region->y + whole_region->height)
                {
                  /* do bilinear interpolation to get a nice smooth result */
                  gfloat dx = ix - floor (ix);
                  gfloat dy = iy - floor (iy);

                  gfloat *pix0, *pix1, *pix2, *pix3;
                  gfloat mixy0[4];
                  gfloat mixy1[4];

                  pix0 = get_pixel_color (in_buf, &src_rect, ix, iy);
                  pix1 = get_pixel_color (in_buf, &src_rect, ix+1, iy);
                  pix2 = get_pixel_color (in_buf, &src_rect, ix, iy+1);
                  pix3 = get_pixel_color (in_buf, &src_rect, ix+1, iy+1);

                  for (c = 0; c < 4; ++c)
                    {
                      mixy0[c] = dy * (pix2[c] - pix0[c]) + pix0[c];
                      mixy1[c] = dy * (pix3[c] - pix1[c]) + pix1[c];

                      sum[c] +=  dx * (mixy1[c] - mixy0[c]) + mixy0[c];
                    }

                  count++;
                }
            }

          if (count == 0)
            {
              gfloat *pix = get_pixel_color (in_buf, &src_rect, x, y);
              for (c = 0; c < 4; ++c)
                *out_pixel++ = pix[c];
            }
          else
            {
              for (c = 0; c < 4; ++c)
                *out_pixel++ = sum[c] / (gfloat) count;
            }
        }
    }

274
  gegl_buffer_set (output, roi, 0, babl_format ("RaGaBaA float"),
275
                   out_buf, GEGL_AUTO_ROWSTRIDE);
276 277 278 279 280 281 282 283

  g_free (in_buf);
  g_free (out_buf);

  return  TRUE;
}

static void
284
gegl_op_class_init (GeglOpClass *klass)
285 286 287 288 289 290 291 292
{
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;

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

  operation_class->prepare = prepare;
293
  filter_class->process    = process;
294 295

  gegl_operation_class_set_keys (operation_class,
296
      "name",               "gegl:motion-blur-circular",
297
      "title",              _("Circular Motion Blur"),
298 299
      "categories",         "blur",
      "position-dependent", "true",
300
      "license",            "GPL3+",
301 302
      "description", _("Circular motion blur"),
      NULL);
303 304 305
}

#endif