[gegl] operations: add gegl:bump-map
- From: Dov Grobgeld <dov src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] operations: add gegl:bump-map
- Date: Tue, 25 Jun 2013 19:59:12 +0000 (UTC)
commit b35a8f6f5ce1cbee558ddacfdb612580b117c290
Author: Dov Grobgeld <dov grobgeld gmail com>
Date: Tue Jun 25 22:58:42 2013 +0300
operations: add gegl:bump-map
operations/common/bump-map.c | 462 ++++++++++++++++++++++++++++++++++++++++++
1 files changed, 462 insertions(+), 0 deletions(-)
---
diff --git a/operations/common/bump-map.c b/operations/common/bump-map.c
new file mode 100644
index 0000000..679f1da
--- /dev/null
+++ b/operations/common/bump-map.c
@@ -0,0 +1,462 @@
+/* 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 (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This plug-in uses the algorithm described by John Schlag, "Fast
+ * Embossing Effects on Raster Image Data" in Graphics Gems IV (ISBN
+ * 0-12-336155-9). It takes a grayscale image to be applied as a
+ * bump-map to another image, producing a nice embossing effect.
+ *
+ * Ported to gegl by Dov Grobgeld <dov grobgeld gmail com>
+ *
+ * ToDo:
+ *
+ * - Make lut table size configurable?
+ * - Support and y-offset tiling of the bump map.
+ * - Make an opencl version.
+ * - Don't "upgrade" Y,YA and RGB images to RGBA as it is a waste of time.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <stdio.h>
+#include <math.h>
+
+#ifdef GEGL_CHANT_PROPERTIES
+
+gegl_chant_double(azimuth,_("Azimuth"),
+ 0.0,360.0,135.0,
+ _("azimuth"))
+gegl_chant_double(elevation,_("Elevation"),
+ 0.0,360.0,45.0,
+ _("elevation"))
+gegl_chant_double(depth,_("Depth"),
+ 0.0005,100,0.005,
+ _("depth"))
+gegl_chant_int(xofs,_("xofs"),
+ -1000,1000,0,
+ _("xofs"))
+gegl_chant_int(yofs,_("yofs"),
+ -1000,1000,0,
+ _("yofs"))
+gegl_chant_double(waterlevel,_("waterlevel"),
+ 0.0,1.0,0.0,
+ _("waterlevel"))
+gegl_chant_double(ambient,_("ambient"),
+ 0.0,1.0,0.0,
+ _("ambient"))
+gegl_chant_boolean(compensate,_("Compensate"),TRUE,_("Compensate"))
+gegl_chant_boolean(invert,_("Invert"),FALSE,_("invert"))
+
+gegl_chant_register_enum (gegl_bump_map_type)
+ enum_value (GEGL_BUMP_MAP_TYPE_LINEAR, "Linear")
+ enum_value (GEGL_BUMP_MAP_TYPE_SPHERICAL, "Cosinus")
+ enum_value (GEGL_BUMP_MAP_TYPE_SINUSOIDAL, "Sinusoidal")
+gegl_chant_register_enum_end (GeglBumpMapType)
+
+
+gegl_chant_enum (type,_("Type"),GeglBumpMapType,
+ gegl_bump_map_type, GEGL_BUMP_MAP_TYPE_LINEAR,
+ _("type"))
+gegl_chant_boolean(tiled,_("Tiled"),FALSE, _("tiled"))
+
+#else
+
+// Should the LUT table be a configurable property? Should it
+// be interpolated instead of by a plain lookup?
+#define LUT_TABLE_SIZE 8192
+#define GEGL_CHANT_TYPE_COMPOSER
+#define GEGL_CHANT_C_FILE "bump-map.c"
+
+#include "gegl-chant.h"
+#include <math.h>
+
+/***** Macros *****/
+
+#define MOD(x, y) \
+ ((x) < 0 ? ((y) - 1 - ((y) - 1 - (x)) % (y)) : (x) % (y))
+
+typedef struct
+{
+ gdouble lx, ly; /* X and Y components of light vector */
+ gdouble nz2, nzlz; /* nz^2, nz*lz */
+ gdouble background; /* Shade for vertical normals */
+ gdouble compensation; /* Background compensation */
+ gfloat lut[LUT_TABLE_SIZE]; /* Look-up table for modes - should be made interpolated*/
+} bumpmap_params_t;
+
+static void
+bumpmap_setup_calc (GeglChantO *o,
+ bumpmap_params_t *params)
+{
+ /* Convert to radians */
+ const gdouble azimuth = G_PI * o->azimuth / 180.0;
+ const gdouble elevation = G_PI * o->elevation / 180.0;
+
+ double lz, nz;
+ gint i;
+
+ /* Calculate the light vector */
+ params->lx = cos (azimuth) * cos (elevation);
+ params->ly = sin (azimuth) * cos (elevation);
+ lz = sin (elevation);
+
+ /* Calculate constant Z component of surface normal */
+ /* (depth may be 0 if non-interactive) */
+ nz = 6.0 / MAX(o->depth,0.001);
+ params->nz2 = nz * nz;
+ params->nzlz = nz * lz;
+
+ /* Optimize for vertical normals */
+ params->background = lz;
+
+ /* Calculate darkness compensation factor */
+ params->compensation = sin(elevation);
+
+ /* Create look-up table for map type */
+ for (i = 0; i < LUT_TABLE_SIZE; i++)
+ {
+ gdouble n;
+
+ switch (o->type)
+ {
+ case GEGL_BUMP_MAP_TYPE_SPHERICAL:
+ n = 1.0 - 1.0*i / LUT_TABLE_SIZE;
+ params->lut[i] = sqrt(1.0 - n * n) + 0.5;
+ break;
+
+ case GEGL_BUMP_MAP_TYPE_SINUSOIDAL:
+ n = 1.0 * i / (LUT_TABLE_SIZE-1);
+ params->lut[i] = (sin((-G_PI / 2.0) + G_PI * n) + 1.0) / 2.0 + 0.5;
+ break;
+
+ case GEGL_BUMP_MAP_TYPE_LINEAR:
+ default:
+ params->lut[i] = 1.0*i/(LUT_TABLE_SIZE-1);
+ }
+
+ if (o->invert)
+ params->lut[i] = 1.0 - params->lut[i];
+ }
+}
+
+static void
+bumpmap_row (const gfloat *src,
+ gfloat *dest,
+ gint width,
+ gint nchannels,
+ gint has_alpha,
+ const gfloat *bm_row1,
+ const gfloat *bm_row2,
+ const gfloat *bm_row3,
+ gint bm_width,
+ gint bm_xofs,
+ gboolean row_in_bumpmap,
+ GeglChantO *o,
+ bumpmap_params_t *params)
+{
+ gint xofs1, xofs2, xofs3;
+ gint x, k;
+ gdouble result;
+
+ xofs2 = MOD (bm_xofs,bm_width);
+
+ for (x = 0; x < width; x++)
+ {
+ double shade;
+ double nx, ny;
+
+ /* Calculate surface normal from bump map */
+
+ if (o->tiled || (row_in_bumpmap &&
+ x >= - bm_xofs && x < - bm_xofs + bm_width))
+ {
+ if (o->tiled)
+ {
+ xofs1 = MOD (xofs2 - 1, bm_width);
+ xofs3 = MOD (xofs2 + 1, bm_width);
+ }
+ else
+ {
+ xofs1 = CLAMP (xofs2 - 1, 0, bm_width - 1);
+ xofs3 = CLAMP (xofs2 + 1, 0, bm_width - 1);
+ }
+
+ nx = (bm_row1[xofs1] + bm_row2[xofs1] + bm_row3[xofs1] -
+ bm_row1[xofs3] - bm_row2[xofs3] - bm_row3[xofs3]);
+ ny = (bm_row3[xofs1] + bm_row3[xofs2] + bm_row3[xofs3] -
+ bm_row1[xofs1] - bm_row1[xofs2] - bm_row1[xofs3]);
+ }
+ else
+ {
+ nx = ny = 0;
+ }
+
+ if ((nx == 0) && (ny == 0))
+ {
+ shade = params->background;
+ }
+ else
+ {
+ double ndotl = nx * params->lx + ny * params->ly + params->nzlz;
+
+ if (ndotl < 0)
+ {
+ shade = params->compensation * o->ambient;
+ }
+ else
+ {
+ double pre_shade = ndotl / sqrt (nx * nx + ny * ny + params->nz2);
+
+ shade = pre_shade + MAX(0, (params->compensation - pre_shade)) *
+ o->ambient;
+ }
+ }
+
+ /* Paint */
+ if (o->compensate)
+ {
+ for (k = nchannels-has_alpha; k; k--)
+ {
+ result = (*src++ * shade) / params->compensation;
+ *dest++ = result;
+ }
+ }
+ else
+ {
+ for (k = nchannels-has_alpha; k; k--)
+ *dest++ = *src++ * shade;
+ }
+
+ if (has_alpha)
+ *dest++ = *src++;
+
+ /* Next pixel */
+ if (++xofs2 == bm_width)
+ xofs2 = 0;
+ }
+}
+
+// Map the FIFO bumpmap row according to the lookup table.
+static void
+bumpmap_convert_row (gfloat *row,
+ gint width,
+ const gfloat *lut,
+ double waterlevel)
+{
+ gfloat *p = row;
+
+ for (; width; width--)
+ {
+ int lut_idx = CLAMP((int)(waterlevel * (LUT_TABLE_SIZE-1)
+ + *p *(LUT_TABLE_SIZE-1)*(1.0-waterlevel)),0,(LUT_TABLE_SIZE-1));
+ *p++ = lut[lut_idx];
+ }
+}
+
+static void
+prepare (GeglOperation *operation)
+{
+ gegl_operation_set_format (operation, "input", babl_format ("RGBA float"));
+ gegl_operation_set_format (operation, "aux", babl_format ("Y float"));
+ gegl_operation_set_format (operation, "output", babl_format ("RGBA float"));
+}
+
+static gboolean
+process (GeglOperation *operation,
+ GeglBuffer *input,
+ GeglBuffer *aux,
+ GeglBuffer *output,
+ const GeglRectangle *rect,
+ gint level)
+{
+ GeglChantO *o = GEGL_CHANT_PROPERTIES (operation);
+ gfloat *src_buf, *dst_buf, *bm_row1, *bm_row2, *bm_row3, *src_row, *dest_row, *bm_tmprow;
+ const Babl *format = gegl_operation_get_format(operation,"output");
+ gint channels = babl_format_get_n_components(format);
+ bumpmap_params_t params;
+ gint yofs1, yofs2, yofs3;
+ gint row_stride;
+ gint row, y;
+ gint bm_width, bm_height;
+ gint slice_thickness = 32;
+ gboolean first_time = TRUE;
+
+ // This should be made more sophisticated
+ int has_alpha = (channels == 4) || (channels == 2);
+
+ bm_width = gegl_buffer_get_width(aux);
+ bm_height = gegl_buffer_get_height(aux);
+
+ src_buf = g_new0 (gfloat, rect->width * slice_thickness * channels);
+ dst_buf = g_new0 (gfloat, rect->width * slice_thickness * channels);
+
+ bumpmap_setup_calc (o, ¶ms);
+
+ /* Initialize offsets */
+ if (o->tiled)
+ {
+ yofs2 = MOD (o->yofs, bm_height);
+ yofs1 = MOD (yofs2 - 1, bm_height);
+ yofs3 = MOD (yofs2 + 1, bm_height);
+ }
+ else
+ {
+ yofs2 = o->yofs;
+ yofs1 = yofs2-1;
+ yofs3 = yofs2+1;
+ }
+
+ /* Initialize three line fifo buffers */
+ bm_row1 = g_new (gfloat, bm_width);
+ bm_row2 = g_new (gfloat, bm_width);
+ bm_row3 = g_new (gfloat, bm_width);
+
+ // The source and destination row stride in floats
+ row_stride = rect->width*channels;
+
+ // Process the input buffer one slice at a time, but the bumpmap one row at a time.
+ for (row=rect->y; row < rect->y+rect->height; row+= slice_thickness)
+ {
+ GeglRectangle rect_slice, bm_rect;
+ rect_slice.x = rect->x;
+ rect_slice.width = rect->width;
+ rect_slice.y = rect->y+row;
+ rect_slice.height = MIN(slice_thickness, rect->height-row);
+ gegl_buffer_get (input, &rect_slice, 1.0, babl_format ("RGBA float"), src_buf,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ // Get the bumpmap one row at a time. The following values are constant
+ // in the bumpmap buffer access.
+ bm_rect.x = 0;
+ bm_rect.width = bm_width;
+ bm_rect.height = 1;
+
+ for (y = 0; y < rect_slice.height; y++)
+ {
+ gboolean row_in_bumpmap = (yofs2 > 0 && yofs2 < bm_height);
+
+ // Fill in the three rows FIFO the first time we are inside the bumpmap.
+ if (row_in_bumpmap && first_time)
+ {
+ first_time = FALSE;
+
+ bm_rect.y = yofs1;
+ gegl_buffer_get (aux, &bm_rect, 1.0, babl_format ("Y float"), bm_row1,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ bm_rect.y = yofs2;
+ gegl_buffer_get (aux, &bm_rect, 1.0, babl_format ("Y float"), bm_row2,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ bm_rect.y = yofs3;
+ gegl_buffer_get (aux, &bm_rect, 1.0, babl_format ("Y float"), bm_row3,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ bumpmap_convert_row (bm_row1, bm_width, params.lut, o->waterlevel);
+ bumpmap_convert_row (bm_row2, bm_width, params.lut, o->waterlevel);
+ bumpmap_convert_row (bm_row3, bm_width, params.lut, o->waterlevel);
+ }
+
+
+ src_row = src_buf + y * row_stride;
+ dest_row = dst_buf + y * row_stride;
+
+ bumpmap_row (src_row, dest_row,
+ rect->width,
+ channels,
+ has_alpha,
+ bm_row1, bm_row2, bm_row3,
+ bm_width,
+ o->xofs,
+ row_in_bumpmap,
+ o,
+ ¶ms);
+
+ if (!first_time)
+ {
+ /* Next line */
+ bm_tmprow = bm_row1;
+ bm_row1 = bm_row2;
+ bm_row2 = bm_row3;
+ bm_row3 = bm_tmprow;
+ }
+
+ if (++yofs2 == bm_height && o->tiled)
+ yofs2 = 0;
+
+ if (o->tiled)
+ yofs3 = MOD (yofs2 + 1, bm_height);
+ else
+ yofs3 = CLAMP (yofs2 + 1, 0, bm_height);
+
+ if (!first_time)
+ {
+ bm_rect.y = yofs3;
+ gegl_buffer_get (aux, &bm_rect, 1.0, babl_format ("Y float"), bm_row3,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ bumpmap_convert_row (bm_row3, bm_width, params.lut, o->waterlevel);
+ }
+ }
+ gegl_buffer_set (output, &rect_slice, 0, babl_format ("RGBA float"),
+ dst_buf, GEGL_AUTO_ROWSTRIDE);
+ }
+
+ g_free (bm_row1);
+ g_free (bm_row2);
+ g_free (bm_row3);
+
+ g_free (dst_buf);
+ g_free (src_buf);
+
+ return TRUE;
+}
+
+static GeglRectangle
+get_bounding_box (GeglOperation *self)
+{
+ GeglRectangle result = { 0, 0, 0, 0 };
+ GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (self, "input");
+
+ if (!in_rect)
+ return result;
+
+ return *in_rect;
+}
+
+static void
+gegl_chant_class_init (GeglChantClass *klass)
+{
+ GeglOperationClass *operation_class;
+ GeglOperationComposerClass *composer_class;
+
+ operation_class = GEGL_OPERATION_CLASS (klass);
+ composer_class = GEGL_OPERATION_COMPOSER_CLASS (klass);
+
+ operation_class->prepare = prepare;
+ operation_class->get_bounding_box = get_bounding_box;
+ composer_class->process = process;
+
+ gegl_operation_class_set_keys (operation_class,
+ "name", "gegl:bump-map",
+ "categories", "map",
+ "description", _( "This plug-in uses the algorithm described by John "
+ "Schlag, \"Fast Embossing Effects on Raster Image "
+ "Data\" in Graphics GEMS IV (ISBN 0-12-336155-9). "
+ "It takes a drawable to be applied as a bump "
+ "map to another image and produces a nice embossing "
+ "effect." ),
+ NULL);
+}
+#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]