emboss.c 16.5 KB
Newer Older
Elliot Lee's avatar
Elliot Lee committed
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
/**************************************************
 * file: emboss/emboss.c
 *
 * Copyright (c) 1997 Eric L. Hernes (erich@rrnet.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software withough specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

27 28
#include "config.h"

Elliot Lee's avatar
Elliot Lee committed
29
#include <string.h>
30 31 32

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
33

34
#include "libgimp/stdplugins-intl.h"
Manish Singh's avatar
Manish Singh committed
35

36

37 38
#define PLUG_IN_PROC   "plug-in-emboss"
#define PLUG_IN_BINARY "emboss"
39
#define PLUG_IN_ROLE   "gimp-emboss"
40 41


42 43 44 45 46 47
enum
{
  FUNCTION_BUMPMAP = 0,
  FUNCTION_EMBOSS  = 1
};

48
typedef struct
49
{
50 51 52 53
  gdouble  azimuth;
  gdouble  elevation;
  gint32   depth;
  gint32   embossp;
54
} piArgs;
Elliot Lee's avatar
Elliot Lee committed
55

56 57 58 59 60
static piArgs evals =
{
  30.0,    /* azimuth   */
  45.0,    /* elevation */
  20,      /* depth     */
61
  1        /* emboss    */
62 63
};

64 65 66 67 68 69 70 71 72
struct embossFilter
{
  gdouble Lx;
  gdouble Ly;
  gdouble Lz;
  gdouble Nz;
  gdouble Nz2;
  gdouble NzLz;
  gdouble bg;
73
} static Filter;
Elliot Lee's avatar
Elliot Lee committed
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
static void     query         (void);
static void     run           (const gchar      *name,
                               gint              nparam,
                               const GimpParam  *param,
                               gint             *nretvals,
                               GimpParam       **retvals);

static void     emboss        (GimpDrawable     *drawable,
                               GimpPreview      *preview);
static gboolean emboss_dialog (GimpDrawable     *drawable);

static void     emboss_init   (gdouble           azimuth,
                               gdouble           elevation,
                               gushort           width45);
static void     emboss_row    (const guchar     *src,
                               const guchar     *texture,
                               guchar           *dst,
                               guint             width,
                               guint             bypp,
                               gboolean          alpha);

Elliot Lee's avatar
Elliot Lee committed
96

97
#define DtoR(d) ((d)*(G_PI/(gdouble)180))
Elliot Lee's avatar
Elliot Lee committed
98

99

100
const GimpPlugInInfo PLUG_IN_INFO =
101 102 103
{
  NULL,  /* init  */
  NULL,  /* quit  */
Elliot Lee's avatar
Elliot Lee committed
104
  query, /* query */
105
  run,   /* run   */
Elliot Lee's avatar
Elliot Lee committed
106 107
};

108
MAIN ()
Elliot Lee's avatar
Elliot Lee committed
109 110

