[gegl] apply-lens: some improvements



commit 8cbdef878f568266b5adde37a80c58757f27d762
Author: Thomas Manni <thomas manni free fr>
Date:   Thu Aug 6 19:24:39 2015 +0200

    apply-lens: some improvements
    
    * work with float instead of uint8
    * do not allocate twice the size of the whole input
    * do not use the whole input extent as cached region
    * use sampler to fetch projected input pixels
    * use iterator on the input/output buffers roi

 operations/common/apply-lens.c               |  326 ++++++++++++--------------
 tests/compositions/reference/apply-lens.png  |  Bin 311382 -> 1091577 bytes
 tests/compositions/reference/apply-lens2.png |  Bin 212757 -> 933426 bytes
 tests/compositions/reference/apply-lens3.png |  Bin 454266 -> 1138856 bytes
 4 files changed, 148 insertions(+), 178 deletions(-)
---
diff --git a/operations/common/apply-lens.c b/operations/common/apply-lens.c
index e4d28fb..6394dfd 100644
--- a/operations/common/apply-lens.c
+++ b/operations/common/apply-lens.c
@@ -21,6 +21,11 @@
  * 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>
+ * 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...
  */
 
 #include "config.h"
@@ -35,7 +40,7 @@ property_boolean (keep_surroundings, _("Keep original surroundings"), FALSE)
   description(_("Keep image unchanged, where not affected by the lens."))
 
 property_color (background_color, _("Background color"), "none")
-  //ui_meta ("role", "color-secondary")
+  ui_meta ("role", "color-secondary")
 
 #else
 
@@ -44,224 +49,185 @@ property_color (background_color, _("Background color"), "none")
 
 #include "gegl-op.h"
 #include <math.h>
-#include <stdio.h>
+
+typedef struct
+{
+  gfloat  bg_color[4];
+  gdouble a, b, c;
+  gdouble asqr, bsqr, csqr;
+} AlParamsType;
 
 /**
- * Computes the projected position (projx, projy) of the
- * original point (x, y) after passing through the lens
+ * Computes the original position (ox, oy) of the
+ * distorted point (x, y) after passing through the lens
  * 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.
- *
- * @param a2 semiaxis a
- * @param b2 semiaxis b
- * @param c2 semiaxis c
- * @param x coordinate x
- * @param y coordinate y
- * @param refraction refraction index
- * @param projy inout of the projection x
- * @param projy inout of the projection y
  */
 static void
-find_projected_pos (gfloat  a2,
-                    gfloat  b2,
-                    gfloat  c2,
-                    gfloat  x,
-                    gfloat  y,
-                    gfloat  refraction,
-                    gfloat *projx,
-                    gfloat *projy)
+find_undistorted_pos (gdouble       x,
+                      gdouble       y,
+                      gdouble       refraction,
+                      AlParamsType *params,
+                      gdouble      *ox,
+                      gdouble      *oy)
 {
-  gfloat z;
-  gfloat nxangle, nyangle, theta1, theta2;
-  gfloat ri1 = 1.0;
-  gfloat ri2 = refraction;
+  gdouble z;
+  gdouble nxangle, nyangle, theta1, theta2;
+  gdouble ri1 = 1.0;
+  gdouble ri2 = refraction;
 
-  z = sqrt ((1 - x * x / a2 - y * y / b2) * c2);
+  z = sqrt ((1 - x * x / params->asqr - y * y / params->bsqr) * params->csqr);
 
   nxangle = acos (x / sqrt(x * x + z * z));
-  theta1 = G_PI / 2 - nxangle;
+  theta1 = G_PI / 2.0 - nxangle;
   theta2 = asin (sin (theta1) * ri1 / ri2);
-  theta2 = G_PI / 2 - nxangle - theta2;
-  *projx = x - tan (theta2) * z;
+  theta2 = G_PI / 2.0 - nxangle - theta2;
+  *ox = x - tan (theta2) * z;
 
   nyangle = acos (y / sqrt (y * y + z * z));
-  theta1 = G_PI / 2 - nyangle;
+  theta1 = G_PI / 2.0 - nyangle;
   theta2 = asin (sin (theta1) * ri1 / ri2);
-  theta2 = G_PI / 2 - nyangle - theta2;
-  *projy = y - tan (theta2) * z;
+  theta2 = G_PI / 2.0 - nyangle - theta2;
+  *oy = y - tan (theta2) * z;
 }
 
