motion-blur-circular.c 9.3 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 60
#define GEGL_OP_AREA_FILTER
#define GEGL_OP_C_FILE "motion-blur-circular.c"
61

62
#include "gegl-op.h"
63

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

#define NOMINAL_NUM_IT  100
#define SQRT_2          1.41

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

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

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

  if (whole_region != NULL)
    {
84 85 86 87 88 89 90 91
      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));
92

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

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
      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"));
}


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

124 125 126 127 128 129 130
  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
131 132
compute_phi (gdouble xr,
             gdouble yr)
133 134
{
  gdouble phi;
135

136 137 138 139 140 141 142 143 144 145 146 147 148
  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;
    }
149

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

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

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

171 172 173 174 175 176
  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);


177 178 179 180 181 182 183 184 185 186
  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;

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

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

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

195 196 197 198 199 200 201 202 203
  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;

204 205
          gdouble xr = x - center_x;
          gdouble yr = y - center_y;
206 207 208 209 210 211 212 213 214 215
          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)
216
            n = NOMINAL_NUM_IT + (gint) sqrt (n - NOMINAL_NUM_IT);
217 218 219 220 221 222 223 224 225 226 227

          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);

228 229
              gfloat ix = center_x + radius * c_val;
              gfloat iy = center_y + radius * s_val;
230 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

              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;
            }
        }
    }

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

  g_free (in_buf);
  g_free (out_buf);

  return  TRUE;
}

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

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

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

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

#endif