static void
111 112
query (void)
{
113
  static const GimpParamDef args[] =
114
  {
115
    { GIMP_PDB_INT32,    "run-mode",  "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"  },
116 117 118 119 120
    { GIMP_PDB_IMAGE,    "image",     "The Image"                     },
    { GIMP_PDB_DRAWABLE, "drawable",  "The Drawable"                  },
    { GIMP_PDB_FLOAT,    "azimuth",   "The Light Angle (degrees)"     },
    { GIMP_PDB_FLOAT,    "elevation", "The Elevation Angle (degrees)" },
    { GIMP_PDB_INT32,    "depth",     "The Filter Width"              },
121
    { GIMP_PDB_INT32,    "emboss",    "Emboss or Bumpmap"             }
Elliot Lee's avatar
Elliot Lee committed
122 123
  };

124
  gimp_install_procedure (PLUG_IN_PROC,
125
                          N_("Simulate an image created by embossing"),
David Odin's avatar
David Odin committed
126 127 128 129 130
                          "Emboss or Bumpmap the given drawable, specifying "
                          "the angle and elevation for the light source.",
                          "Eric L. Hernes, John Schlag",
                          "Eric L. Hernes",
                          "1997",
131
                          N_("_Emboss (legacy)..."),
David Odin's avatar
David Odin committed
132 133 134 135
                          "RGB*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);
136

137
  gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Distorts");
Elliot Lee's avatar
Elliot Lee committed
138 139 140
}

static void
141 142 143 144 145
run (const gchar      *name,
     gint              nparam,
     const GimpParam  *param,
     gint             *nretvals,
     GimpParam       **retvals)
Elliot Lee's avatar
Elliot Lee committed
146
{
147
  static GimpParam  rvals[1];
148
  GimpDrawable     *drawable;
Elliot Lee's avatar
Elliot Lee committed
149 150 151 152

  *nretvals = 1;
  *retvals = rvals;

153 154
  INIT_I18N ();

155
  drawable = gimp_drawable_get (param[2].data.d_drawable);
156 157
  gimp_tile_cache_ntiles (drawable->ntile_cols);

Elliot Lee's avatar
Elliot Lee committed
158

Sven Neumann's avatar
Sven Neumann committed
159 160
  rvals[0].type = GIMP_PDB_STATUS;
  rvals[0].data.d_status = GIMP_PDB_SUCCESS;
161

Elliot Lee's avatar
Elliot Lee committed
162 163
  switch (param[0].data.d_int32)
    {
Sven Neumann's avatar
Sven Neumann committed
164
    case GIMP_RUN_INTERACTIVE:
165
      gimp_get_data (PLUG_IN_PROC, &evals);
Elliot Lee's avatar
Elliot Lee committed
166

167
      if (! emboss_dialog (drawable))
David Odin's avatar
David Odin committed
168
        {
169
          rvals[0].data.d_status = GIMP_PDB_CANCEL;
David Odin's avatar
David Odin committed
170
        }
Elliot Lee's avatar
Elliot Lee committed
171
      else
David Odin's avatar
David Odin committed
172
        {
173
          gimp_set_data (PLUG_IN_PROC, &evals, sizeof (piArgs));
David Odin's avatar
David Odin committed
174
        }
Elliot Lee's avatar
Elliot Lee committed
175 176 177

      break;

Sven Neumann's avatar
Sven Neumann committed
178
    case GIMP_RUN_NONINTERACTIVE:
Elliot Lee's avatar
Elliot Lee committed
179
      if (nparam != 7)
David Odin's avatar
David Odin committed
180 181 182 183
        {
          rvals[0].data.d_status = GIMP_PDB_CALLING_ERROR;
          break;
        }
Elliot Lee's avatar
Elliot Lee committed
184

185 186 187 188
      evals.azimuth   = param[3].data.d_float;
      evals.elevation = param[4].data.d_float;
      evals.depth     = param[5].data.d_int32;
      evals.embossp   = param[6].data.d_int32;
Elliot Lee's avatar
Elliot Lee committed
189

190
      emboss (drawable, NULL);
191
      break;
Elliot Lee's avatar
Elliot Lee committed
192

Sven Neumann's avatar
Sven Neumann committed
193
    case GIMP_RUN_WITH_LAST_VALS:
194
      gimp_get_data (PLUG_IN_PROC, &evals);
195
      /* use this image and drawable, even with last args */
196
      emboss (drawable, NULL);
Elliot Lee's avatar
Elliot Lee committed
197 198 199 200 201
    break;
  }
}

#define pixelScale 255.9
202

203
static void
204 205 206
emboss_init (gdouble azimuth,
             gdouble elevation,
             gushort width45)
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
{
  /*
   * compute the light vector from the input parameters.
   * normalize the length to pixelScale for fast shading calculation.
   */
  Filter.Lx = cos (azimuth) * cos (elevation) * pixelScale;
  Filter.Ly = sin (azimuth) * cos (elevation) * pixelScale;
  Filter.Lz = sin (elevation) * pixelScale;

  /*
   * constant z component of image surface normal - this depends on the
   * image slope we wish to associate with an angle of 45 degrees, which
   * depends on the width of the filter used to produce the source image.
   */
  Filter.Nz = (6 * 255) / width45;
  Filter.Nz2 = Filter.Nz * Filter.Nz;
  Filter.NzLz = Filter.Nz * Filter.Lz;

  /* optimization for vertical normals: L.[0 0 1] */
  Filter.bg = Filter.Lz;
Elliot Lee's avatar
Elliot Lee committed
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
}


/*
 * ANSI C code from the article
 * "Fast Embossing Effects on Raster Image Data"
 * by John Schlag, jfs@kerner.com
 * in "Graphics Gems IV", Academic Press, 1994
 *
 *
 * Emboss - shade 24-bit pixels using a single distant light source.
 * Normals are obtained by differentiating a monochrome 'bump' image.
 * The unary case ('texture' == NULL) uses the shading result as output.
 * The binary case multiples the optional 'texture' image by the shade.
 * Images are in row major order with interleaved color components (rgbrgb...).
242
 * E.g., component c of pixel x,y of 'dst' is dst[3*(y*width + x) + c].
Elliot Lee's avatar
Elliot Lee committed
243 244 245
 *
 */

246 247 248 249 250 251 252
static void
emboss_row (const guchar *src,
            const guchar *texture,
            guchar       *dst,
            guint         width,
            guint         bypp,
            gboolean      alpha)
253
{
254 255 256
  const guchar *s[3];
  gdouble       M[3][3];
  gint          x, bytes;
257 258

  /* mung pixels, avoiding edge pixels */
259 260 261
  s[0] = src;
  s[1] = s[0] + (width * bypp);
  s[2] = s[1] + (width * bypp);
262 263 264 265 266
  dst += bypp;

  bytes = (alpha) ? bypp - 1 : bypp;

  if (texture)
267
    texture += (width + 1) * bypp;
268

269
  for (x = 1; x < width - 1; x++)
270
    {
271 272 273 274 275
      gdouble a;
      glong   Nx, Ny, NdotL;
      gint    shade, b;
      gint    i, j;

276 277
      for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
278
          M[i][j] = 0.0;
279 280

      for (b = 0; b < bytes; b++)
281 282 283 284 285
        {
          for (i = 0; i < 3; i++)
            for (j = 0; j < 3; j++)
              {
                if (alpha)
286
                  a = s[i][j * bypp + bytes] / 255.0;
287 288 289
                else
                  a = 1.0;

290
                M[i][j] += a * s[i][j * bypp + b];
291 292
              }
        }
293 294 295

      Nx = M[0][0] + M[1][0] + M[2][0] - M[0][2] - M[1][2] - M[2][2];
      Ny = M[2][0] + M[2][1] + M[2][2] - M[0][0] - M[0][1] - M[0][2];
296 297

      /* shade with distant light source */
Elliot Lee's avatar
Elliot Lee committed
298
      if ( Nx == 0 && Ny == 0 )
David Odin's avatar
David Odin committed
299
        shade = Filter.bg;
300
      else if ( (NdotL = Nx * Filter.Lx + Ny * Filter.Ly + Filter.NzLz) < 0 )
David Odin's avatar
David Odin committed
301
        shade = 0;
Elliot Lee's avatar
Elliot Lee committed
302
      else
David Odin's avatar
David Odin committed
303
        shade = NdotL / sqrt(Nx*Nx + Ny*Ny + Filter.Nz2);
Elliot Lee's avatar
Elliot Lee committed
304

305 306
      /* do something with the shading result */
      if (texture)
David Odin's avatar
David Odin committed
307 308
        {
          for (b = 0; b < bytes; b++)
309 310
            *dst++ = (*texture++ * shade) >> 8;

David Odin's avatar
David Odin committed
311 312
          if (alpha)
            {
313
              *dst++ = s[1][bypp + bytes]; /* preserve the alpha */
David Odin's avatar
David Odin committed
314 315 316
              texture++;
            }
        }
317
      else
David Odin's avatar
David Odin committed
318 319
        {
          for (b = 0; b < bytes; b++)
320 321
            *dst++ = shade;

David Odin's avatar
David Odin committed
322
          if (alpha)
323
            *dst++ = s[1][bypp + bytes]; /* preserve the alpha */
David Odin's avatar
David Odin committed
324
        }
325 326 327

      for (i = 0; i < 3; i++)
        s[i] += bypp;
328
    }
329

330 331
  if (texture)
    texture += bypp;
Elliot Lee's avatar
Elliot Lee committed
332 333
}

334
static void
335 336
emboss (GimpDrawable *drawable,
        GimpPreview  *preview)
337
{
338 339 340 341
  GimpPixelRgn  src, dst;
  gint          p_update;
  gint          y;
  gint          x1, y1, x2, y2;
342
  gint          width, height;
343 344
  gint          bypp, rowsize;
  gboolean      has_alpha;
345
  guchar       *srcbuf, *dstbuf;
Elliot Lee's avatar
Elliot Lee committed
346

347 348 349 350 351 352 353 354 355
  if (preview)
    {
      gimp_preview_get_position (preview, &x1, &y1);
      gimp_preview_get_size (preview, &width, &height);
      x2 = x1 + width;
      y2 = y1 + height;
    }
  else
    {
356 357
      if (! gimp_drawable_mask_intersect (drawable->drawable_id,
                                          &x1, &y1, &width, &height))
358
        return;
359

360 361 362
      /* expand the bounds a little */
      x1 = MAX (0, x1 - evals.depth);
      y1 = MAX (0, y1 - evals.depth);
363 364
      x2 = MIN (drawable->width, x1 + width + evals.depth);
      y2 = MIN (drawable->height, y1 + height + evals.depth);
365

366
      width  = x2 - x1;
367 368
      height = y2 - y1;
    }
369

370
  bypp = drawable->bpp;
371
  p_update = MAX (1, height / 20);
Elliot Lee's avatar
Elliot Lee committed
372
  rowsize = width * bypp;
373
  has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);
Elliot Lee's avatar
Elliot Lee committed
374

375 376 377 378 379 380
  gimp_pixel_rgn_init (&src, drawable,
                       x1, y1, width, height,
                       FALSE, FALSE);
  gimp_pixel_rgn_init (&dst, drawable,
                       x1, y1, width, height,
                       preview == NULL, TRUE);
Elliot Lee's avatar
Elliot Lee committed
381

382 383
  srcbuf = g_new0 (guchar, rowsize * 3);
  dstbuf = g_new0 (guchar, rowsize);
Elliot Lee's avatar
Elliot Lee committed
384

385
  emboss_init (DtoR(evals.azimuth), DtoR(evals.elevation), evals.depth);
386 387
  if (!preview)
    gimp_progress_init (_("Emboss"));
Elliot Lee's avatar
Elliot Lee committed
388 389

  /* first row */
390 391
  gimp_pixel_rgn_get_rect (&src, srcbuf, x1, y1, width, 3);
  memcpy (srcbuf, srcbuf + rowsize, rowsize);
392 393
  emboss_row (srcbuf, evals.embossp ? NULL : srcbuf,
              dstbuf, width, bypp, has_alpha);
394
  gimp_pixel_rgn_set_row (&dst, dstbuf, 0, 0, width);
Elliot Lee's avatar
Elliot Lee committed
395

396
  /* middle rows */
397 398
  for (y = 0; y < height - 2; y++)
    {
399
      if (! preview && (y % p_update == 0))
400
        gimp_progress_update ((gdouble) y / (gdouble) height);
401

402 403 404 405 406 407 408 409 410 411 412 413
      gimp_pixel_rgn_get_rect (&src, srcbuf, x1, y1 + y, width, 3);
      emboss_row (srcbuf, evals.embossp ? NULL : srcbuf,
                  dstbuf, width, bypp, has_alpha);
      gimp_pixel_rgn_set_row (&dst, dstbuf, x1, y1 + y + 1, width);
    }

  /* last row */
  gimp_pixel_rgn_get_rect (&src, srcbuf, x1, y2 - 3, width, 3);
  memcpy (srcbuf + rowsize * 2, srcbuf + rowsize, rowsize);
  emboss_row (srcbuf, evals.embossp ? NULL : srcbuf,
              dstbuf, width, bypp, has_alpha);
  gimp_pixel_rgn_set_row (&dst, dstbuf, x1, y2 - 1, width);
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428

  if (preview)
    {
      gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview),
                                         &dst);
    }
  else
    {
      gimp_progress_update (1.0);

      gimp_drawable_flush (drawable);
      gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
      gimp_drawable_update (drawable->drawable_id, x1, y1, width, height);
      gimp_displays_flush ();
    }