-/**
- * Prepare function of gegl filter.
- * @param operation given Gegl operation
- */
 static void
 prepare (GeglOperation *operation)
 {
-  const Babl *format = gegl_operation_get_source_format (operation, "input");
+  GeglProperties *o = GEGL_PROPERTIES (operation);
+  const Babl *format = babl_format ("RGBA float");
+
+  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);
 
   gegl_operation_set_format (operation, "input", format);
   gegl_operation_set_format (operation, "output", format);
 }
 
-/**
- * Returns the cached region. This is an area filter, which acts on the whole image.
- * @param operation given Gegl operation
- * @param roi the rectangle of interest
- * @return result the new rectangle
- */
+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);
+}
+
 static GeglRectangle
-get_cached_region (GeglOperation       *operation,
-                   const GeglRectangle *roi)
+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;
+  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;
 }
 
-/**
- * Process the gegl filter
- * @param operation the given Gegl operation
- * @param input the input buffer.
- * @param output the output buffer.
- * @param result the region of interest.
- * @param level the level of detail
- * @return True, if the filter was successfull applied.
- */
 static gboolean
 process (GeglOperation       *operation,
          GeglBuffer          *input,
          GeglBuffer          *output,
-         const GeglRectangle *result,
+         const GeglRectangle *roi,
          gint                 level)
 {
-  GeglProperties         *o = GEGL_PROPERTIES (operation);
-  const Babl  *input_format = gegl_buffer_get_format (input);
-  const int bytes_per_pixel = babl_format_get_bytes_per_pixel (input_format);
-
-  /**
-   * src_buf, dst_buf:
-   * Input- and output-buffers, containing the whole selection,
-   * the filter should be applied on.
-   *
-   * src_pixel, dst_pixel:
-   * pointers to the current source- and destination-pixel.
-   * Using these pointers, the reading and writing can be delayed till the
-   * end of the loop. Hence src_pixel can also point to background_color if
-   * necessary.
-   */
-  guint8    *src_buf, *dst_buf, *src_pixel, *dst_pixel, background_color[bytes_per_pixel];
-
-  /**
-   * (x, y): Position of the pixel we compute at the moment.
-   * (width, height): Dimensions of the lens
-   * pixel_offset: For calculating the pixels position in src_buf / dst_buf.
-   */
-  gint         x, y,
-               width, height,
-               pixel_offset, projected_pixel_offset;
-
-  /**
-   * Further parameters that are needed to calculate the position of a projected
-   * further pixel at (x, y).
-   */
-  gfloat       a, b, c,
-               asqr, bsqr, csqr,
-               dx, dy, dysqr,
-               projected_x, projected_y,
-               refraction_index;
-
-  gboolean     keep_surroundings;
-
-  width = result->width;
-  height = result->height;
-
-  refraction_index = o->refraction_index;
-  keep_surroundings = o->keep_surroundings;
-  gegl_color_get_pixel (o->background_color, input_format, background_color);
-
-  a = 0.5 * width;
-  b = 0.5 * height;
-  c = MIN (a, b);
-  asqr = a * a;
-  bsqr = b * b;
-  csqr = c * c;
-
-  /**
-   * Todo: We might want to change the buffers sizes, as the memory consumption
-   * could be rather large.However, due to the lens, it might happen, that one pixel from the
-   * images center gets stretched to the whole image, so we will still need
-   * at least a src_buf of size (width/2) * (height/2) * 4 to be able to
-   * process one quarter of the image.
-   */
-  src_buf = gegl_malloc (width * height * bytes_per_pixel);
-  dst_buf = gegl_malloc (width * height * bytes_per_pixel);
-
-  gegl_buffer_get (input, result, 1.0, input_format, src_buf,
-                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
-
-  for (y = 0; y < height; y++)
+  GeglProperties       *o = GEGL_PROPERTIES (operation);
+  AlParamsType    *params = (AlParamsType *) o->user_data;
+  const Babl      *format = babl_format ("RGBA float");
+
+  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,
+                                   GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+
+  gegl_buffer_iterator_add (iter, input, roi, level, format,
+                            GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+
+  while (gegl_buffer_iterator_next (iter))
     {
-      /* dy is the current pixels distance (in y-direction) of the lenses center */
-      dy = -((gfloat)y - b + 0.5);
-
-      /**
-       * To determine, whether the pixel is within the elliptical region affected
-       * by the lens, we furthermore need the squared distance. So we calculate it
-       * once for each row.
-       */
-      dysqr = dy * dy;
-      for (x = 0; x < width; x++)
-        {
-          /* Again we need to calculate the pixels distance from the lens dx. */
-          dx = (gfloat)x - a + 0.5;
+      gfloat *out_pixel = iter->data[0];
+      gfloat *in_pixel  = iter->data[1];
 
-          /* Given x and y we can now determine the pixels offset in our buffer. */
-          pixel_offset = (x + y * width) * bytes_per_pixel;
+      for (y = iter->roi->y; y < iter->roi->y + iter->roi->height; y++)
+        {
+          gdouble dy, dysqr;
 
-          /* As described above, we only read and write image data once. */
-          dst_pixel = &dst_buf[pixel_offset];
+          dy = -((gdouble) y - params->b + 0.5);
+          dysqr = dy * dy;
 
-          if (dysqr < (bsqr - (bsqr * dx * dx) / asqr))
+          for (x = iter->roi->x; x < iter->roi->x + iter->roi->width; x++)
             {
-              /**
-               * If (x, y) is inside the affected region, we can find its projected
-               * position, calculate the projected_pixel_offset and set the src_pixel
-               * to where we want to copy the pixel-data from.
-               */
-              find_projected_pos (asqr, bsqr, csqr, dx, dy, refraction_index,
-                                  &projected_x, &projected_y);
-
-              projected_pixel_offset = ( (gint)(-projected_y + b) * width +
-                                         (gint)( projected_x + a) ) * bytes_per_pixel;
-
-              src_pixel = &src_buf[projected_pixel_offset];
-            }
-          else
-            {
-              /**
-               * 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 (keep_surroundings)
-                src_pixel = &src_buf[pixel_offset];
+              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);
+                }
               else
-                src_pixel = background_color;
+                {
+                  /**
+                   * 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;
             }
-
-          /**
-           * At the end, we can copy the src_pixel (which was determined above), to
-           * dst_pixel.
-           */
-          memcpy (dst_pixel, src_pixel, bytes_per_pixel);
         }
-  }
-
-  gegl_buffer_set (output, result, 0, gegl_buffer_get_format (output), dst_buf,
-                   GEGL_AUTO_ROWSTRIDE);
+    }
 
-  gegl_free (dst_buf);
-  gegl_free (src_buf);
+  g_object_unref (sampler);
 
   return TRUE;
 }
@@ -269,6 +235,7 @@ process (GeglOperation       *operation,
 static void
 gegl_op_class_init (GeglOpClass *klass)
 {
+  GObjectClass             *object_class;
   GeglOperationClass       *operation_class;
   GeglOperationFilterClass *filter_class;
   gchar                    *composition =
@@ -288,12 +255,14 @@ gegl_op_class_init (GeglOpClass *klass)
     "</node>"
     "</gegl>";
 
+  object_class    = G_OBJECT_CLASS (klass);
   operation_class = GEGL_OPERATION_CLASS (klass);
   filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);
 
+  object_class->finalize                   = finalize;
   operation_class->threaded                = FALSE;
   operation_class->prepare                 = prepare;
-  operation_class->get_cached_region       = get_cached_region;
+  operation_class->get_required_for_output = get_required_for_output;
   filter_class->process                    = process;
 
   gegl_operation_class_set_keys (operation_class,
@@ -301,7 +270,8 @@ gegl_op_class_init (GeglOpClass *klass)
     "title",       _("Apply Lens"),
     "categories",  "map",
     "license",     "GPL3+",
-    "description", _("Simulates the optical distoration caused by having an elliptical lens over the image"),
+    "description", _("Simulates the optical distortion caused by having "
+                     "an elliptical lens over the image"),
     "reference-composition", composition,
     NULL);
 }
diff --git a/tests/compositions/reference/apply-lens.png b/tests/compositions/reference/apply-lens.png
index 045284d..5e65ee3 100644
Binary files a/tests/compositions/reference/apply-lens.png and b/tests/compositions/reference/apply-lens.png 
differ
diff --git a/tests/compositions/reference/apply-lens2.png b/tests/compositions/reference/apply-lens2.png
index f560a14..13b4566 100644
Binary files a/tests/compositions/reference/apply-lens2.png and 
b/tests/compositions/reference/apply-lens2.png differ
diff --git a/tests/compositions/reference/apply-lens3.png b/tests/compositions/reference/apply-lens3.png
index 088ab76..37ffe37 100644
Binary files a/tests/compositions/reference/apply-lens3.png and 
b/tests/compositions/reference/apply-lens3.png differ


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]