Elliot Lee's avatar
Elliot Lee committed
429

430 431
  g_free (srcbuf);
  g_free (dstbuf);
Elliot Lee's avatar
Elliot Lee committed
432 433
}

434
static gboolean
435
emboss_dialog (GimpDrawable *drawable)
436
{
437 438 439 440 441 442
  GtkWidget     *dialog;
  GtkWidget     *main_vbox;
  GtkWidget     *preview;
  GtkWidget     *radio1;
  GtkWidget     *radio2;
  GtkWidget     *frame;
443
  GtkWidget     *grid;
444 445
  GtkAdjustment *adj;
  gboolean       run;
Elliot Lee's avatar
Elliot Lee committed
446

447
  gimp_ui_init (PLUG_IN_BINARY, TRUE);
448

449
  dialog = gimp_dialog_new (_("Emboss"), PLUG_IN_ROLE,
450
                            NULL, 0,
451
                            gimp_standard_help_func, PLUG_IN_PROC,
452

453 454
                            _("_Cancel"), GTK_RESPONSE_CANCEL,
                            _("_OK"),     GTK_RESPONSE_OK,
Elliot Lee's avatar
Elliot Lee committed
455

456
                            NULL);
457

458
  gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
459 460 461
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);
462

463
  gimp_window_set_transient (GTK_WINDOW (dialog));
464

465
  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
Sven Neumann's avatar
Sven Neumann committed
466
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
467 468
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      main_vbox, TRUE, TRUE, 0);
469
  gtk_widget_show (main_vbox);
Elliot Lee's avatar
Elliot Lee committed
470

471
  preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id);
472
  gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
473 474
  gtk_widget_show (preview);
  g_signal_connect_swapped (preview, "invalidated",
475
                            G_CALLBACK (emboss),
476
                            drawable);
477

478
  frame = gimp_int_radio_group_new (TRUE, _("Function"),
479 480
                                    G_CALLBACK (gimp_radio_button_update),
                                    &evals.embossp, evals.embossp,
481

482 483
                                    _("_Bumpmap"), FUNCTION_BUMPMAP, &radio1,
                                    _("_Emboss"),  FUNCTION_EMBOSS,  &radio2,
484

David Odin's avatar
David Odin committed
485
                                    NULL);
486

487
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
488 489
  gtk_widget_show (frame);

490 491 492 493 494 495 496
  g_signal_connect_swapped (radio1, "toggled",
                            G_CALLBACK (gimp_preview_invalidate),
                            preview);
  g_signal_connect_swapped (radio2, "toggled",
                            G_CALLBACK (gimp_preview_invalidate),
                            preview);

497 498 499 500
  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_box_pack_start (GTK_BOX (main_vbox), grid, FALSE, FALSE, 0);
501

502 503 504 505 506
  adj = gimp_scale_entry_new (GTK_GRID (grid), 0, 0,
                              _("_Azimuth:"), 100, 6,
                              evals.azimuth, 0.0, 360.0, 1.0, 10.0, 2,
                              TRUE, 0, 0,
                              NULL, NULL);
507
  g_signal_connect (adj, "value-changed",
508 509
                    G_CALLBACK (gimp_double_adjustment_update),
                    &evals.azimuth);
510
  g_signal_connect_swapped (adj, "value-changed",
511 512
                            G_CALLBACK (gimp_preview_invalidate),
                            preview);
513

514 515 516 517 518
  adj = gimp_scale_entry_new (GTK_GRID (grid), 0, 1,
                              _("E_levation:"), 100, 6,
                              evals.elevation, 0.0, 180.0, 1.0, 10.0, 2,
                              TRUE, 0, 0,
                              NULL, NULL);
519
  g_signal_connect (adj, "value-changed",
520 521
                    G_CALLBACK (gimp_double_adjustment_update),
                    &evals.elevation);
522
  g_signal_connect_swapped (adj, "value-changed",
523 524
                            G_CALLBACK (gimp_preview_invalidate),
                            preview);
525

526 527 528 529 530
  adj = gimp_scale_entry_new (GTK_GRID (grid), 0, 2,
                              _("_Depth:"), 100, 6,
                              evals.depth, 1.0, 100.0, 1.0, 5.0, 0,
                              TRUE, 0, 0,
                              NULL, NULL);
531
  g_signal_connect (adj, "value-changed",
532 533
                    G_CALLBACK (gimp_int_adjustment_update),
                    &evals.depth);
534
  g_signal_connect_swapped (adj, "value-changed",
535 536
                            G_CALLBACK (gimp_preview_invalidate),
                            preview);
537

538
  gtk_widget_show (grid);
539

540
  gtk_widget_show (dialog);
541

542
  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
543

544
  gtk_widget_destroy (dialog);
Elliot Lee's avatar
Elliot Lee committed
545

546
  if (run)
547
    emboss (drawable, NULL);
Elliot Lee's avatar
Elliot Lee committed
548

549
  return run;
550
}